Головна / Статті

Практичні нотатки: створення самокоригуючого AI-агента за допомогою Self-RAG та LangGraph

Покрокова інструкція з практичних нотаток: створення самокоригуючого AI-агента за допомогою Self-RAG та LangGraph: контракти, перевірки та готові блоки коду для команд, які використовують цю схему.

Видавець: REACTAPP.TOP30 хв читання6935 слів

Джерела: Build a Self-Correcting AI Agent with Self-RAG & LangGraph

У цьому посібнику описано процес створення системи від сировини до готового продукту для: створення самокоригуючого AI-агента з використанням Self-RAG та LangGraph. Основна увага приділяється конкретним крокам виконання, чітким перевіркам та коду, який можна просто додати до репозиторію без необхідності здогадуватися щодо його призначення.

Чому стандартні пайплайни RAG є крихкими

Щоб зрозуміти, чому стандартні пайплайни RAG є крихкими, необхідно спочатку визначити вхідні дані, відповідального за кожен крок та критерії завершення роботи перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись здогадатися про прихований стан системи. Конфігурацію слід тримати окремо від коду програми – файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь код. Також необхідно посилатися на конкретні уривки тексту, які лежать в основі відповіді; без цих посилань оператори не зможуть відрізнити галюцинації від проблем із індексуванням.

Лінійний пайплайн: отримання та припущення

Для лінійної системи обробки: перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори мають змогу перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Указуйте ті частини тексту, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинацію від проблем із індексуванням.

[User Query] ──> [Vector Database] ──> [Top-K Chunks] ──> [LLM Prompt] ──> [Final Answer]

Анатомія тихої несправності

Для аналізу «тихої» невдачі необхідно визначити вхідні дані, виконавця кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій. Наводьте ті уривки, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинацію від проблем із індексуванням.

Коренева причина: сліпа довіра

Щодо кореневої причини: сліпої довіри, необхідно визначити вхідні дані, відповідального за крок та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового завершення. Наводьте цитати з тих частин тексту, які фактично лежать в основі відповіді. Без цитат оператори не зможуть відрізнити галюцинацію від проблем із індексуванням.

Рішення: впровадження саморефлексивних циклів

Для рішення: впроваджуючи саморефлексивні цикли, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам під час переходу з демо-середовища у спільні середовища. Наводьте уривки тексту, які фактично лягли в основу відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням. Для рішення: впроваджуючи саморефлексивні цикли, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Документуйте як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продакшн-середовища.

ct, не пізніше ніж після опрацювання.

Під час роботи над проектом «Від сліпого пошуку до саморефлексії: патерн Self-RAG» спочатку запишіть умови використання: необхідні дані вхіду, сигнал успіху та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Коли якась крок виконується невдало, причина має стосуватися лише однієї функції, а не всієї складної системи. Перед налаштуванням запитів вимірюйте рівень відтворення інформації на фіксованому наборі запитань. Часта зміна формулювань запитів рідко допомагає покращити якість пошуку.

Під час роботи над «Трьома стовпами самокорекції» спочатку запишіть угоду: необхідні вхідні дані, сигнал успіху та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Розглядайте цей етап як угоду між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте беззвучного часткового виконання завдань. Вимірюйте рівень запам’ятовування на фіксованому наборі запитань перед налаштуванням підказок. Часта зміна підказок рідко виправляє проблеми з недостатньою ефективністю пошуку інформації.

       [ Query ]
           │
           ▼
   1. Is Retrieval Needed? ──(No)──► Generate directly from parametric memory
           │
         (Yes)
           ▼
     [ Retrieve ]
           │
           ▼
   2. Are Docs Relevant? ──(No)──► Reformulate search query & retry
           │
         (Yes)
           ▼
     [ Generate ]
           │
           ▼
   3. Is Answer Faithful? ──(No)──► Regenerate or extract better context
           │
         (Yes)
           ▼
     [ Final Output ]

Токени рефлексії як сигнали керування

Під час роботи з Reflection Tokens як керуючими сигналами спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Відображення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-середовища до спільних. Зберігайте у кеші стабільні інструкції системи та схеми інструментів. Повторна передача ідентичних даних є поширеною причиною витрат. Під час роботи з Reflection Tokens як керуючими сигналами спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Одночасно задокументуйте оптимальний та відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

Архітектура саморефлексії

Архітектура саморефлексії працює найкраще, коли її розглядають як вимірювану поверхню. Запишіть один ідеальний приклад, один випадок невдачі та примітку про скасування змін перед розширенням обсягу роботи. Віддавайте перевагу невеликим, тестованим одиницям перед складними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність дій. Розділяйте політику часткового оброблення даних та політику їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

graph TD
    Start[User Query] --> DecideNeed{Is Retrieval Needed?}
    DecideNeed -- No --> GenerateDirect[Generate Output]
    DecideNeed -- Yes --> Retrieve[Retrieve Documents]
    Retrieve --> GradeDocs[Grade Document Relevance]
    GradeDocs -- Irrelevant --> Fallback[Rewrite Query / Fallback]
    GradeDocs -- Relevant --> GenerateRef[Generate Response with Context]
    GenerateRef --> GradeFaith[Grade Faithfulness & Hallucinations]
    GradeFaith -- Hallucinated --> GenerateRef
    GradeFaith -- Faithful --> GradeUtility[Grade Utility]
    GradeUtility -- Low Utility --> Rewrite[Refine Response]
    GradeUtility -- High Utility --> End[Final Answer]

Порівняння конвеєрів обробки та робочих процесів

Порівняння потоків обробки даних та робочих процесів найкраще проводити, якщо розглядати їх як вимірювану структуру. Збережіть один ідеальний зразок виконання, один випадок невдачі та примітки щодо скасування змін перед розширенням обсягу роботи. Розглядайте цей етап як угоду між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового виконання завдань. Розділіть політику розбиття на частини та політику отримання даних. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

Архітектура з використанням LangGraph: від лінійних ланцюгів до машин станів

Архітектура з LangGraph: від лінійних ланцюгів до машин станів працює найкраще, коли її розглядають як вимірювану поверхню. Збережіть один ідеальний приклад виконання, один випадок збою та примітки щодо скасування змін перед розширенням обсягу роботи. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ. Розділіть політику часткового оброблення даних від політики їх пошуку. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості. Архітектура з LangGraph: від лінійних ланцюгів до машин станів працює найкраще, коли її розглядають як вимірювану поверхню. Збережіть один ідеальний приклад виконання, один випадок збою та примітки щодо скасування змін перед розширенням обсягу роботи. Документуйте як успішний, так і відновлювальний сценарії роботи разом. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

Linear RAG:    [Query] ──> [Retrieve] ──> [Generate] ──> [Response] (No correction)
Self-RAG:      [Query] ──> [Retrieve] ──> [Evaluate] ──┬─ (Valid) ──> [Response]
                              ▲                        │
                              └─────── (Invalid) ──────┘ (Loop & Refine)

Межі лінійних ланцюгів

Для роботи з межами лінійних ланцюгів необхідно спочатку визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру обробки даних. Наводьте ті уривки, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням.

Розкриття таємниць машини станів ШІ

Щоб роз’яснити принципи автомату станів ШІ, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового завершення. Наводьте конкретні уривки, які лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням.

                  ┌──────────────────────┐
                  │        Start         │
                  └──────────┬───────────┘
                             ▼
┌──────────────┐  ┌──────────────────────┐
│  Formulate   │◄─┤    Retrieve Docs     ├─┐
│  New Query   │  └──────────────────────┘ │
└──────▲───────┘                           │
       │                                   ▼
┌──────┴───────┐  ┌──────────────────────┐ │
│  Re-Retrieve │  │   Grade Relevance    │─┘
└──────────────┘  └──────────┬───────────┘
                             ▼
                  ┌──────────────────────┐
                  │    Generate Answer   │
                  └──────────┬───────────┘
                             ▼
                  ┌──────────────────────┐
                  │  Grade Hallucination │
                  └──────────────────────┘

Внутрішня структура основних компонентів LangGraph

У посібнику «Inside the LangGraph Core Components» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке відображення витрат заздалегідь запобігає несподіваним рахункам під час переходу з демо-середовища у спільні. Наводьте конкретні уривки тексту, які лягли в основу відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням. У посібнику «Inside the LangGraph Core Components» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Документуйте як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

Під час роботи над реалізацією центрального стану графа спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Коли якась крок виконується невдало, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій. Перед налаштуванням запитів вимірюйте рівень відтворення інформації на фіксованому наборі запитань. Часта зміна формулювань запитів рідко допомагає покращити ефективність пошуку.

from typing import List, TypedDict
class GraphState(TypedDict):
    """
    Represents the central state of our Self-RAG agent.

    This TypedDict serves as the single source of truth passed between
    each node in the LangGraph execution flow.
    """
    question: str       # The original user query
    generation: str     # The most recent answer generated by the LLM
    documents: List[str] # Relevant text passages retrieved from our database
    steps: List[str]    # An audit trail tracking the sequence of execution steps

Створення графа, частина 1: визначення вузлів стану та дій

Під час роботи над «Створення графа, частина 1: Визначення вузлів стану та дій» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Дайте назви елементам, визначте критерії успіху та не допускайте беззвучного часткового виконання. Вимірюйте рівень запам’ятовування на фіксованому наборі запитань перед налаштуванням підказок. Часта зміна підказок рідко виправляє слабкі алгоритми пошуку.

Розуміння оркестрації на основі стану

Під час вивчення концепції оркестрації, заснованої на станах, спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Запишіть час виконання та витрати на обробку токенів чи запитів поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-середовища до спільних. Перевірте ефективність системи на фіксованому наборі запитань перед налаштуванням формулювань запитів. Часта зміна формулювань рідко допомагає покращити якість пошуку. Під час вивчення концепції оркестрації, заснованої на станах, спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Одночасно задокументуйте оптимальний та резервний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

+-------------------------------------------------------------+
|                         GRAPH STATE                         |
|  - question: str                                            |
|  - documents: List[Document]                                |
|  - generation: str                                          |
|  - relevance_grade: str                                     |
+-------------------------------------------------------------+
                              |
       +----------------------+----------------------+
       |                      |                      |
       v                      v                      v
+--------------+       +--------------+       +--------------+
| Retrieve     |       | Grade Docs   |       | Generate     |
| Node         |       | Node         |       | Node         |
+--------------+       +--------------+       +--------------+

Проектування облікової книги спільного стану

Найкраще працювати над проектуванням облікової книги спільного стану, якщо розглядати її як вимірювану структуру. Збережіть один ідеальний зразок роботи, один випадок збою та примітки щодо скасування змін перед розширенням обсягу роботи. Віддавайте перевагу невеликим, тестованим одиницям перед складними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність дій. Розділяйте політику часткового оброблення даних та політику їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

from typing import List, Dict, Any
from typing_extensions import TypedDict
from langchain_core.documents import Document

class GraphState(TypedDict):
    """
    Represents the state of our Self-RAG graph.
    Attributes:
        question: The user query string.
        documents: A list of retrieved document chunks.
        generation: The generated answer from the LLM.
        relevance_grade: The evaluation score of our documents ('yes' or 'no').
    """
    question: str
    documents: List[Document]
    generation: str
    relevance_grade: str

Вузол отримання даних: взаємодія з базами знань

Вузол отримання даних: взаємодія з базами знань найкраще функціонує, якщо її розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок невдачі та примітку про скасування змін перед розширенням обсягу роботи. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового виконання завдань. Розділіть політику часткового оброблення даних від політики їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.embeddings import FakeEmbeddings

# Initialize a mock vector store to simulate a production knowledge base
embeddings = FakeEmbeddings(size=1536)
vectorstore = InMemoryVectorStore(embeddings)
# Seed our vector store with sample documentation chunks
vectorstore.add_documents([
    Document(page_content="LangGraph is a library for building stateful, multi-actor applications with LLMs.", metadata={"source": "docs"}),
    Document(page_content="Self-RAG is an architecture that introduces self-reflection and grading into retrieval chains.", metadata={"source": "paper"}),
    Document(page_content="Vector databases index text embeddings to enable fast semantic similarity search.", metadata={"source": "tech-blog"})
])
# Create a retriever interface
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
def retrieve(state: GraphState) -> Dict[str, Any]:
    """
    Retrieve documents from the vector database based on the current question.
    Args:
        state (GraphState): The current state of the graph.
    Returns:
        dict: An update to the state containing the retrieved documents.
    """
    print("--- NODE: RETRIEVING DOCUMENTS ---")
    question = state["question"]

    # Perform the semantic vector search
    retrieved_docs = retriever.invoke(question)

    # Return the updated document list to the graph state
    return {"documents": retrieved_docs}

Вузол генерації: синтез з урахуванням контексту

Вузол „Генерація: Синтез з урахуванням контексту“ працює найкраще, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам під час переходу з демо-середовища у спільні середовища. Розділіть політику часткової обробки даних від політики їх пошуку. Зміна однієї з них не повинна змушувати переписувати іншу, коли змінюються показники якості. Вузол „Генерація: Синтез з урахуванням контексту“ працює найкраще, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Одночасно задокументуйте оптимальний та відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.language_models.fake import FakeListLLM

# Set up a mock LLM that simulates generating structured responses
# In production, replace this with: from langchain_openai import ChatOpenAI
llm = FakeListLLM(responses=[
    "Self-RAG is an advanced architecture that uses self-reflection and document grading to improve retrieval accuracy."
])
def generate(state: GraphState) -> Dict[str, Any]:
    """
    Generate an answer using the retrieved documents as grounding context.
    Args:
        state (GraphState): The current state of the graph.
    Returns:
        dict: An update to the state containing the generated answer.
    """
    print("--- NODE: GENERATING ANSWER ---")
    question = state["question"]
    documents = state["documents"]

    # Format the retrieved documents into a single context block
    context = "\n\n".join([doc.page_content for doc in documents])

    # Construct the instruction prompt
    prompt = ChatPromptTemplate.from_template(
        "You are an assistant for question-answering tasks.\n"
        "Use the following pieces of retrieved context to answer the question.\n"
        "If you do not know the answer, say that you do not know.\n"
        "Question: {question}\n"
        "Context: {context}\n"
        "Answer:"
    )

    # Format the prompt with our state variables and run the model
    chain = prompt | llm
    generation = chain.invoke({"question": question, "context": context})

    # Save the generated response to the state
    return {"generation": generation}

Вузол „Документація оцінки: Інспектор якості“

Для вузла документів класу: інспектор якості має визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру обробки даних. Наводьте уривки тексту, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинацію від проблем із індексуванням.

                               +-------------------+
                               |  Retrieved Docs   |
                               +-------------------+
                                         |
                                         v
                             +-----------------------+
                             |   Is Doc Relevant?    |
                             +-----------------------+
                                   /           \
                             Yes  /             \  No
                                 /               \
                                v                 v
                      +-------------------+     +------------------+
                      | Keep in State     |     | Filter / Discard |
                      | (Pass to Gen)     |     | (Trigger Loop)   |
                      +-------------------+     +------------------+
from pydantic import BaseModel, Field
from langchain_core.output_parsers import JsonOutputParser

class DocumentGrade(BaseModel):
    """Binary score for document relevance assessment."""
    binary_score: str = Field(
        description="Relevance score for the document. Must be 'yes' or 'no'."
    )
def grade_documents(state: GraphState) -> Dict[str, Any]:
    """
    Assess whether the retrieved documents are relevant to the user question.
    Filters out any irrelevant documents from the state.
    Args:
        state (GraphState): The current state of the graph.
    Returns:
        dict: Updated documents list and a global relevance grade flag.
    """
    print("--- NODE: GRADING RETRIEVED DOCUMENTS ---")
    question = state["question"]
    documents = state["documents"]

    # Use a mock parser to simulate LLM grading
    # In production, use: llm.with_structured_output(DocumentGrade)
    parser = JsonOutputParser(pydantic_object=DocumentGrade)

    filtered_docs = []
    has_relevant_doc = "no"

    # Evaluate each document individually
    for doc in documents:
        # Construct a strict grading prompt
        grade_prompt = (
            f"Question: {question}\n"
            f"Document: {doc.page_content}\n"
            "Assess if this document is relevant to the question. Output JSON with key 'binary_score' as 'yes' or 'no'."
        )

        # Simulating LLM evaluation logic (in production, run the LLM chain here)
        # For our mock run, if the query terms overlap with the document, mark 'yes'
        is_relevant = any(term.lower() in doc.page_content.lower() for term in question.split())
        score = "yes" if is_relevant else "no"

        if score == "yes":
            print(f"  -> DOCUMENT GRADING: [RELEVANT] - Source: {doc.metadata.get('source')}")
            filtered_docs.append(doc)
            has_relevant_doc = "yes"
        else:
            print(f"  -> DOCUMENT GRADING: [NOT RELEVANT] - Source: {doc.metadata.get('source')}")

    # Update state with filtered documents and set the control flag
    return {
        "documents": filtered_docs,
        "relevance_grade": has_relevant_doc
    }

Створення графа, частина 2: Підключення рішень за допомогою умовних країв

Для створення графа, частина 2: Пов’язування рішень за допомогою умовних країв — спочатку визначте вхідні дані, виконавця кроку та критерії завершення, перш ніж змінювати код. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового завершення роботи. Наводьте конкретні уривки тексту, які лягли в основу відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем із індексуванням.

                  +------------------+
                  |  route_question  |
                  +--------+---------+
                           |
            +--------------+--------------+
            |                             |
     [web_search]                    [retrieve]
            |                             |
            +--------------> +------------+-------------+
                             |     grade_documents       |
                             +------------+--------------+
                                          |
                                    +-----+-----+
                                    |  Decision |
                                    +-----+-----+
                                          |
                        +-----------------+-----------------+
                        |                                   |
                  (transform_query)                     (generate)
                        ^                                   |
                        |                            +------+------+
                        |                            |  Decision   |
                        |                            +------+------+
                        |                                   |
                        +--- [hallucination / unsat] -------+------> [__end__]

Маршрутизатор вхідних точок: спочатку перевірка запиту

Для маршрутизатора вхідної точки: спочатку перевірте запит. Перед зміною коду визначте вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Запишіть час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам під час переходу з демо-середовища у спільні. Наведіть уривки тексту, які фактично лягли в основу відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням. Для маршрутизатора вхідної точки: спочатку перевірте запит. Перед зміною коду визначте вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Одночасно задокументуйте оптимальний та резервний сценарії виконання. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною

товар, а не пізніше його опрацювання.

# router.py
from typing import Literal
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from state import AgentState

# Define the structured output schema for our router
class RouteQuery(BaseModel):
    """Route a user query to the most authoritative datasource."""
    datasource: Literal["vectorstore", "web_search"] = Field(
        ...,
        description="Given a user question choose to route it to web search or a vectorstore.",
    )
def route_question(state: AgentState) -> str:
    """
    Routes the initial question to either the vectorstore retrieval node or web search.

    Args:
        state (AgentState): The current graph state containing the user query.

    Returns:
        str: The next node to execute ("vectorstore" or "web_search").
    """
    print("--- ROUTING QUESTION ---")
    question = state["question"]

    # Initialize our LLM with structured output mapping to our Pydantic schema
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    structured_router = llm.with_structured_output(RouteQuery)

    # Create the routing system prompt
    system_prompt = """You are an expert router directing user questions to a vectorstore or web search.
    The vectorstore contains highly technical documentation regarding LangGraph, LangChain, and state machines.
    Use the vectorstore for these specialized topics. Otherwise, use web_search for general knowledge or current events."""

    route_prompt = ChatPromptTemplate.from_messages([
        ("system", system_prompt),
        ("human", "{question}"),
    ])

    # Run the router chain
    router_chain = route_prompt | structured_router
    decision = router_chain.invoke({"question": question})

    if decision.datasource == "web_search":
        print("--- ROUTING TO WEB SEARCH ---")
        return "web_search"

    print("--- ROUTING TO VECTORSTORE ---")
    return "retrieve"

The Context Gate: Оцінка релевантності документа

Під час роботи над The Context Gate: Оцінка релевантності документа спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає уникнути нечесних змін у коді пізніше. Віддавайте перевагу невеликим, тестованим одиницям перед об’ємними скриптами. Коли якась крок виявляється невдалим, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність дій. Вимірюйте рівень відтворення інформації за фіксованим набором запитань перед налаштуванням підказок. Часта зміна підказок рідко вирішує проблеми слабкого пошуку інформації.

# context_gate.py
from state import AgentState

def decide_to_generate(state: AgentState) -> str:
    """
    Evaluates the document grading state to choose the next step.

    If any documents were graded as relevant, we proceed to answer generation.
    If all documents were deemed irrelevant, we rewrite the query to try again.

    Args:
        state (AgentState): The current state of the agent.

    Returns:
        str: Next node to execute ("generate" or "transform_query").
    """
    print("--- ASSESSING DOCUMENT RELEVANCE ---")
    # Retrieve the decision variable set by the grading node
    run_web_search = state.get("run_web_search", False)

    if run_web_search:
        # No documents were relevant, we must rewrite and search the web
        print("--- DECISION: ALL DOCUMENTS IRRELEVANT, ROUTING TO QUERY TRANSFORMATION ---")
        return "transform_query"

    # We found relevant documents, we can proceed safely to generation
    print("--- DECISION: RELEVANT DOCUMENTS FOUND, ROUTING TO GENERATE ---")
    return "generate"

The Self-Correction Loop: Перевірка точності та корисності

Під час роботи над «Циклом самокорекції: перевірка точності та корисності» спочатку запишіть угоду: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Розглядайте цей етап як угоду між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте беззвучного часткового виконання завдань. Вимірюйте рівень запам’ятовування на фіксованому наборі запитань перед налаштуванням запрошень. Часта зміна запрошень рідко виправляє проблеми з недостатньою ефективністю пошуку.

                          +-------------------------+
                          |   Answer Generated      |
                          +------------+------------+
                                       |
                                       v
                      +--------------------------------+
                      |   Is the answer grounded in    |
                      |      retrieved context?        |
                      +----------------+---------------+
                                       |
                       +---------------+---------------+
                       |                               |
                     [No]                            [Yes]
                       |                               |
                       v                               v
             +-------------------+          +---------------------+
             | Re-route to:      |          | Is the answer       |
             | "generate"        |          | actually useful?    |
             +-------------------+          +----------+----------+
                                                       |
                                        +--------------+--------------+
                                        |                             |
                                      [No]                          [Yes]
                                        |                             |
                                        v                             v
                              +-------------------+         +------------------+
                              | Re-route to:      |         | Re-route to:     |
                              | "transform_query" |         | "__end__"        |
                              +-------------------+         +------------------+
# self_correction.py
from typing import Literal
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from state import AgentState

class GradeHallucinations(BaseModel):
    """Binary score for hallucination present in generation."""
    binary_score: Literal["yes", "no"] = Field(
        ...,
        description="Answer is grounded in the facts, 'yes' or 'no'",
    )
class GradeAnswer(BaseModel):
    """Binary score to assess if answer addresses question."""
    binary_score: Literal["yes", "no"] = Field(
        ...,
        description="Answer addresses the question, 'yes' or 'no'",
    )
def grade_generation_v_documents_and_question(state: AgentState) -> str:
    """
    Grades the generation for hallucinations and utility.

    Args:
        state (AgentState): The current graph state.

    Returns:
        str: Next step path ("supported" -> check utility, "not supported" -> regenerate, "not useful" -> rewrite query)
    """
    print("--- CHECKING FOR HALLUCINATIONS ---")
    question = state["question"]
    documents = state["documents"]
    generation = state["generation"]

    # Initialize evaluator LLM
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

    # ----------------------------------------
    # STEP 1: Groundedness (Hallucination) Check
    # ----------------------------------------
    hallucination_grader = llm.with_structured_output(GradeHallucinations)

    hallucination_system_prompt = """You are an objective grader assessing whether an LLM generation is grounded in / supported by a set of retrieved facts.
    Give a binary score of 'yes' if the generation is strictly grounded in and supported by the provided facts. Otherwise, give 'no'."""

    hallucination_prompt = ChatPromptTemplate.from_messages([
        ("system", hallucination_system_prompt),
        ("human", "Set of facts: \n\n {documents} \n\n LLM generation: {generation}"),
    ])

    # Format document contents into a single string context
    doc_context = "\n\n".join([doc.page_content for doc in documents])
    hallucination_chain = hallucination_prompt | hallucination_grader
    res = hallucination_chain.invoke({"documents": doc_context, "generation": generation})

    if res.binary_score == "no":
        print("--- DECISION: HALLUCINATION DETECTED! RE-TRYING GENERATION ---")
        return "generate"

    print("--- DECISION: GENERATION IS GROUNDED IN DOCUMENTS ---")

    # ----------------------------------------
    # STEP 2: Utility (Answer Relevance) Check
    # ----------------------------------------
    answer_grader = llm.with_structured_output(GradeAnswer)

    answer_system_prompt = """You are an objective grader assessing whether an LLM generation addresses and resolves the user's question.
    Give a binary score of 'yes' if the answer directly addresses and completely answers the user's question. Otherwise, give 'no'."""

    answer_prompt = ChatPromptTemplate.from_messages([
        ("system", answer_system_prompt),
        ("human", "User question: \n\n {question} \n\n LLM generation: {generation}"),
    ])

    answer_chain = answer_prompt | answer_grader
    res_answer = answer_chain.invoke({"question": question, "generation": generation})

    if res_answer.binary_score == "yes":
        print("--- DECISION: ANSWER IS USEFUL AND ACCURATE. ENDING GRAPH ---")
        return "useful"

    print("--- DECISION: ANSWER DOES NOT FULLY ADDRESS QUESTION. REWRITING QUERY ---")
    return "not useful"

Складання та компіляція StateGraph

Під час роботи над складанням та компіляцією StateGraph спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Запишіть час виконання та витрати на токени або запити поруч із функціональними результатами. Відображення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-середовища до спільних. Вимірюйте рівень точності відповідей на фіксований набір запитань перед налаштуванням підказок. Часта зміна підказок рідко виправляє проблеми з пошуком інформації. Під час роботи над складанням та компіляцією StateGraph спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Одночасно задокументуйте оптимальний та відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

# graph_builder.py
from langgraph.graph import END, StateGraph
from state import AgentState

# Import node functions from Part 1
from nodes import retrieve, generate, transform_query, web_search, grade_documents
# Import conditional routing edges
from router import route_question
from context_gate import decide_to_generate
from self_correction import grade_generation_v_documents_and_question
# Initialize our state-based graph
workflow = StateGraph(AgentState)
# ----------------------------------------
# 1. Register All Computational Nodes
# ----------------------------------------
workflow.add_node("retrieve", retrieve)
workflow.add_node("grade_documents", grade_documents)
workflow.add_node("generate", generate)
workflow.add_node("transform_query", transform_query)
workflow.add_node("web_search", web_search)
# ----------------------------------------
# 2. Configure Node Routing & Transitions
# ----------------------------------------
# The entry point of our execution is dynamic, decided by the routing edge
workflow.set_conditional_entry_point(
    route_question,
    {
        "web_search": "web_search",
        "retrieve": "retrieve",
    },
)
# After retrieval, we must immediately grade our retrieved documents
workflow.add_edge("retrieve", "grade_documents")
# Once documents are graded, the Context Gate decides where to go
workflow.add_conditional_edges(
    "grade_documents",
    decide_to_generate,
    {
        "transform_query": "transform_query",
        "generate": "generate",
    },
)
# If query transformation occurs, we execute a web search to gather new data
workflow.add_edge("transform_query", "web_search")
# After web searching, we feed those newly acquired documents back to the grade step
workflow.add_edge("web_search", "grade_documents")
# Once generation occurs, our Self-Correction edge decides the final quality check
workflow.add_conditional_edges(
    "generate",
    grade_generation_v_documents_and_question,
    {
        "generate": "generate",          # Loop back to fix hallucination
        "not useful": "transform_query", # Loop back to rewrite query and search
        "useful": END,                   # Exit cleanly
    },
)
# ----------------------------------------
# 3. Compile and Run the Graph
# ----------------------------------------
app = workflow.compile()
# visualize.py
from graph_builder import app

try:
    # Print the plain text visualization of the state machine architecture
    app.get_graph().print_ascii()
except Exception as e:
    print(f"ASCII generation failed: {e}")
               +----------------+
               |  __start__     |
               +-------+--------+
                       |
             +---------+---------+
             v                   v
      +------------+      +------------+
      | web_search |      |  retrieve  |
      +-----+------+      +-----+------+
            |                   |
            |                   v
            |             +-----+------+
            |             | grade_docs |
            |             +-----+------+
            |                   |
            +---------+---------+
                      |
                      v
            +---------+---------+
            |  decide_to_gen    |
            +----+---------+----+
                 |         |
      +----------+         +----------+
      v                               v
+-----+------+                  +-----+------+
| transform_q|                  |  generate  |
+-----+------+                  +-----+------+
      |                               |
      |                               v
      |                     +---------+---------+
      |                     |  self_correct     |
      |                     +----+----+----+----+
      |                          |    |    |
      +--------------------------+    |    +---> [__end__]
                                      v
                                (regenerate)

Інженерний синтез

Інженерний синтез найкраще функціонує, якщо його розглядати як вимірювану поверхню. Зафіксуйте один ідеальний приклад роботи, один випадок збою та примітки щодо скасування змін перед розширенням обсягу роботи. Віддавайте перевагу невеликим, тестованим одиницям перед складними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність дій. Розділяйте політику часткового оброблення даних та політику їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

Практичне застосування

«Практичне застосування» функціонує найкраще, коли його розглядають як вимірювану поверхню. Запишіть один ідеальний зразок результату, один випадок невдачі та примітки щодо скасування змін перед розширенням обсягу роботи. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового виконання завдань. Розділіть політику часткового оброблення даних від політики їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

Розширені боти для підтримки клієнтів

Боти для розширеної підтримки клієнтів працюють найкраще, коли їх розглядають як вимірювану систему. Збережіть один ідеальний запис розмови, один випадок збою та примітку про скасування змін перед розширенням їх функціоналу. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільних середовищ. Розділяйте політику часткового оброблення даних від політики їх пошуку. Зміна однієї з них не повинна змушувати переписувати іншу, коли змінюються показники якості. Боти для розширеної підтримки клієнтів працюють найкраще, коли їх розглядають як вимірювану систему. Збережіть один ідеальний запис розмови, один випадок збою та примітку про скасування змін перед розширенням їх функціоналу. Одночасно задокументуйте оптимальний та відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

Автоматизовані системи перевірки фактів

Для автоматизованих систем перевірки фактів необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість знову виконати крок, починаючи з відомої точки контролю, без необхідності здогадуватися про прихований стан. Краще використовувати невеликі, тестирувані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру обробки даних. Наводьте уривки тексту, які фактично лягли в основу відповіді. Без посилань оператори не зможуть відрізнити галюцинацію від проблем із індексуванням.

Динамічні двигуни рекомендацій

Для динамічних двигунів рекомендацій необхідно визначити вхідні дані, відповідального за кожен етап та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити етап з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте беззвучного часткового завершення роботи. Наводьте конкретні уривки тексту, які лягли в основу відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем із індексуванням.

Асистенти з наукових досліджень

Для асистентів з наукових досліджень необхідно визначити вхідні дані, відповідальну особу за кожен етап та критерії завершення перед зміною коду. Оператори повинні мати можливість знову виконати етап з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на обробку даних поруч із функціональними результатами. Чітке відображення витрат заздалегідь запобігає несподіваним рахункам під час переходу з демо-середовища у спільні. Цитуйте ті частини тексту, які фактично лягли в основу відповіді. Без цитат оператори не зможуть відрізнити галюцинації від проблем з індексуванням. Для асистентів з наукових досліджень необхідно визначити вхідні дані, відповідальну особу за кожен етап та критерії завершення перед зміною коду. Оператори повинні мати можливість знову виконати етап з відомої точки контролю, не намагаючись вгадати прихований стан. Документуйте як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

Коли варто використовувати той чи інший підхід?

Під час роботи над питанням «Коли варто використовувати той чи інший підхід?» спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Якщо якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну послідовність операцій. Перед налаштуванням запитів вимірюйте рівень відтворення інформації на фіксованому наборі запитань. Часта зміна формулювань запитів рідко допомагає покращити якість пошуку.

Механізми контролю у продакшені: витрати, затримки та можливості спостереження

Під час роботи над книгою «Production Guardrails: Costs, Latency, and Observability» спочатку складіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допоможе зберегти чесність пізніших змін у коді. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте беззвучного часткового виконання завдань. Вимірюйте рівень відтворення інформації на фіксованому наборі запитань перед налаштуванням промптів. Часта зміна промптів рідко вирішує проблеми слабкої системи пошуку інформації.

Балансування витрат та затримки за допомогою асиметричного маршрутизування

Під час роботи над збалансуванням витрат та затримки за допомогою асиметричного маршрутизування спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової несправності. Цей перелік допомагає зберігати чесність пізніших змін у коді. Запишіть час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли шлях передачі даних змінюється від демо-середовища до спільних. Вимірюйте ефективність пошуку на фіксованому наборі запитань перед налаштуванням формулювань запитів. Часта зміна формулювань рідко допомагає покращити якість пошуку. Під час роботи над збалансуванням витрат та затримки за допомогою асиметричного маршрутизування спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової несправності. Цей перелік допомагає зберігати чесність пізніших змін у коді. Одночасно задокументуйте оптимальний та резервний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

Вимикачі аварійного зупинення у стані LangGraph

Вимикачі аварійного зупинення у стані LangGraph працюють найкраще, коли їх розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування дій перед розширенням обсягу роботи. Віддавайте перевагу невеликим, тестованим одиницям перед складними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність дій. Розділяйте політику часткової обробки даних та політику їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

from typing import Dict, TypedDict, Literal

# Define graph state with a loop counter to act as a circuit breaker
class GraphState(TypedDict):
    question: str
    generation: str
    documents: list[str]
    loop_step: int  # Tracks iterations to prevent runaway execution costs
MAX_RETRIES = 3
def grade_generation_route(state: GraphState) -> Literal["web_search", "finalize", "max_retries_exceeded"]:
    """
    Evaluates the quality of generation and routes the agent accordingly.
    Why: Acts as a deterministic circuit breaker when LLMs get stuck in correction loops.
    """
    current_step = state.get("loop_step", 0)

    # 1. Evaluate circuit breaker before making any expensive LLM calls
    if current_step >= MAX_RETRIES:
        return "max_retries_exceeded"

    # 2. Simulated grader check (In production, this queries your grading model)
    is_accurate = False  # Assume we failed the grade in this step

    if is_accurate:
        return "finalize"
    else:
        # Route back for retrieval/generation rewrite
        return "web_search"

Структуровані запити для детерміністичної оцінки

Структуровані запити для детерміністичної оцінки найкраще працюють, якщо їх розглядати як вимірювану поверхню. Зафіксуйте один ідеальний приклад виконання, один випадок невдачі та примітку про скасування змін перед розширенням обсягу завдань. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового виконання завдань. Встановіть ліміт токенів на кожен хід та на кожну сесію. Інструменти типу агентів активно розширюють контекст; жорсткі обмеження запобігають тому, щоб демонстрації перетворювалися на несподівані рахунки.

from langchain_core.pydantic_v1 import BaseModel, Field

class GradeDocument(BaseModel):
    """Binary score for document relevance assessment."""

    binary_score: str = Field(
        description="Relevance score for the document. Must be 'yes' or 'no'."
    )
    explanation: str = Field(
        description="A brief, single-sentence justification for the given score."
    )

Відстеження виконання графа за допомогою двигунів спостережуваності

Відстеження виконання графа з використанням двигунів спостережуваності найкраще функціонує, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-середовища до спільних. Розділіть політику часткового оброблення даних від політики їх отримання. Зміна однієї не повинна змушувати переписувати іншу, коли змінюються показники якості. Відстеження виконання графа з використанням двигунів спостережуваності найкраще функціонує, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу. Документуйте як успішний, так і відновлювальний шляхи виконання. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.

[User Query] ──> [retrieve] ──> [grade_documents] (Score: No)
                                      │
                                      ▼
[finalize] <── [generate] <── [web_search] (Updated State: Loop Count = 1)

Що розкриває робочий процес, керований станом

Щоб зрозуміти, що розкриває робочий процес, керований станом, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись здогадатися про прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру процесу. Наводьте ті уривки, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинацію від проблем з індексуванням.

Чек-лист для експлуатації

Чек-лист для експлуатації найефективніше працює, якщо його розглядати як вимірювану структуру. Зберігайте один ідеальний запис, один випадок невдачі та примітки щодо скасування змін перед розширенням обсягу роботи.

Тримайте конфігурацію окремо від коду програми. Файли середовища, сховища секретних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь кодовий граф.

Розділіть політику чанкінгу від політики отримання даних. Зміна однієї не повинна змушувати переписувати іншу при зміні показників якості.

Встановіть людське схвалення для операцій, які призводять до витрат грошей чи змінюють продуктивні дані. Підключення під час компіляції не є гарантією повності бізнес-процесу.

Напишіть короткий посібник: як обмінювати ключі, як спорожнювати чергу, як скасовувати останнє введення даних.

Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового завершення роботи.

Перед підвищенням версії стеку заморозьте його версії, збережіть ідеальний запис для критичного шляху та підтвердьте кроки скасування змін. У спільних середовищах необхідні обмеження швидкості, перевірки прав власності та чіткий власник для обміну секретами. Віддавайте перевагу надійності перед креативними одноразовими демонстраціями.

Примітка до пакету eeb69aedbcbc: не включайте ключі постачальників у репозиторій, встановіть ліміт токенів на сеанс та зберігайте транскрипції поруч із фіксами для оцінки, щоб подальша заміна моделей залишалася порівнянною.