Wskazówki praktyczne: Budowa pętli przeglądu kodu z udziałem dwóch agentów przy użyciu A2A, LangGraph i
Krok po kroku instrukcja dotycząca praktycznych wskazówek: tworzenie pętli przeglądu kodu z udziałem dwóch agentów przy użyciu A2A, LangGraph oraz mechanizmów umów, sprawdzeń i gotowych miejsc na kod dla zespołów wdrażających ten wzorzec.
Źródła: Building a Two-Agent Code Review Loop with A2A, LangGraph, and LM Studio
Poniższe notatki przedstawiają praktyczny plan działania dotyczący „Tworzenia pętli przeglądu kodu z udziałem dwóch agentów przy użyciu A2A, LangGraph i LM Studio”. Nacisk kładziony jest na umowy, sprawdzania oraz miejsca zastępcze dla kodu, a nie na aspekty motywacyjne. Podczas analizy przeglądu ogólnego najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całego grafu.
Czym właściwie jest A2A (jeden akapit)
To, czym właściwie jest A2A (jeden akapit), najlepiej funkcjonuje, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden idealny przypadek działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Udokumentuj zarówno prawidłowy przebieg procesu, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później. Oddziel zasadę dzielenia na fragmenty od zasady wyszukiwania. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej w przypadku zmian wskaźników jakości.
Części składowe
Części te działają najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden przykład udanego działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok się nie powiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wtórne struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, i powodują przerwanie kontynuacji po przerwach.
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
Plik konfiguracyjny
Plik Config funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadań. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji po przerwach. Plik Config funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Utrzymuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą audytować bez konieczności czytania całego grafu.
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
Fabryka LLM
Dla fabryki LLM należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg działania, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później. W przypadku, gdy następnym krokiem jest kod lub wywołanie narzędzia, należy preferować ustrukturyzowane wyniki z walidacją schematu zamiast tekstu w formie swobodnej.
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,
)
Graf kodera
Dla grafu The Coder należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Konieczne jest ludzkie zatwierdzenie dla operacji, które wiążą się z wydatkami lub modyfikacją danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się kompletności biznesowej.
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()
Zawijanie grafu jako wykonywacz A2A
Aby zap包 graf jako wykonywacz A2A, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Wprowadź ludzką aprobatę dla operacji, które wiążą się z wydawaniem pieniędzy lub modyfikacją danych produkcyjnych. Połączenia skompilowane w czasie kompilacji nie równają się pełnej kompletności biznesowej. Aby zap包 graf jako wykonywacz A2A, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, bez konieczności czytania całego pliku.
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()
...
Służenie każdemu agentowi
Podczas pracy nad tematem Służenie każdemu agentowi najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno prawidłowy przebieg, jak i ścieżkę naprawczą. Próby ponowne, kontrola przez człowieka oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później. Ustal punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
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()
Orkiestrator
Gdy pracujesz nad narzędziem The Orchestrator, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolę małe, testowalne jednostki niż rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Ustaw punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
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()
Rozpoczynanie pracy
Gdy pracujesz nad sekcją „Running it”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć przypadki cichego, częściowego ukończenia zadania. Ustaw punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy element. Gdy pracujesz nad sekcją „Running it”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.
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
Przykład rzeczywistego uruchomienia
Przykład rzeczywistego przebiegu działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden udany przypadek, jeden przypadek awarii oraz notatkę o cofnięciu działań, zanim rozszerzysz zakres badania. Zdokumentuj zarówno pomyślny, jak i naprawczy ścieżkę działania. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione obiekty ukrywają informację o tym, który węzeł zapisał dane do którego pola, i powodują przerwanie kontynuacji po przerwach.
Rzeczy, które się zepsuły, i jak je naprawić
Rzeczy, które się zepsuły, oraz sposoby ich naprawy działają najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wtórne struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, co utrudnia kontynuację pracy po przerwach.
ModuleNotFoundError: No module named 'a2a.server.apps'
ModuleNotFoundError: No module named 'a2a.server.apps' sprawdza się najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres.
Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy artefaktom, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań.
Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji po przerwach.
ModuleNotFoundError: No module named 'a2a.server.apps' sprawdza się najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres.
Utrzymuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą audytować bez konieczności czytania całego grafu.
ModuleNotFoundError: Brak modułu o nazwie 'sse_starlette'
W przypadku błędu ModuleNotFoundError: Brak modułu o nazwie 'sse_starlette' należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu.
Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżki naprawcze. Próby ponownego wykonania, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później.
Konieczne jest uzyskanie zatwierdzenia człowieka w przypadku operacji, które wiążą się z wydatkami lub zmianą danych produkcyjnych. Podłączenia realizowane w czasie kompilacji nie równają się kompletności funkcjonalności biznesowej.
Tajemnicze opóźnienia trwające około 5 sekund
Aby uniknąć tajemniczych przerw trwających około 5 sekund, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanej punktacji kontrolnej, bez konieczności zgadywania ukrytego stanu. Lepiej używać małych, łatwych do przetestowania jednostek niż rozbudowanych skryptów. Gdy dany krok zawiedzie, powinno to wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Konieczna jest ludzka akceptacja w przypadkach, gdy dochodzi do wydawania pieniędzy lub zmiany danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się kompletności biznesowej.
Modele rozumowania nie wystarczą
Dla modeli Reasoning eat the round należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Przy następnym kroku, który polega na tworzeniu kodu lub wywoływaniu narzędzia, preferuj strukturyzowane wyniki z walidacją schematu zamiast tekstów w formie swobodnej. Dla modeli Reasoning eat the round należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.
Nazwy enum w Protobuf
Pracując z nazwami enum w Protobuf, najpierw spisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Dokumentuj zarówno ścieżkę prawidłowego działania, jak i ścieżkę naprawczą. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Ustalaj punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
Part nie obejmuje już TextPart
Gdy przechodzisz przez etap, w którym Part nie obejmuje już TextPart, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie.
Wolę małe, testowalne jednostki niż rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.
Ustalaj punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
Miejsce na rozbudowę
Gdy rozszerzasz system za pomocą Room, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań. Ustaw punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą operację LLM, gdy operator próbuje ponownie wykonać późniejszy element. Gdy rozszerzasz system za pomocą Room, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.
Odnośniki
Reference works najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden idealny przypadek, jeden przypadek awarii oraz notatkę o cofnięciu działań, zanim rozszerzysz zakres. Udokumentuj zarówno ścieżkę pomyślną, jak i ścieżkę naprawczą. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, i powodują przerwanie kontynuacji po przerwach.
Lista kontrolna operacyjna
Dla listy kontrolnej operacyjnej zdefiniuj dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie wykonać dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do wspólnych środowisk.
Należy uzyskać zatwierdzenie człowieka dla operacji, które powodują wydatki lub zmiany w danych produkcyjnych. Połączenia skompilowane w czasie kompilacji nie gwarantują kompletności biznesowej.
Napisz krótki przewodnik: jak rotować klucze, jak opróżniać kolejki, jak cofnąć ostatnią operację importu.
Zachowaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności przeglądania całej struktury.
Należy uzyskać zatwierdzenie człowieka dla operacji, które powodują wydatki lub zmiany w danych produkcyjnych. Połączenia skompilowane w czasie kompilacji nie gwarantują kompletności biznesowej.
Zanim wdrożysz całą architekturę, zamroź wersje, utwórz „złoty zapis” dla kluczowych ścieżek oraz potwierdź kroki cofania. Środowiska współdzielone wymagają ograniczeń szybkości, weryfikacji przynależności oraz jasno określonego właściciela odpowiedzialnego za rotację tajnych danych. Wolisz nudną niezawodność od pomysłowych, jednorazowych demonstracji.
Uwaga dotycząca partii 684455491963: unikaj przechowywania kluczy dostawcy w repozytorium, ustaw ograniczenie liczby tokenów na sesję oraz przechowuj transkrypcje obok plików testowych, aby późniejsze zmiany modeli pozostały porównywalne.
Pracując nad uwagą dotyczącą wzmocnienia bezpieczeństwa nr 0, najpierw zapisz specyfikację: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Konfigurację należy przechowywać oddzielnie od kodu aplikacji – pliki środowiskowe, magazyny tajemnic oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności przeglądania całej struktury.
Szczegół wzmocnienia bezpieczeństwa 0/640: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej uwagi, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu kryteriów, a nie jedynie osobistych obserwacji.
Uwaga dotycząca wzmocnienia nr 1 działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny przypadek działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian przed rozszerzaniem zakresu. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.
Szczegół wzmocnienia 1/640: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę, opierając się na ustalonej serii pytań, a nie na anegdotach.
Dla notatki dotyczącej wzmocnienia nr 2 zdefiniuj dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie wykonać ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Zapisz czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy proces przechodzi z środowiska demonstracyjnego do wspólnych środowisk.
Szczegóły wzmocnienia 2/640: zmierz czas działania ściany, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie anegdoty.