Главная / Статьи

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

Пошаговое руководство по практическим рекомендациям: создание цикла код-ревью с участием двух агентов с использованием A2A, LangGraph, а также контрактов, проверок и готовых блоков кода для команд, применяющих эту схему.

Издатель: REACTAPP.TOP17 мин чтения3798 слов

Источники: Building a Two-Agent Code Review Loop with A2A, LangGraph, and LM Studio

В следующих заметках описывается практический подход к созданию цикла код-ревью с участием двух агентов с использованием A2A, LangGraph и LM Studio. Основное внимание уделяется контрактам, проверкам и шаблонам кода, которые можно легко вставить, а не мотивирующим аспектам. При изучении обзора сначала запишите контракт: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь граф.

Что такое A2A на самом деле (один абзац)

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

Компоненты

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

a2a-coder-reviewer/
├── shared/
│   ├── config.py           # tunables: system prompts, model, temperature, MAX_ROUNDS
│   ├── llm.py              # LM Studio ChatOpenAI factory
│   ├── coder_graph.py      # LangGraph graph: spec (+ feedback) -> code
│   └── reviewer_graph.py   # LangGraph graph: spec + code -> verdict + feedback
├── coder_agent/
│   ├── agent_executor.py   # A2A executor wrapping the coder graph
│   └── __main__.py         # A2A server, port 9001
├── reviewer_agent/
│   ├── agent_executor.py   # A2A executor wrapping the reviewer graph
│   └── __main__.py         # A2A server, port 9002
├── orchestrator.py         # A2A client driving the back-and-forth
└── requirements.txt

Файл конфигурации

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

from dataclasses import dataclass

@dataclass(frozen=True)
class AgentConfig:
    model: str
    temperature: float
    system_prompt: str


CODER_CONFIG = AgentConfig(
    model="local-model",
    temperature=0.2,
    system_prompt="""You are a careful Python engineer.
Write a single Python function that satisfies the spec.
If reviewer feedback is provided, revise your previous code to address it.
Return ONLY the code in a single ```python fenced block, no extra commentary.
""",
)

REVIEWER_CONFIG = AgentConfig(
    model="local-model",
    temperature=0.0,
    system_prompt="""You are a strict senior Python code reviewer performing a
rigorous code review. Do not approve code that has any of these problems:
- Missing a docstring explaining parameters, return value, and behavior.
- Missing type hints on parameters and the return type.
- Failing to handle edge cases implied by the spec (empty input, None,
  zero, negative numbers, single-element collections, etc.) unless the
  spec explicitly says not to.
- Silently swallowing errors or returning a sentinel value (None, "", -1,
  an error string) instead of raising, unless the spec asks for that.
- Using a data structure that visibly discards information the spec
  requires (e.g. a `set` when order or duplicate counts must be kept).

If you see ANY of the above, or anything else that would fail a real code
review, you MUST respond with VERDICT: CHANGES_REQUESTED and list the
concrete problems as bullet points, even if the code "basically works."
Only respond with VERDICT: APPROVED if the code has none of these
problems. Never approve code and then list problems as "suggestions" or
"nice to haves" — if it's worth mentioning, it's worth requesting changes
for.

Start your reply with exactly one line:
VERDICT: APPROVED
or
VERDICT: CHANGES_REQUESTED
Then, on the following lines, give concise, actionable feedback
(bullet points). If APPROVED, feedback can just be brief praise/notes.
""",
)

# Max coder<->reviewer rounds before orchestrator.py gives up.
MAX_ROUNDS = 3

Завод по производству LLM

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

import os
from langchain_openai import ChatOpenAI

LM_STUDIO_BASE_URL = os.environ.get(
    "LM_STUDIO_BASE_URL", "http://localhost:7777/v1"
)

def get_llm(model: str = "local-model", temperature: float = 0.2) -> ChatOpenAI:
    return ChatOpenAI(
        base_url=LM_STUDIO_BASE_URL,
        api_key="lm-studio",       # placeholder, LM Studio ignores it
        model=model,
        temperature=temperature,
    )

Граф кодера

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

from typing import TypedDict, Optional
from langgraph.graph import StateGraph, END
from shared.config import CODER_CONFIG
from shared.llm import get_llm

class CoderState(TypedDict):
    spec: str
    previous_code: Optional[str]
    feedback: Optional[str]
    code: str


def _generate(state: CoderState) -> CoderState:
    llm = get_llm(model=CODER_CONFIG.model, temperature=CODER_CONFIG.temperature)
    user_msg = f"Spec:\n{state['spec']}\n"
    if state.get("previous_code"):
        user_msg += f"\nPrevious code:\n{state['previous_code']}\n"
    if state.get("feedback"):
        user_msg += f"\nReviewer feedback to address:\n{state['feedback']}\n"

    response = llm.invoke(
        [
            {"role": "system", "content": CODER_CONFIG.system_prompt},
            {"role": "user", "content": user_msg},
        ]
    )
    state["code"] = response.content
    return state


def build_coder_graph():
    graph = StateGraph(CoderState)
    graph.add_node("generate", _generate)
    graph.set_entry_point("generate")
    graph.add_edge("generate", END)
    return graph.compile()


coder_graph = build_coder_graph()
import re
from typing import TypedDict, Optional
from langgraph.graph import StateGraph, END
from shared.config import REVIEWER_CONFIG
from shared.llm import get_llm


class ReviewerState(TypedDict):
    spec: str
    code: str
    verdict: str
    feedback: str


def _review(state: ReviewerState) -> ReviewerState:
    llm = get_llm(model=REVIEWER_CONFIG.model, temperature=REVIEWER_CONFIG.temperature)
    user_msg = f"Spec:\n{state['spec']}\n\nCode to review:\n{state['code']}\n"
    response = llm.invoke(
        [
            {"role": "system", "content": REVIEWER_CONFIG.system_prompt},
            {"role": "user", "content": user_msg},
        ]
    )
    text = response.content
    match = re.search(r"VERDICT:\s*(APPROVED|CHANGES_REQUESTED)", text, re.IGNORECASE)
    verdict = match.group(1).upper() if match else "CHANGES_REQUESTED"
    feedback = text[match.end():].strip() if match else text.strip()

    state["verdict"] = verdict
    state["feedback"] = feedback
    return state


def build_reviewer_graph():
    graph = StateGraph(ReviewerState)
    graph.add_node("review", _review)
    graph.set_entry_point("review")
    graph.add_edge("review", END)
    return graph.compile()


reviewer_graph = build_reviewer_graph()

Преобразование графа в исполнитель A2A

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

aph.

from a2a.server.agent_execution.agent_executor import AgentExecutor
from a2a.server.agent_execution.context import RequestContext
from a2a.server.events.event_queue import EventQueue
from a2a.server.tasks.task_updater import TaskUpdater
from a2a.types import Part, Task, TaskState, TaskStatus

from shared.coder_graph import coder_graph

class CoderAgentExecutor(AgentExecutor):
    async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
        user_message = context.message
        task_id = context.task_id
        context_id = context.context_id

        await event_queue.enqueue_event(
            Task(
                id=task_id,
                context_id=context_id,
                status=TaskStatus(state=TaskState.TASK_STATE_SUBMITTED),
                history=[user_message] if user_message else [],
            )
        )
        updater = TaskUpdater(
            event_queue=event_queue, task_id=task_id, context_id=context_id
        )
        working_message = updater.new_agent_message(
            parts=[Part(text="Generating code...")]
        )
        await updater.start_work(message=working_message)

        query = context.get_user_input()
        spec, previous_code, feedback = _parse_input(query)

        try:
            result = coder_graph.invoke(
                {
                    "spec": spec,
                    "previous_code": previous_code,
                    "feedback": feedback,
                    "code": "",
                }
            )
        except Exception as exc:
            error_message = updater.new_agent_message(
                parts=[Part(text=f"❌ Code generation failed: {exc}")]
            )
            await updater.failed(message=error_message)
            return

        await updater.add_artifact(
            parts=[Part(text=result["code"])], name="code", last_chunk=True
        )
        await updater.complete()

...

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

import uvicorn

from starlette.applications import Starlette
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes
from a2a.server.tasks.inmemory_task_store import InMemoryTaskStore
from a2a.types import AgentCapabilities, AgentCard, AgentInterface, AgentSkill

from coder_agent.agent_executor import CoderAgentExecutor

HOST = "127.0.0.1"
PORT = 9001
RPC_URL = "/a2a/jsonrpc"

def build_agent_card() -> AgentCard:
    skill = AgentSkill(
        id="write_code",
        name="Write / revise Python code",
        description="Writes a Python function from a spec, or revises "
                    "previous code given reviewer feedback.",
        tags=["coding", "python"],
        examples=["SPEC: write a function that reverses a linked list"],
        input_modes=["text"],
        output_modes=["text", "task-status"],
    )
    return AgentCard(
        name="Coder Agent",
        description="Generates and revises Python code based on a spec "
                    "and optional reviewer feedback.",
        version="0.1.0",
        capabilities=AgentCapabilities(streaming=True, push_notifications=False),
        default_input_modes=["text"],
        default_output_modes=["text", "task-status"],
        skills=[skill],
        supported_interfaces=[
            AgentInterface(
                protocol_binding="JSONRPC",
                protocol_version="1.0",
                url=f"http://{HOST}:{PORT}{RPC_URL}",
            ),
        ],
    )

def main():
    agent_card = build_agent_card()
    request_handler = DefaultRequestHandler(
        agent_executor=CoderAgentExecutor(),
        task_store=InMemoryTaskStore(),
        agent_card=agent_card,
    )

    routes = []
    routes.extend(create_agent_card_routes(agent_card=agent_card))
    routes.extend(create_jsonrpc_routes(request_handler=request_handler, rpc_url=RPC_URL))

    app = Starlette(routes=routes)
    uvicorn.run(app, host=HOST, port=PORT)


if __name__ == "__main__":
    main()

При работе над The Orchestrator сначала запишите контракт: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг терпит неудачу, ошибка должна указывать на конкретную ответственность, а не на запутанную цепочку операций. Вносите контрольные точки после дорогостоящих шагов. Механизм возобновления работы не должен повторно оплачивать один и тот же вызов LLM, когда оператор пытается выполнить следующий узел.

import asyncio
import sys
import uuid

import httpx
from a2a.client import A2ACardResolver, ClientConfig, create_client
from a2a.helpers import get_artifact_text, get_message_text
from a2a.types import Message, Part, Role, SendMessageRequest
from shared.config import MAX_ROUNDS

CODER_URL = "http://127.0.0.1:9001"
REVIEWER_URL = "http://127.0.0.1:9002"


async def get_client_for(httpx_client: httpx.AsyncClient, base_url: str):
    resolver = A2ACardResolver(httpx_client, base_url)
    card = await resolver.get_agent_card()
    return await create_client(card, client_config=ClientConfig(httpx_client=httpx_client))

async def send_and_collect(client, text: str) -> str:
    message = Message(
        role=Role.ROLE_USER,
        message_id=str(uuid.uuid4()),
        parts=[Part(text=text)],
        context_id=str(uuid.uuid4()),
    )
    request = SendMessageRequest(message=message)

    result_text = ""
    async for event in client.send_message(request):
        if event.HasField("message"):
            result_text = get_message_text(event.message, delimiter="\n")
        elif event.HasField("artifact_update"):
            result_text += get_artifact_text(
                event.artifact_update.artifact, delimiter="\n"
            )
        elif event.HasField("status_update"):
            status = event.status_update.status
            if status.state == TaskState.TASK_STATE_FAILED:
                error_text = (
                    get_message_text(status.message, delimiter="\n")
                    if status.HasField("message")
                    else "Task failed with no error message."
                )
                raise RuntimeError(error_text)
    return result_text
async def run(spec: str) -> dict:
    """
    Runs the coder<->reviewer loop for `spec`. Returns a result dict:
        {"approved": bool, "rounds": int, "code": str, "review": str}
    or, if a task failed, {"approved": False, "error": str, "code": str | None}.
    """
    async with httpx.AsyncClient(timeout=120) as httpx_client:
        coder = await get_client_for(httpx_client, CODER_URL)
        reviewer = await get_client_for(httpx_client, REVIEWER_URL)

        code = None
        feedback = None
        review_text = ""

        try:
            for round_num in range(1, MAX_ROUNDS + 1):
                print(f"\n=== Round {round_num}: Coder ===")
                coder_prompt = f"SPEC: {spec}\n"
                if code:
                    coder_prompt += f"PREVIOUS_CODE: {code}\nFEEDBACK: {feedback}\n"
                code = await send_and_collect(coder, coder_prompt)
                print(code)

                print(f"\n=== Round {round_num}: Reviewer ===")
                reviewer_prompt = f"SPEC: {spec}\nCODE: {code}"
                review_text = await send_and_collect(reviewer, reviewer_prompt)
                print(review_text)

                if review_text.startswith("VERDICT: APPROVED"):
                    print("\n✅ Approved. Final code:\n")
                    print(code)
                    return {
                        "approved": True,
                        "rounds": round_num,
                        "code": code,
                        "review": review_text,
                    }

                feedback = review_text.split("\n", 1)[-1]

            print(f"\n⚠️ Max rounds ({MAX_ROUNDS}) reached without approval. Last code:\n")
            print(code)
            return {
                "approved": False,
                "rounds": MAX_ROUNDS,
                "code": code,
                "review": review_text,
            }
        except RuntimeError as exc:
            print(f"\n{exc}")
            return {"approved": False, "error": str(exc), "code": code}
        finally:
            await coder.close()
            await reviewer.close()

Запуск

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

pip install -r requirements.txt

# terminal 1
python -m coder_agent

# terminal 2
python -m reviewer_agent

# terminal 3
python orchestrator.py "write a function that checks if a string is a palindrome"
curl http://127.0.0.1:9001/.well-known/agent-card.json

Пример реального запуска

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

Что сломалось и как это исправить

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

ModuleNotFoundError: No module named 'a2a.server.apps'

ModuleNotFoundError: No module named 'a2a.server.apps' лучше всего рассматривать как измеримую поверхность. Соберите один эталонный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия создаваемым элементам, определите критерии успеха и не допускайте молчаливого частичного выполнения задач. Сохраняйте структуру графа простой и типизированной. Вложенные структуры скрывают информацию о том, какой узел заполнил тот или иной поле, что приводит к нарушению возобновления работы после перерывов. ModuleNotFoundError: No module named 'a2a.server.apps' лучше всего рассматривать как измеримую поверхность. Соберите один эталонный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь граф.

ModuleNotFoundError: No module named 'sse_starlette'

При ошибке ModuleNotFoundError: No module named 'sse_starlette' необходимо сначала определить входные данные, ответственного за выполнение шага и критерии завершения, прежде чем изменять код. Операторы должны иметь возможность перезапустить шаг, исходя из известной точки контроля, без необходимости угадывать скрытое состояние. Необходимо одновременно задокументировать успешный и аварийный сценарии работы. Повторные попытки, проверка человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Внедрять утверждение человеком для тех операций, которые влекут за собой расходы или изменение производственных данных. Наличие связей во время компиляции не гарантирует полноты функционала продукта.

Таинственные таймауты примерно на 5 секунд

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

Модели обоснования мешают эффективности

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

Имена enum в Protobuf

При работе с именами enum в Protobuf сначала запишите условия работы: необходимые параметры входных данных, сигнал о успешном выполнении и последствия частичной неудачи. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Документируйте как успешный, так и восстановительный пути работы. Повторные попытки, проверки со стороны человека и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Создавайте контрольные точки после затратных операций. Механизм возобновления работы не должен повторно взимать плату за один и тот же вызов LLM при повторной попытке оператора обработки последующего этапа.

Part больше не оборачивает TextPart

Когда вы работаете над тем, чтобы Part больше не оборачивал TextPart, сначала запишите спецификацию: необходимые входные данные, сигнал о успехе и что происходит при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг терпит неудачу, ошибка должна указывать на конкретную ответственность, а не на запутанную цепочку операций. Вносите контрольные точки после дорогостоящих шагов. Механизм возобновления работы не должен повторно оплачивать один и тот же вызов LLM, когда оператор пытается выполнить более поздний узел.

Возможности для расширения

При расширении с помощью Room сначала запишите контракт: необходимые входные данные, сигнал о успехе и что происходит при частичной неудаче. Такой список помогает сохранять честность последующих изменений в коде. Рассматривайте этот этап как контракт между входными данными и проверенными выходными данными. Дайте названия элементам, определите критерии успеха и не допускайте безупречного завершения работы только частично. Вводите контрольные точки после дорогостоящих операций. Механизм возобновления работы не должен повторно запрашивать один и тот же вызов LLM, когда оператор пытается выполнить следующий элемент. При расширении с помощью Room сначала запишите контракт: необходимые входные данные, сигнал о успехе и что происходит при частичной неудаче. Такой список помогает сохранять честность последующих изменений в коде. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь граф.

Ссылки

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

Чек-лист операционной работы

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

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

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

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

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

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

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

При работе над примечанием по усилению безопасности №0 сначала запишите условия работы: необходимые входные данные, сигнал успешного выполнения и последствия частичной неудачи. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Храните конфигурацию вне кода приложения — файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, которое операторы могут проверять, не читая весь код.

Деталь усиления безопасности 0/640: измеряйте время выполнения, класс ошибки и расход токенов для этого примечания, затем решайте, следует ли сохранять изменение, опираясь на фиксированный набор критериев, а не на устные оценки.

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

Деталь укрепления 1/640: измерьте время выполнения, класс ошибки и расход токенов для этого примечания, затем решите, следует ли сохранять изменения, опираясь на заранее определенный набор критериев, а не на устные описания.

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

Подробности усиления безопасности 2/640: измерьте время работы стены, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранять изменения на основе фиксированного набора вопросов, а не на основе единичных примеров.