Notatki praktyczne: Projektowanie pętli ulepszeń agenta: Ocena na poziomie produkcyjnym
Szczegółowy przewodnik po Notatkach praktycznych: Projektowanie pętli ulepszeń agenta: Ocena na poziomie produkcyjnym: umowy, sprawdzania oraz gotowe elementy kodu dla zespołów wdrażających ten wzorzec.
Poniższe notatki przedstawiają praktyczny plan działania dotyczący tematu „Projektowanie pętli ulepszeń agenta: pipeline’y oceny klasy produkcyjnej”. Nacisk kładziony jest na umowy, sprawdzania oraz miejsca zastępcze dla kodu, a nie na aspekty motywacyjne.
Wprowadzenie
Podczas przechodzenia przez etap wprowadzenia 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. Obok wyników funkcjonalnych zapisz czas wykonywania oraz koszt tokenów lub zapytań. Jasna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się od wersji demonstracyjnej do środowisk współdzielonych. Ustaw punkty kontrolne po kosztownych krokach – program nie powinien ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator ponawia próbę z późniejszym węzłem.
Spis treści
Gdy przechodzisz przez etap spisu treści, 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. Ustaw punkty kontrolne po kosztownych krokach. System powinien unikać ponownego naliczania opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
Faza 1: Podstawa
Gdy przechodzisz przez etap podstawowy Fazy 1, 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 ścieżkę prawidłowego działania, 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. Ustal punkty kontrolne po kosztownych krokach. System powrotu nie powinien ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
Ustawianie środowiska
Podczas przechodzenia przez etap konfiguracji środowiska, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co się dzieje w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. 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 pracy nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
# Install the libraries we need
pip install langgraph langchain langchain-openai langsmith
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=your_langsmith_key
export LANGSMITH_PROJECT=agent-improvement-loop
export OPENAI_API_KEY=your_openai_key
# Step 1: Import the pieces we need
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
# Step 2: Define two tools the agent can call
@tool
def get_account_info(account_id: str) -> str:
"""Look up basic information for an account by account_id."""
fake_accounts = {
"A100": "Account A100: plan=pro, status=active, email=ada@example.com",
"A200": "Account A200: plan=free, status=suspended, email=turing@example.com",
}
return fake_accounts.get(account_id, f"No account found with id {account_id}")
@tool
def get_recent_orders(account_id: str) -> str:
"""Return the three most recent orders for a given account_id."""
fake_orders = {
"A100": "Orders for A100: #9001 shipped, #9002 processing, #9003 returned",
"A200": "Orders for A200: no orders in the last 90 days",
}
return fake_orders.get(account_id, f"No orders for {account_id}")
# Step 3: Wire the tools into a ReAct agent
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [get_account_info, get_recent_orders]
agent = create_react_agent(model, tools)
Przetestujmy agenta
Gdy pracujesz nad etapem „Sprawdźmy”, 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 odrzuć możliwość cichego, częściowego ukończenia zadania. Ustaw punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą funkcję LLM, gdy operator próbuje ponownie wykonać późniejszy element.
# Step 4: Invoke the agent with a simple query
result = agent.invoke({
"messages": [{"role": "user", "content": "What plan is account A100 on?"}]
})
for message in result["messages"]:
print(message.type, ":", message.content)
Gdy pracujesz nad etapem „Sprawdźmy”, 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.
human : What plan is account A100 on?
ai :
tool : Account A100: plan=pro, status=active, email=ada@example.com
ai : Account A100 is on the pro plan.
Faza 2: Zbieranie śladów
Etap zbierania śladów w fazie 2 działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przepływ działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres badania. Zdokumentuj zarówno pomyślny, jak i alternatywny przepływ działania. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później. Utrzymuj stan grafu w formie prostych, spójnych struktur. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane w danym polu, i powodują przerwanie kontynuacji po przerwach.
Pobieranie najnowszych śladów z produkcji
Pobieranie najnowszych śladów z etapu realizacji działa 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, i powodują przerwanie kontynuacji po przerwach.
# Step 1: Create a LangSmith client
from langsmith import Client
from datetime import datetime, timedelta
client = Client()
# Step 2: List recent root runs from our project
recent_runs = list(client.list_runs(
project_name="agent-improvement-loop",
is_root=True,
start_time=datetime.utcnow() - timedelta(days=1),
))
print(f"Found {len(recent_runs)} root runs in the last 24 hours")
Found 42 root runs in the last 24 hours
Zbieranie potrzebnych nam pól
Faza zbierania pól funkcjonuje najlepiej, gdy traktowana jest jako mierzalna powierzchnia. Zapisz jeden idealny zapis, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy artefaktom, 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 z danego pola, co powoduje przerwanie kontynuacji po przerwach. Faza zbierania pól funkcjonuje najlepiej, gdy traktowana jest jako mierzalna powierzchnia. Zapisz jeden idealny zapis, 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.
# Step 3: Build a compact trace record from each run
def compact_trace(run):
return {
"run_id": str(run.id),
"inputs": run.inputs,
"outputs": run.outputs,
"error": run.error,
"latency_ms": (run.end_time - run.start_time).total_seconds() * 1000
if run.end_time else None,
"tool_calls": [
child.name for child in client.list_runs(
trace_id=run.trace_id, run_type="tool"
)
],
}
traces = [compact_trace(run) for run in recent_runs]
print(f"Collected {len(traces)} compact traces")
print("Example tool calls:", traces[0]["tool_calls"])
Collected 42 compact traces
Example tool calls: ['get_account_info']
# Step 4: Inspect the first trace end to end
import json
print(json.dumps(traces[0], indent=2, default=str))
{
"run_id": "b1d2e3f4-...",
"inputs": {"messages": [{"role": "user", "content": "What plan is account A100 on?"}]},
"outputs": {"messages": [{"role": "ai", "content": "Account A100 is on the pro plan."}]},
"error": null,
"latency_ms": 1423.51,
"tool_calls": ["get_account_info"]
}
Faza 3: Wzbogacanie śladów za pomocą ocen
W etapie wzbogacania śladów z Fazy 3 należy najpierw określić dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą 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łędowych stanowią część produktu, a nie elementy dodawane później. Konieczna jest ludzka akceptacja w przypadku operacji, które powodują wydatki lub zmieniają dane produkcyjne. Podłączenia realizowane w czasie kompilacji nie równają się pełnej kompletności rozwiązania biznesowego.
Szczegół 1: Sprawdzanie poprawności narzędzi opartych na kodzie
Dla etapu opartego na kodzie warstwy 1 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, powinno to wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Autoryzuj procesy przy bramie wejściowej, a ponownie udzielaj uprawnień na poziomie płaszczyzny danych. Sam token nie stanowi granicy między poszczególnymi użytkownikami.
# Step 1: Define a rule that says which tool SHOULD be called for a given query
import re
def expected_tool_for_query(query: str) -> str:
q = query.lower()
if re.search(r"\b(order|shipment|shipped|return)\b", q):
return "get_recent_orders"
if re.search(r"\b(plan|account|status|email)\b", q):
return "get_account_info"
return "any"
def score_tool_correctness(trace):
query = trace["inputs"]["messages"][0]["content"]
expected = expected_tool_for_query(query)
actual = trace["tool_calls"][0] if trace["tool_calls"] else None
if expected == "any":
return 1.0
return 1.0 if actual == expected else 0.0
# Step 2: Score qualitative properties with an LLM judge
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
judge_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
judge_prompt = ChatPromptTemplate.from_messages([
("system", "You are an expert reviewer of AI agent responses. "
"Rate the response on a scale of 1 to 5 for helpfulness. "
"Respond with only a single integer, nothing else."),
("user", "User question: {question}\n\nAgent response: {response}\n\nScore:"),
])
def score_helpfulness(trace):
question = trace["inputs"]["messages"][0]["content"]
response = trace["outputs"]["messages"][-1]["content"]
chain = judge_prompt | judge_model
result = chain.invoke({"question": question, "response": response})
try:
return int(result.content.strip()) / 5.0
except ValueError:
return 0.0
Warstwa 3: przegląd przez autora jako kolejka adnotacji
W fazie przeglądu przez autora w warstwie 3 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 zadań. Zapewnij ludzką aprobatę w przypadku operacji, które wiążą się z wydawaniem pieniędzy lub modyfikacją danych produkcyjnych. Konfiguracja w czasie kompilacji nie równa się pełnej kompletności biznesowej. W fazie przeglądu przez autora w warstwie 3 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łego pliku.
aph.# Step 3: Model a human review signal
def score_human(trace, human_labels: dict) -> float | None:
run_id = trace["run_id"]
return human_labels.get(run_id)
human_labels = {
"b1d2e3f4-...": 1.0,
"c2e3f4g5-...": 0.0,
}
Łączenie trzech warstw
Podczas prace nad etapem łączenia trzech warstw 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 ś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. 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 późniejszego węzła.
# Step 4: Run all three scorers and attach scores to each trace
def enrich(trace):
trace["scores"] = {
"tool_correctness": score_tool_correctness(trace),
"helpfulness": score_helpfulness(trace),
"human": score_human(trace, human_labels),
}
return trace
enriched = [enrich(t) for t in traces]
print(json.dumps(enriched[0]["scores"], indent=2))
{
"tool_correctness": 1.0,
"helpfulness": 0.8,
"human": null
}
Faza 4: Odkrywanie wzorców
Podczas prace na etapie 4 – odkrywania wzorców, 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. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. 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. System powinien unikać ponownego pobierania opłat za tę samą funkcję LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
Filtrowanie błędów
Gdy przechodzisz przez etap filtrowania błędów, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego błędu. 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 odrzuć ciche, częściowe ukończenie zadania. Ustaw punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą wywołanie modelu LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
# Step 1: Keep only traces where at least one score is low
def is_low_scoring(trace):
scores = trace["scores"]
if scores["human"] is not None and scores["human"] < 0.5:
return True
if scores["tool_correctness"] < 0.5:
return True
if scores["helpfulness"] < 0.6:
return True
return False
failures = [t for t in enriched if is_low_scoring(t)]
print(f"{len(failures)} of {len(enriched)} traces flagged as low scoring")
Gdy przechodzisz przez etap filtrowania błędów, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego błędu. 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.
9 of 42 traces flagged as low scoring
Klasyfikacja niepowodzeń w kategorie
Etap klasyfikacji niepowodzeń w kategorie działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przepływ działania, jeden przypadek niepowodzenia oraz notatkę dotyczącą cofnięcia działań przed rozszerzeniem zakresu. Zdokumentuj razem ścieżkę prawidłowego działania oraz ścieżkę przywracania stanu. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane w danym polu, i powodują przerwanie kontynuacji po przerwach.
# Step 2: Tag each failure with a category based on its scores
def categorize(trace):
scores = trace["scores"]
if scores["tool_correctness"] < 0.5:
return "wrong_tool"
if scores["helpfulness"] < 0.6 and scores["tool_correctness"] >= 0.5:
return "unhelpful_answer"
if scores["human"] is not None and scores["human"] < 0.5:
return "human_flagged"
return "other"
from collections import Counter
categories = Counter(categorize(t) for t in failures)
print(categories.most_common())
[('wrong_tool', 5), ('unhelpful_answer', 3), ('human_flagged', 1)]
# Step 3: Print the inputs and outputs for every wrong_tool failure
wrong_tool = [t for t in failures if categorize(t) == "wrong_tool"]
for trace in wrong_tool[:3]:
print("QUERY:", trace["inputs"]["messages"][0]["content"])
print("TOOLS CALLED:", trace["tool_calls"])
print("RESPONSE:", trace["outputs"]["messages"][-1]["content"])
print("---")
QUERY: Is my subscription active?
TOOLS CALLED: ['get_recent_orders']
RESPONSE: I could not find any recent orders to confirm your subscription status.
---
QUERY: Tell me about my subscription to your product
TOOLS CALLED: ['get_recent_orders']
RESPONSE: There are no recent orders for this account.
---
Faza 5: Zestawy testów offline
Faza 5, test offline, funkcjonuje najlepiej, gdy traktowana jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny wynik testu, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres testów. Wolno preferować małe, łatwe do przetestowania 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 danych ukrywają informację o tym, który węzeł zapisał dane do którego pola, i powodują przerwę w kontynuacji pracy po zakłóceniach.
Tworzenie zestawu danych
Faza tworzenia zestawu danych funkcjonuje najlepiej, gdy traktowana jest jako mierzalna powierzchnia. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Traktuj tę fazę 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ń. Utrzymuj stan grafu w prostej formie i określonej strukturze typów. Wkładki nawiasowe ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji pracy po zakłóceniach.
# Step 1: Create a LangSmith dataset for our agent
dataset_name = "agent-production-failures"
dataset = client.create_dataset(
dataset_name=dataset_name,
description="Production traces where the agent failed. Each example captures the user query and the expected tool call.",
)
print(f"Created dataset: {dataset.id}")
Faza tworzenia zestawu danych funkcjonuje najlepiej, gdy traktowana jest jako mierzalna powierzchnia. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Utrzymuj 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.
Created dataset: 3f2a1b0c-...
# Step 2: Convert each failure trace into a dataset example
examples = []
for trace in failures:
query = trace["inputs"]["messages"][0]["content"]
expected_tool = expected_tool_for_query(query)
examples.append({
"inputs": {"question": query},
"outputs": {"expected_tool": expected_tool},
"metadata": {"category": categorize(trace), "source_run_id": trace["run_id"]},
})
client.create_examples(dataset_id=dataset.id, examples=examples)
print(f"Added {len(examples)} examples to {dataset_name}")
Added 9 examples to agent-production-failures
# Step 3: Write an evaluator that checks the agent's tool call against the expected tool
def tool_match_evaluator(inputs: dict, outputs: dict, reference_outputs: dict):
actual_messages = outputs.get("messages", [])
tool_called = None
for m in actual_messages:
if getattr(m, "tool_calls", None):
tool_called = m.tool_calls[0]["name"]
break
expected = reference_outputs["expected_tool"]
score = 1.0 if (expected == "any" or tool_called == expected) else 0.0
return {"key": "tool_match", "score": score}
Funkcja docelowa
Na etapie funkcji docelowej 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 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 ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Konieczna jest ludzka akceptacja w przypadku operacji, które powodują wydatki lub zmieniają dane produkcyjne. Połączenia realizowane w czasie kompilacji nie równają się pełnej kompletności biznesowej.
# Step 4: Wrap our agent in a function that takes a LangSmith example and returns its output
def run_agent(inputs: dict) -> dict:
result = agent.invoke({
"messages": [{"role": "user", "content": inputs["question"]}]
})
return result
Faza 6: Zamknięcie pętli
W fazie 6, czyli zamykaniu etapu, należy określić 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 preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powinien wskazywać na jedną konkretne odpowiedzialność, a nie na skomplikowany łańcuch operacji. Konieczne jest ludzkie zatwierdzenie w przypadkach, gdy dochodzi do wydatków lub zmian w danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się kompletności procesu biznesowego.
Prowadzenie oceny
Podczas etapu oceny 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 na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj ten etap 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. Konieczna jest ludzka akceptacja w przypadku operacji, które wiążą się z wydatkami lub zmianami w danych produkcyjnych. Podłączenia realizowane w czasie kompilacji nie równają się pełnej kompletności biznesowej.
# Step 1: Run the evaluator across the dataset with the current agent
from langsmith.evaluation import evaluate
experiment_results = evaluate(
run_agent,
data=dataset_name,
evaluators=[tool_match_evaluator],
experiment_prefix="agent-v1-baseline",
max_concurrency=4,
)
Podczas etapu oceny 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. Konfigurację należy przechowywać poza kodem aplikacji. Pliki środowiskowe, magazyny poufnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.
View the evaluation results for experiment: 'agent-v1-baseline-...' at:
https://smith.langchain.com/o/.../datasets/.../compare?selectedSessions=...
9/9 runs | avg tool_match: 0.33
Dostarczenie poprawki i ponowna ocena
Gdy pracujesz nad naprawą procesu wysyłki, 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 operacji, jak i ścieżkę odzyskiwania. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędowych są częścią produktu, a nie elementem późniejszej dopracowywania. Ustal punkty kontrolne po kosztownych krokach. System powrotu nie powinien ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
# Step 2: Update the tool docstring to teach the agent about subscriptions
@tool
def get_account_info_v2(account_id: str) -> str:
"""Look up basic information for an account, including plan, status,
subscription tier, and contact email. Use this tool for any question
about the account itself, including subscription status."""
fake_accounts = {
"A100": "Account A100: plan=pro, status=active, email=ada@example.com",
"A200": "Account A200: plan=free, status=suspended, email=turing@example.com",
}
return fake_accounts.get(account_id, f"No account found with id {account_id}")
tools_v2 = [get_account_info_v2, get_recent_orders]
agent_v2 = create_react_agent(model, tools_v2)
def run_agent_v2(inputs: dict) -> dict:
result = agent_v2.invoke({
"messages": [{"role": "user", "content": inputs["question"]}]
})
return result
# Step 3: Run the evaluator on the v2 agent
experiment_results_v2 = evaluate(
run_agent_v2,
data=dataset_name,
evaluators=[tool_match_evaluator],
experiment_prefix="agent-v2-docstring-fix",
max_concurrency=4,
)
View the evaluation results for experiment: 'agent-v2-docstring-fix-...' at:
https://smith.langchain.com/o/.../datasets/.../compare?selectedSessions=...
9/9 runs | avg tool_match: 0.89
Przed i po
Gdy przechodzisz przez etap „Przed” i „Po”, 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. Wolij małe, testowalne jednostki od rozbudowanych skryptów. 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. System powinien unikać ponownego pobierania opłat za tę samą funkcję LLM, gdy operator próbuje ponownie wykonać późniejszy element.
Before (v1): After (v2):
wrong_tool: 5 wrong_tool: 1
unhelpful: 3 unhelpful: 0
human: 1 human: 0
avg score: 0.33 avg score: 0.89
Ocena i testowanie
Podczas przechodzenia przez etap oceny i testowania 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 odrzuć możliwość cichego, częściowego ukończenia zadania. 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. Podczas przechodzenia przez etap oceny i testowania 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.
# Full loop driver
def run_improvement_loop():
# 1. Pull traces
runs = list(client.list_runs(
project_name="agent-improvement-loop",
is_root=True,
start_time=datetime.utcnow() - timedelta(days=1),
))
traces = [compact_trace(r) for r in runs]
# 2. Enrich with scores
enriched = [enrich(t) for t in traces]
# 3. Find failures and categorize them
failures = [t for t in enriched if is_low_scoring(t)]
categories = Counter(categorize(t) for t in failures)
# 4. Add failures to the dataset
new_examples = [{
"inputs": {"question": t["inputs"]["messages"][0]["content"]},
"outputs": {"expected_tool": expected_tool_for_query(
t["inputs"]["messages"][0]["content"])},
"metadata": {"category": categorize(t), "source_run_id": t["run_id"]},
} for t in failures]
if new_examples:
client.create_examples(dataset_id=dataset.id, examples=new_examples)
# 5. Re-evaluate the current agent against the full dataset
result = evaluate(
run_agent_v2,
data=dataset_name,
evaluators=[tool_match_evaluator],
experiment_prefix="daily-regression",
)
return {
"traces_collected": len(traces),
"failures_found": len(failures),
"by_category": dict(categories),
"examples_added": len(new_examples),
}
print(run_improvement_loop())
{
"traces_collected": 186,
"failures_found": 12,
"by_category": {"wrong_tool": 4, "unhelpful_answer": 6, "human_flagged": 2},
"examples_added": 12,
"regression_score": 0.87
}
Jak dalej to ulepszyć
Etap „Jak dalej to ulepszyć” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia do poprawek. Zanim rozszerzysz zakres, zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian. Zdokumentuj razem ścieżkę prawidłowego działania oraz ścieżkę przywracania stanu. 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 struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji po przerwach.
Lista kontrolna operacyjna
Dla etapu listy kontrolnej operacyjnej zdefiniuj wprowadzane dane, osobę odpowiedzialną za daną czynność oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie wykonać daną czynność na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu.
Zapisuj czas wykonywania oraz koszt tokena lub zapytania obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do udostępnionych środowisk.
Zastosuj ludzką aprobatę w przypadku operacji, które generują wydatki lub zmieniają dane produkcyjne. Połączenia skompilowane w czasie kompilacji nie gwarantują kompletności rozwiązania biznesowego.
Ewentualnie gorsza odpowiedź, która kosztuje 10 razy mniej, może być lepszym wyborem do użycia w środowisku produkcyjnym.
Zapisz wersje zależności oraz digest obrazu, który uruchomił demonstrację. Reprodukowalność jest ważniejsza od lokalnej wiedzy specjalistów.
Niechaj przeważać będą małe, testowalne jednostki nad rozbudowanymi skryptami. Gdy jakiś krok zawiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.
Zanim wdrożysz cały stack, zamroź wersje, utwórz dokładny zapis dla kluczowych etapów realizacji oraz potwierdź kroki odwracania zmian. Środowiska współdzielone wymagają ograniczeń szybkości, weryfikacji przynależności użytkowników oraz wyraźnego właściciela odpowiedzialnego za rotację haseł. Wolimy nudną niezawodność od pomysłowych, jednorazowych demonstracji.
Uwaga dotycząca procesu batch dla febbeae44915: unikaj przechowywania kluczy dostawcy w repozytorium, ustaw ograniczenie liczby tokenów na sesję oraz przechowuj zapisy obok plików testowych, aby późniejsze zmiany modeli pozostały porównywalne.
Podczas pracy nad etapem 0 dotyczącym wzmocnienia bezpieczeństwa najpierw zapisz specyfikację: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego awarii. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolimy małe, testowalne jednostki niż rozbudowane skrypty. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.
Szczegół wzmocnienia 0/961: zmierz czas wykonywania, 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.
Faza 1 notatki dotyczącej wzmocnienia działa najlepiej, gdy jest traktowana jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmiany, zanim rozszerzysz zakres badania. 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ółdzielonych środowisk.
Szczegół wzmocnienia 1/961: zmierz czas wykonywania, 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.
Dla drugiego etapu ulepszeń związanych z wzmacnianiem bezpieczeństwa należy określić dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie wykonać ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg operacji, jak i ścieżkę naprawczą. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później.
Szczegół 2/961 dotyczący wzmacniania bezpieczeństwa: należy zmierzyć czas wykonywania operacji, klasę błędu oraz zużycie tokenów dla tego przypadku, a następnie zdecydować o utrzymaniu zmiany na podstawie ustalonego zestawu pytań, a nie opisów incydentów.
Gdy przechodzisz przez trzeci etap notatki dotyczącej wzmocnienia bezpieczeństwa, 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 odrzuć przypadki częściowego ukończenia bez żadnych informacji.
Szczegół 3/961 dotyczący wzmocnienia bezpieczeństwa: zmierz czas wykonywania, 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 informacji anegdotycznych.
Etap 4 notatki o wzmocnieniu bezpieczeństwa funkcjonuje najlepiej, gdy jest traktowany jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przykład działania, jeden przypadek niepowodzenia oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres prac. Utrzymuj 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.
Szczegół wzmocnienia 4/961: zmierz czas wykonywania, 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.
W fazie 5 notatki dotyczącej wzmocnienia określ 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. Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powód awarii powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces.
Szczegół wzmocnienia 5/961: zmierz czas wykonywania, 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.
Gdy przechodzisz przez etap nr 6 notatki dotyczącej wzmocnienia bezpieczeństwa, najpierw zapisz warunki umowy: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego awarii. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Obok wyników funkcjonalnych zapisz czas wykonywania oraz koszt tokena lub zapytania. Jasna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk.
Szczegóły wzmocnienia bezpieczeństwa 6/961: zmierz czas wykonywania, 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 osobistych obserwacji.
Etap nr 7 notatki dotyczącej wzmocnienia bezpieczeństwa działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię do analizy. Zanim rozszerzysz zakres, zapisz jeden idealny przepływ działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian. Zdokumentuj razem ścieżkę prawidłowego działania oraz ścieżkę naprawczą. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później.
Szczegół wzmocnienia 7/961: zmierz czas wykonywania, 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.
W etapie 8 notatki dotyczącej wzmocnienia określ dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie wykonać ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy artefaktom, zdefiniuj sprawdzenia sukcesu i odrzuć ciche, częściowe ukończenie zadania.
Szczegół wzmocnienia 8/961: zmierz czas wykonywania, 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.
Gdy przechodzisz przez etap nr 9 notatki dotyczącej wzmocnienia bezpieczeństwa, najpierw zapisz warunki kontraktu: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego awarii. 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.
Szczegół nr 9/961 dotyczący wzmocnienia bezpieczeństwa: zmierz czas wykonywania, 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 informacji anegdotycznych.
Etap nr 10 notatki dotyczącej wzmocnienia bezpieczeństwa działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię do analizy. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres prac. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję działań.
Szczegóły wzmocnienia bezpieczeństwa 10/961: zmierz czas działania, 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 informacji anegdotycznych.