Accueil / Articles

Notes pratiques : Création d’un cycle de revue de code à deux agents avec A2A, LangGraph et

Guide pas à pas pratique : création d’un cycle de revue de code à deux agents utilisant A2A, LangGraph, ainsi que des contrats, des vérifications et des emplacements de code prêts à l’emploi pour les équipes qui adoptent ce modèle.

Éditeur : REACTAPP.TOP17 min de lecture3798 mots

Sources: Building a Two-Agent Code Review Loop with A2A, LangGraph, and LM Studio

Les notes suivantes reconstituent une approche pratique pour « Construire un cycle de revue de code à deux agents avec A2A, LangGraph et LM Studio ». L’accent est mis sur les contrats, les vérifications et les placeholders de code à insérer, plutôt que sur une présentation motivante. Lorsque vous travaillez sur l’aperçu général, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans avoir à lire l’ensemble du graphe.

Qu’est-ce que A2A vraiment (un paragraphe)

Ce qu’est réellement A2A (un paragraphe) fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de rollback avant d’élargir le périmètre. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Séparez la politique de segmentation de la politique de récupération : modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

Les éléments

Ces éléments fonctionnent le mieux lorsqu’ils sont considérés comme une surface mesurable. Capturez un exemple réussi, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Maintenez l’état du graphe plat et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit telle champ et perturbent la reprise après interruption.

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

Le fichier de configuration

Le fichier Config fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un exemplaire idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définites des vérifications de succès et refusez toute complétion partielle silencieuse. Maintenez l’état du graphe plat et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit telle champ et perturbent la reprise après interruption. Le fichier Config fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un exemplaire idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent se trouver en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du graphe.

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

L’usine de LLM

Pour The LLM factory, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’une mise en forme ultérieure. Préférez des sorties structurées avec validation de schéma plutôt que du texte libre lorsque l’étape suivante consiste en du code ou une appel à outil.

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,
    )

Le graphique du codeur

Pour le graphe The Coder, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Mettez en place une approbation humaine pour les étapes qui engagent des dépenses ou modifient des données de production. La connexion en temps de compilation ne garantit pas la complétude du processus métier.

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()

Envelopper un graphe en exécuteur A2A

Pour encapsuler un graphe en tant qu’exécuteur A2A, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute exécution partielle silencieuse. Faites approuver par un humain les actions qui entraînent des dépenses ou modifient des données de production. La connexion en temps de compilation ne correspond pas à une complétude opérationnelle. Pour encapsuler un graphe en tant qu’exécuteur A2A, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent se trouver en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’intégralité du code.

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()

...

Service à chaque agent

Lorsque vous travaillez sur le service à chaque agent, notez d’abord les éléments requis : les entrées nécessaires, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez à la fois le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations apportées ultérieurement. Créez un point de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur tente à nouveau un nœud ultérieur.

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()

L’orchestrateur

Lorsque vous travaillez sur The Orchestrator, notez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de maintenir l’honnêteté des modifications ultérieures du code. Préférez des unités petites et testables aux scripts volumineux. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus complexe et embrouillé. Faites un point d’étape après les étapes coûteuses. Le mécanisme de reprise ne doit pas facturer à nouveau la même appel au LLM lorsque l’opérateur réessaie un nœud ultérieur.

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()

Lancer l’application

Lorsque vous travaillez sur l’étape « Running it », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez toute exécution partielle silencieuse. Faites des points de contrôle après les étapes coûteuses. Le mécanisme de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur réessaie un nœud ultérieur. Lorsque vous travaillez sur l’étape « Running it », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du graphe.

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

Un exemple d’exécution réelle

Choses qui ont mal fonctionné et comment les réparer

Les éléments qui se sont cassés et les moyens de les réparer fonctionnent le mieux lorsqu’ils sont considérés comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Maintenez l’état du graphe plat et typé. Les blocs imbriqués cachent le fait que tel nœud a écrit telle champ, ce qui empêche la reprise après interruption.

ModuleNotFoundError: Aucun module nommé 'a2a.server.apps' n’a été trouvé

ModuleNotFoundError: No module named 'a2a.server.apps' se prête le mieux à être traité comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et une note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définites des vérifications de succès et refusez toute complétion partielle silencieuse. Maintenez l’état du graphe plat et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit telle champ et perturbent la reprise après interruption. ModuleNotFoundError: No module named 'a2a.server.apps' se prête le mieux à être traité comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et une note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les indicateurs fonctionnels doivent se trouver en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du graphe.

ModuleNotFoundError: Aucun module nommé 'sse_starlette' n’a été trouvé

Pour le ModuleNotFoundError: Aucun module nommé 'sse_starlette' n’a été trouvé, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations ultérieures. Imposez une approbation humaine pour les actions qui entraînent des dépenses ou modifient des données de production. La connexion en temps de compilation ne garantit pas la complétude du processus métier.

Délais d’exécution mystérieux d’environ 5 secondes

Pour les temps d’attente mystérieux d’environ 5 secondes, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Faites approuver par des humains les actions qui entraînent des dépenses ou modifient des données de production. La connexion en temps de compilation ne garantit pas la complétude du processus métier.

Les modèles de raisonnement gâchent tout

Pour les modèles Reasoning eat the round, il faut définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminaisons partielles silencieuses. Préférez des sorties structurées avec validation de schéma plutôt que du texte libre lorsque l’étape suivante consiste en du code ou une appel d’outil. Pour les modèles Reasoning eat the round, il faut définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du graphe.

Noms d’enum Protobuf

Lorsque vous travaillez avec les noms d’enum Protobuf, notez d’abord le contrat : entrées requises, signal de succès, et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez à la fois le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’une mise en forme ultérieure. Faites un point après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel LLM lorsque l’opérateur tente à nouveau un nœud ultérieur.

Part ne contient plus TextPart

Lorsque vous travaillez sur un Part qui ne contient plus de TextPart, notez d’abord les exigences : entrées requises, signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Faites des points de contrôle après les étapes coûteuses. Le mécanisme de reprise ne doit pas facturer à nouveau la même appel au LLM lorsque l’opérateur réessaie un nœud ultérieur.

Espace pour étendre

Lorsque vous travaillez à l’extension de Room, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses. Faites des points de contrôle après les étapes coûteuses. Le mécanisme de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur réessaie un nœud ultérieur. Lorsque vous travaillez à l’extension de Room, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du graphe.

Références

Les références fonctionnent le mieux lorsqu’elles sont considérées comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de rollback avant d’élargir le périmètre. Documentez en même temps le parcours optimal et celui de récupération. Les tentatives répétées, les contrôles humains et le traitement des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Gardez l’état des graphes simple et typé ; les blocs imbriqués masquent le fait que tel nœud a écrit telle champ et perturbent la reprise après interruption.

Liste de contrôle opérationnelle

Pour la liste de contrôle opérationnelle, définissez les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir exécuter à nouveau l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le parcours passe de l’environnement de démonstration à des environnements partagés.

Obtenez l’approbation humaine pour les étapes qui engagent des dépenses ou modifient les données de production. La connexion en temps de compilation ne garantit pas la complétude des processus métier.

Rédigez un petit manuel d’utilisation : comment rotationner les clés, vider la file d’attente, et annuler la dernière ingestion.

Gardez les configurations en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.

Obtenez l’approbation humaine pour les étapes qui engagent des dépenses ou modifient les données de production. La connexion en temps de compilation ne garantit pas la complétude des processus métier.

Au préalable de mettre à niveau l’ensemble, figez les versions, conservez une version d’ référence pour le parcours critique, et vérifiez les étapes de réversion. Les environnements partagés nécessitent des limites de débit, des contrôles d’attribution et un responsable clair pour la rotation des secrets. Préférez une fiabilité solide à de brillantes démonstrations ponctuelles.

Note de lot pour 684455491963 : ne pas inclure les clés du fournisseur dans le répertoire, fixer une limite pour les tokens par session, et stocker les transcriptions à côté des fichiers d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.

Lors du travail sur la note de renforcement 0, écrivez d’abord le contrat : entrées requises, signal de succès, et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stockages de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.

Détail de renforcement 0/640 : mesurez le temps d’exécution, la classe de l’erreur et la consommation de tokens pour cette note, puis décidez si vous souhaitez conserver la modification en vous basant sur un ensemble de critères prédéfinis plutôt que sur des observations subjectives.

La note de renforcement 1 fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé.

Détail de renforcement 1/640 : mesurez le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décidez si vous conservez le changement en vous basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.

Pour la note de renforcement 2, définites les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés.

Détail de renforcement 2/640 : mesurer le temps d’exécution du mur, la classe d’erreur et l’utilisation des jetons pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble fixe de questions plutôt que sur des anecdotes.