Главная / Статьи / Практические заметки: RAG без угадывания: стандартизированный LangGraph +

Практические заметки: RAG без угадывания: стандартизированный LangGraph +

Пошаговое руководство по Practical notes: RAG без угадывания — стандартизированный LangGraph + с контрактами, проверками и готовыми блоками кода для команд, использующих эту схему.

3577 слов

В этом руководстве показано, как построить цепочку от сырья до рабочей системы для решения задачи RAG без неопределенности: стандартизированная схема LangGraph + LlamaIndex. Основное внимание уделяется практическим шагам, четкой проверке результатов и коду, который можно просто добавить в репозиторий без необходимости угадывать намерения автора. Для получения общего представления сначала определите входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не пытаясь угадать скрытое состояние системы. Документируйте как успешный ход выполнения задачи, так и способы восстановления при сбоях. Повторные попытки, проверка человеком и обработка неработоспособных сообщений являются неотъемлемой частью продукта, а не дополнительными улучшениями.

Почему существует эта статья

При работе над разделом «Почему существует этая статья» сначала запишите условия работы: необходимые входные данные, сигнал успешного выполнения и что происходит при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые модули большим скриптам. Когда какой-то шаг терпит неудачу, ошибка должна указывать на конкретную ответственность, а не на запутанную цепочку операций. Оцените уровень воспроизведения ответов на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.

Часть А: Понимание LlamaIndex (сначала концепция)

При работе над частью A: Понимание LlamaIndex (сначала концепция), сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и что происходит при частичной неудаче. Такой список поможет избежать ошибок при последующих изменениях кода. Рассматривайте этот этап как договор между входными данными и проверенными результатами. Дайте названия элементам, определите критерии успеха и не допускайте молчаливого частичного выполнения задачи. Оцените уровень воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.

Что на самом деле делает LlamaIndex

При изучении материала «Что на самом деле делает LlamaIndex» сначала запишите описание требований: необходимые входные данные, сигнал успешного выполнения и последствия частичной неудачи. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отображение затрат с самого начала предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Измеряйте показатель воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска. При изучении материала «Что на самом деле делает LlamaIndex» сначала запишите описание требований: необходимые входные данные, сигнал успешного выполнения и последствия частичной неудачи. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Документируйте как успешный, так и восстановительный пути работы системы. Повторные попытки, проверка человеком и обработка неработающих запросов являются частью продукта, а не элементами последующей доработки.

Пятиэтапная схема RAG

Пятиэтапная система RAG работает наилучшим образом, если рассматривать её как измеримую структуру. Соберите один идеальный пример обработки данных, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое какого-либо шага причина должна быть связана с конкретной функцией, а не с запутанной структурой всей системы. Разделяйте правила формирования фрагментов данных и правила их поиска. Изменение одних не должно вынуждать переписывать другие при изменении показателей качества.

1. LOAD       → Read raw files (PDF, Word, web pages, databases) into Documents
2. CHUNK      → Split Documents into small, retrievable Nodes
3. EMBED      → Convert each Node's text into a vector (a list of numbers
                representing meaning)
4. STORE      → Save those vectors in a Vector Index for fast lookup
5. RETRIEVE   → At query time, embed the user's question, find the most
                  similar Nodes, and return them as context

Основные ключевые слова, которые вам необходимо знать

Книга «Основные ключевые слова, которые вам необходимо знать» наиболее эффективна, когда с ней обращаются как с измеримой основой. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работы. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия создаваемым элементам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задачи. Разделяйте политику разбиения данных на части и политику их извлечения. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества.

Часть B: Создание автономной базы знаний LlamaIndex

Часть B: Создание автономной базы знаний LlamaIndex работает наилучшим образом, когда рассматривается как измеримая структура. Соберите один идеальный пример транскрипции, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работы. Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счёты при переходе от демо-режима к общедоступным средам. Разделяйте политику разбиения данных на части и политику поиска. Изменение одной из них не должно принуждать к переписыванию другой при изменении показателей качества. Часть B: Создание автономной базы знаний LlamaIndex работает наилучшим образом, когда рассматривается как измеримая структура. Соберите один идеальный пример транскрипции, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работы. Документируйте как успешный, так и восстановительный пути работы. Повторные попытки, проверка человеком и обработка неработающих сообщений являются частью продукта, а не этапом последующей доработки.

Шаг 1: Установка

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

# Core package + OpenAI LLM and embedding integrations (the common starting setup)
pip install llama-index-core llama-index-llms-openai llama-index-embeddings-openai

# Readers for common file types (PDF, Word, etc.)
pip install llama-index-readers-file pypdf

Шаг 2: глобальная настройка с использованием Settings

Для второго шага: глобальная настройка с использованием Settings — необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Укажите названия элементов, определите критерии успеха и не допускайте молчаливого частичного завершения работы. Цитируйте те фрагменты, которые фактически легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.

# ── llamaindex_config.py ─────────────────────────────────────
import os
from llama_index.core import Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding

# Settings is global - configure once, used everywhere in LlamaIndex
Settings.llm = OpenAI(
    model="gpt-4o-mini",       # Used for generating final answers from retrieved context
    temperature=0.1,            # Low temperature: factual, not creative
)
Settings.embed_model = OpenAIEmbedding(
    model="text-embedding-3-small",   # Used to convert text into vectors
)
# Controls how documents are split into Nodes (chunks)
Settings.chunk_size = 512        # Max tokens per chunk
Settings.chunk_overlap = 50      # Overlap between consecutive chunks, to preserve context across boundaries

Шаг 3: Загрузка → Индексация → Запрос (автономная цепочка обработки)

Для шага 3: Загрузка → Индексация → Запрос (автономная цепочка обработки) необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение стоимости заранее предотвращает неожиданные счета при переходе с демо-среды в общедоступные среды. Указывайте те фрагменты текста, которые на самом деле легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.

# ── build_knowledge_base.py ──────────────────────────────────
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex

# ── LOAD: Read all files in a folder into Document objects ──
documents = SimpleDirectoryReader("./data").load_data()
print(f"Loaded {len(documents)} documents")
# ── CHUNK + EMBED + STORE: all three happen inside this one call ──
# VectorStoreIndex automatically:
#   1. Splits each Document into Nodes (using Settings.chunk_size)
#   2. Embeds each Node (using Settings.embed_model)
#   3. Stores the vectors in an in-memory index
index = VectorStoreIndex.from_documents(documents, show_progress=True)
# ── RETRIEVE + GENERATE: ask a question ──────────────────────
query_engine = index.as_query_engine(
    similarity_top_k=3,   # Retrieve the 3 most relevant chunks for each query
)
response = query_engine.query("What is our refund policy for enterprise customers?")
print(response)

Для шага 3: Загрузка → Индексация → Запрос (автономная цепочка обработки) необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Необходимо одновременно задокументировать успешный сценарий выполнения и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки.

Шаг 4: Хранение индекса (не вставляйте его заново каждый раз)

При работе над шагом 4: сохранение индекса (не вставлять его заново каждый раз), сначала запишите спецификацию: необходимые входные данные, сигнал о успешном выполнении и что происходит при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые модули большим скриптам. Когда какой-то шаг терпит неудачу, причина должна указывать на конкретную ответственность, а не на запутанную цепочку операций. Оцените уровень воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.

# ── Save the index after building it ─────────────────────────
index.storage_context.persist(persist_dir="./storage")

# ── Load it back later without re-embedding anything ──────────
from llama_index.core import StorageContext, load_index_from_storage
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)

Шаг 5: использование внешней векторной базы данных (Chroma)

При работе над шагом 5: Использование внешней базы данных векторов (Chroma), сначала запишите условия соглашения: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Рассматривайте этот этап как соглашение между входными данными и проверенными выходными результатами. Укажите названия элементов, определите критерии успешности и не допускайте молчаливого частичного выполнения задачи. Измерьте степень воспроизведения на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.

# ── Using Chroma as a persistent, production-grade vector store ──
# pip install llama-index-vector-stores-chroma chromadb

import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import StorageContext, VectorStoreIndex
chroma_client = chromadb.PersistentClient(path="./chroma_db")
chroma_collection = chroma_client.get_or_create_collection("my_knowledge_base")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# Build the index directly into Chroma
index = VectorStoreIndex.from_documents(
    documents,
    storage_context=storage_context
)
# Later, in a different process, reconnect without re-indexing:
index = VectorStoreIndex.from_vector_store(vector_store=vector_store)

Часть C: Подключение LlamaIndex к LangGraph

При работе над частью C: подключение LlamaIndex к LangGraph, сначала запишите спецификацию: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат заранее предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Измеряйте показатель воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска. При работе над частью C: подключение LlamaIndex к LangGraph, сначала запишите спецификацию: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Документируйте как успешный, так и путь восстановления работы. Повторные попытки, проверка человеком и обработка неработающих сообщений являются частью продукта, а не элементами последующей доработки.

The Bridge: Использование движка запросов как инструмента

The Bridge: Использование движка запросов как инструмента работает наилучшим образом, когда его рассматривают как измеримую структуру. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг сбивается, причина сбоя должна указывать на конкретную ответственность, а не на запутанную цепочку операций. Разделяйте правила разбиения данных на части и правила их поиска. Изменение одних не должно вынуждать переписывать другие при изменении показателей качества.

# ── MODULE 3: TOOLS (LlamaIndex-backed) ─────────────────────
from langchain_core.tools import tool

# The query_engine built in Part B - created once, at startup
# (In a real app, you'd load this from persisted storage, not rebuild it every time)
@tool
def search_knowledge_base(query: str) -> str:
    """Search the internal knowledge base for company policies, product
    documentation, and internal procedures. Use this whenever the user asks
    a question that might be answered by internal company documents rather
    than general knowledge.

    Args:
        query: A natural-language question to search for.

    Returns:
        A synthesized answer based on the most relevant retrieved documents.
    """
    response = query_engine.query(query)
    return str(response)

Полная интеграция: модули с 1 по 7

Полная интеграция: модули с 1 по 7 работают наилучшим образом, если рассматривать их как измеримую структуру. Соберите один эталонный пример успешной работы, один пример сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия создаваемым элементам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задачи. Разделяйте политику разбиения на части и политику извлечения данных. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества.

# ============================================================
# LANGGRAPH + LLAMAINDEX RAG AGENT — COMPLETE TEMPLATE
# Extends: Part 1 (core structure)
# ============================================================

# ── MODULE 1: IMPORTS & CONFIGURATION ───────────────────────
import os
from typing import Literal
# LangChain / LangGraph imports (the orchestration layer)
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, BaseMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode
from langgraph.checkpoint.memory import MemorySaver
# LlamaIndex imports (the retrieval / data layer)
from llama_index.core import (1
    Settings, SimpleDirectoryReader, VectorStoreIndex,
    StorageContext, load_index_from_storage
)
from llama_index.llms.openai import OpenAI as LlamaOpenAI
from llama_index.embeddings.openai import OpenAIEmbedding
# LangGraph's chat model - used by the agent's reasoning
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# LlamaIndex's model config - used internally by the query engine
# Note: these are SEPARATE from the LangGraph llm above. Each framework
# manages its own model instances; they don't share state.
Settings.llm = LlamaOpenAI(model="gpt-4o-mini", temperature=0.1)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.chunk_size = 512

# ── MODULE 2: STATE ──────────────────────────────────────────
class State(MessagesState):
    pass  # messages field inherited; extend if your agent needs more

# ── MODULE 3: TOOLS (RAG-backed) ─────────────────────────────
# Build or load the LlamaIndex knowledge base ONCE, at startup
PERSIST_DIR = "./storage"
if os.path.exists(PERSIST_DIR):
    # Reload existing index - no re-embedding, fast startup
    storage_context = StorageContext.from_defaults(persist_dir=PERSIST_DIR)
    index = load_index_from_storage(storage_context)
else:
    # First run - build the index and persist it
    documents = SimpleDirectoryReader("./data").load_data()
    index = VectorStoreIndex.from_documents(documents, show_progress=True)
    index.storage_context.persist(persist_dir=PERSIST_DIR)
query_engine = index.as_query_engine(similarity_top_k=3)

@tool
def search_knowledge_base(query: str) -> str:
    """Search internal company documents for policies, product specs,
    procedures, and other domain-specific information. Use this for any
    question that requires knowledge specific to this organization rather
    than general world knowledge."""
    response = query_engine.query(query)
    return str(response)

tools = [search_knowledge_base]
llm_with_tools = llm.bind_tools(tools)
tool_node = ToolNode(tools)

# ── MODULE 4: NODES ──────────────────────────────────────────
def agent_node(state: State) -> dict:
    """The reasoning node. Decides whether to answer directly or
    search the knowledge base first."""
    system_prompt = SystemMessage(content=(
        "You are a helpful assistant with access to an internal knowledge base. "
        "Use the search_knowledge_base tool when the user asks about company-specific "
        "information. For general questions, answer directly."
    ))
    messages = [system_prompt] + state["messages"]
    response = llm_with_tools.invoke(messages)
    return {"messages": [response]}

# ── MODULE 5: ROUTING ────────────────────────────────────────
def should_continue(state: State) -> Literal["tools", "__end__"]:
    last_message = state["messages"][-1]
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    return "__end__"

# ── MODULE 6: GRAPH ASSEMBLY ─────────────────────────────────
graph_builder = StateGraph(State)
graph_builder.add_node("agent", agent_node)
graph_builder.add_node("tools", tool_node)
graph_builder.add_edge(START, "agent")
graph_builder.add_conditional_edges(
    "agent", should_continue,
    {"tools": "tools", "__end__": END}
)
graph_builder.add_edge("tools", "agent")
graph = graph_builder.compile(checkpointer=MemorySaver())

# ── MODULE 7: ENTRYPOINT ──────────────────────────────────────
if __name__ == "__main__":
    config = {"configurable": {"thread_id": "session-001"}}

    print("RAG agent ready. Ask about your documents, or anything else.\n")

    while True:
        user_text = input("You: ").strip()
        if not user_text or user_text.lower() == "exit":
            break

        response = graph.invoke(
            {"messages": [HumanMessage(content=user_text)]},
            config=config
        )
        print(f"Agent: {response['messages'][-1].content}\n")

Что на самом деле происходит при запуске этого инструмента

Что на самом деле происходит при запуске этого инструмента, работает лучше всего, если рассматривать его как измеримую среду. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию до расширения объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счёты при переходе от демо-среды к общедоступным средам. Разделяйте политику разбиения данных на части и политику их извлечения. Изменение одной из них не должно принуждать к переписыванию другой при изменении показателей качества. Что на самом деле происходит при запуске этого инструмента, работает лучше всего, если рассматривать его как измеримую среду. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию до расширения объёма работ. Документируйте одновременно успешный путь выполнения и путь восстановления. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не элементами последующей доработки.

User: "What's our policy on remote work?"
        ↓
[agent_node] — LangGraph's LLM reads the message, recognizes this needs
               internal info, decides to call search_knowledge_base
        ↓
[tools] — ToolNode executes search_knowledge_base("What's our policy on remote work?")
        ↓
        Inside the tool: query_engine.query(...) runs —
        this is 100% LlamaIndex, invisible to LangGraph:
          1. Embeds the query
          2. Searches the vector index for the 3 closest chunks
          3. Feeds those chunks + the question to Settings.llm
          4. Returns a synthesized answer string
        ↓
[agent_node] — LangGraph's LLM receives the tool's string result,
               and crafts the final response shown to the user
        ↓
Response to user

Часть D: На один уровень глубже — режим только для Retriever (больший контроль)

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

# ── Retriever-only tool: returns raw chunks, not a synthesized answer ──
retriever = index.as_retriever(similarity_top_k=3)
@tool
def retrieve_documents(query: str) -> str:
    """Retrieve relevant document excerpts from the internal knowledge base.
    Returns raw excerpts for you to read and reason over yourself -
    use this when you need to cite specific sources or combine information
    from multiple documents."""

    nodes = retriever.retrieve(query)

    # Format each retrieved chunk with its source for transparency
    formatted_chunks = []
    for i, node in enumerate(nodes):
        source = node.metadata.get("file_name", "unknown source")
        formatted_chunks.append(f"[Excerpt {i+1} from {source}]\n{node.text}")

    return "\n\n---\n\n".join(formatted_chunks)

Когда использовать QueryEngine вместо Retriever

Чтобы определить, когда использовать QueryEngine, а когда — Retriever, необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия результатам работы, определите критерии успеха и не допускайте молчаливого частичного завершения задачи. Укажите конкретные фрагменты текста, на которых основан ответ. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.

Обновленная карточка с ключевыми словами

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

Руководство по принятию решений: когда вам действительно это нужно?

При работе с Руководством по принятию решений: когда вам действительно это нужно?, сначала запишите условия контракта: необходимые входные данные, сигнал успешного выполнения и что происходит при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Лучше использовать небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг не срабатывает, причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций. Перед настройкой запросов измерьте уровень воспроизведения ответов на фиксированном наборе вопросов. Частая смена запросов редко помогает улучшить качество поиска.

Заключение: две платформы, одна целостность

При работе над разделом «Заключение: две платформы, один соединитель» сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Рассматривайте этот этап как договор между входными данными и проверенными результатами. Дайте названия соответствующим элементам, определите критерии успеха и не допускайте молчаливого частичного выполнения задачи. Оцените уровень воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.

Чек-лист операционной деятельности

Чек-лист работает наилучшим образом, когда его рассматривают как измеряемый показатель. Соберите один эталонный пример работы, один случай сбоя и запись о возможности отката перед расширением объема работ.

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

Разделяйте политику разбиения на части и политику извлечения данных. Изменение одной из них не должно принуждать к переписыванию другой при изменении показателей качества.

Внедряйте проверку человеком для операций, связанных с расходами или изменением производственных данных. Настройки, сделанные во время компиляции, не гарантируют полноты обработки данных в бизнес-процессах.

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

Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Укажите названия файлов, определите критерии успеха и не допускайте молчаливого частичного завершения работы.

Перед повышением уровня стека заморозьте версии, сохраните эталонный вариант данных для критических процессов и убедитесь в наличии шагов для отката. В совместных средах необходимы ограничения на частоту запросов, проверки принадлежности ресурсов и четко определенный ответственный за обновление секретов. Лучше предпочесть простую надежность сложным одноразовым демонстрациям.

Примечание к пакету bcaf14f9c811: не хранить ключи поставщиков в репозитории, установить лимит токенов на сессию и сохранять транскрипции рядом с фиксами для оценки, чтобы последующие замены моделей оставались сопоставимыми.