Skip to main content

Trace Learning Loop

Trace learning improves retrieval, target selection, and planning from execution history without fine-tuning the LLM.

The first goal is not to train a model. The first goal is to capture safe, collection-scoped evidence from real runs, compare it in shadow mode, and promote only the evidence that repeatedly improves future search and plan behavior.

Mental Model

run attempt
-> scrub payload
-> build learning record
-> derive suggestions
-> keep suggestions in shadow
-> validate with repeated success or Quality Lab
-> promote
-> retrieval and target selector receive low-weight evidence

The LLM sees better candidates and metadata later. The LLM itself is not changed by this loop.

Why This Comes Before Fine-Tuning

Most early failures in large API collections are evidence problems:

  • the correct tool is not high enough in Top-K
  • sibling tools have weak action, resource, or result-shape metadata
  • the selector cannot explain why a target is preferred
  • required fields are not linked to producers
  • auth failures are not separated from request or API failures
  • successful retries are not fed back into search or plan evidence

Trace learning fixes these by improving the graph and selector signals that the LLM receives. Fine-tuning can come later, after there is clean evidence worth training on.

Public API

from graph_tool_call.learning import (
apply_learning_suggestions,
build_trace_learning_record,
derive_learning_suggestions,
scrub_trace_payload,
summarize_learning_state,
)

The API is deliberately storage-neutral. graph-tool-call creates records, suggestions, summaries, and optional ranking signals. Your adapter decides where to persist them and when to promote them.

Learning Record

Use build_trace_learning_record() after a search, plan, or execute attempt.

record = build_trace_learning_record(
query="show order detail",
collection_id="orders-api",
attempt_id="attempt_001",
session_id="runtime-session-id",
selected_target="getOrderDetail",
llm_target="getOrderInfo",
plan_tools=["findOrder", "getOrderDetail"],
failure_reason=None,
success=True,
latency_ms=842,
target_selector={
"selected_target": "getOrderDetail",
"overrode_llm": True,
"reason_codes": ["llm_target_overridden"],
},
trace_edges=[
{
"source": "findOrder",
"target": "getOrderDetail",
"data_flow": {"to_field": "orderNo"},
}
],
)

The returned record is JSON-safe and compact.

FieldPurpose
queryScrubbed user query
query_familyNormalized grouping key for similar queries
query_fingerprintStable hash of the query family
collection_idCollection-local scope
attempt_idAttempt identifier
session_id_hashHashed session id, never the raw value
selected_targetFinal selected tool
llm_targetLLM-proposed target, if available
plan_toolsOrdered successful or attempted plan tools
failure_reasonStable reason code, if any
successAttempt status
latency_msEnd-to-end latency
target_selectorScrubbed selector diagnostics
trace_edgesScrubbed run-observed graph edge evidence
created_atISO timestamp

Raw request bodies, raw response bodies, tokens, cookies, API keys, and obvious personal data are not stored.

Scrubbing Boundary

Every record field is passed through scrub_trace_payload().

clean = scrub_trace_payload(
{
"Authorization": "Bearer secret-token",
"email": "person@example.com",
"response_body": {"orderNo": "1234"},
}
)

Example output:

{
"Authorization": "[REDACTED]",
"email": "[REDACTED_EMAIL]",
"response_body": "[REDACTED]"
}

Scrubbing is a safety boundary, not a data science transformation. If a payload cannot be safely compacted, reject the learning record.

Suggestion Types

derive_learning_suggestions() converts a successful record into candidate improvements.

Suggestion TypeMeaningTypical Consumer
target_preferenceThis query family repeatedly selected this targetretrieval and target selector
plan_pathThis ordered plan path workedplan synthesis
data_flow_edgeA runtime trace showed field flow between toolsgraph expansion and planner
field_mappingA field mapping candidate should be reviewedadapter or operator UI
context_default_candidateA context default may remove user-input promptsadapter settings
enum_mapping_candidateAn enum label/value mapping may be usefuladapter settings

Only the first three are derived directly from the current helper. The other types are part of the public suggestion vocabulary so adapters can add reviewed signals without changing the contract.

Suggestion Lifecycle

suggested
-> promotable
-> promoted
-> used as low-weight retrieval/selector evidence

suggested
-> rejected
-> ignored by retrieval/selector

The default promotion policy is conservative:

Policy FieldDefaultMeaning
min_success_observations2repeated success required before a suggestion becomes promotable
max_recent_failure_ratio0.5avoid promoting evidence from unstable query families
max_attempts50compact storage bound for recent attempts
max_suggestions100compact storage bound for suggestions

Adapters can require a human approval step before changing promotable to promoted.

Derive Suggestions

from graph_tool_call.learning import derive_learning_suggestions

suggestions = derive_learning_suggestions(
record,
history=previous_attempts,
existing_suggestions=current_suggestions,
promotion_policy={"min_success_observations": 2},
)

Store returned suggestions under the collection, not globally. A target preference learned from one API collection should not leak into another collection.

Apply Learning Signals

Learning boosts are optional and low-weight.

from graph_tool_call.learning import apply_learning_suggestions

ranked = apply_learning_suggestions(
"show order detail",
candidates=[
{"name": "getOrderInfo", "score": 0.41},
{"name": "getOrderDetail", "score": 0.39},
],
suggestions=collection_learning["suggestions"],
mode="promoted",
)

Example signal:

{
"source": "learning",
"target": "getOrderDetail",
"suggestion_type": "target_preference",
"status": "promoted",
"observations": 3,
"score": 0.045
}

In mode="promoted", only promoted suggestions affect ranking. Shadow analysis can use mode="shadow" to compare suggested and promotable evidence without changing production behavior.

Collection Storage Shape

A product adapter can store learning under a collection graph artifact.

{
"learning": {
"attempts": [],
"suggestions": [],
"promotion_policy": {
"min_success_observations": 2,
"max_recent_failure_ratio": 0.5
},
"summary": {
"attempt_count": 0,
"success_rate": null,
"promoted_count": 0
}
}
}

Use summarize_learning_state() to build a small UI/API summary.

Observe, Shadow, Promote

ModeWhat HappensProduction Ranking Changes?
observeattempts and suggestions are recordedno
shadowlearning-applied result is computed for comparisonno
promotedpromoted suggestions add low-weight signalsyes

This prevents one lucky success from strongly changing future behavior.

Quality Lab Integration

Quality Lab should record both the original result and the learning-shadowed result.

MetricMeaning
learning_suggestions_createdsuggestions derived from the run
learning_applied_shadow_rankrank after applying shadow suggestions
promotion_statussuggested, promotable, promoted, or rejected
target_rank_deltawhether learning would move the expected target upward

Promote only after Quality Lab or repeated real runs prove improvement.

Adapter Boundary

graph-tool-call defines record and suggestion contracts. Product adapters own:

  • persistence
  • retention policy
  • operator approval
  • collection-level isolation
  • auth/session resolution
  • UI controls for promote/reject
  • deciding whether shadow or promoted mode is active

The engine must not receive raw credentials or user identifiers.

Troubleshooting

SymptomCheckLikely Fix
suggestions are never createdrecord success and selected_targetensure successful runs build learning records
suggestions never become promotablematching query fingerprint and success countinspect query normalization and history retention
learning changes ranking too aggressivelymode, max_boost, suggestion statususe promoted mode only and lower boost
sensitive strings appear in learning JSONscrub_trace_payload testsreject record and add scrub rule
target improves in shadow but not productionpromotion statusapprove or promote after gate passes

Validation

poetry run pytest tests/test_trace_learning.py -q
poetry run pytest tests/ -q -k "learning or quality_lab or target_selector"

For documentation-only changes:

cd website
npm run typecheck
npm run build