You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/API_TOOL_CALLING.md
+288-1Lines changed: 288 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,6 +22,7 @@ loop collects all required parameters from the user before the API call is made.
22
22
|**Parallel API caller**| Fires all completed endpoint calls concurrently via `asyncio.gather` with batch timeout and partial-failure handling | ✅ Phase 4 Complete |
23
23
|**Multi-response formatter**| DSPy module that synthesises N API results into a single coherent natural-language answer; supports streaming and blocking execution | ✅ Phase 5 Complete |
24
24
|**Full wiring**|`APIToolWorkflowExecutor` routes parallel sessions through `MultiEndpointAgenticLoop` → `MultiAPICaller` → `MultiResponseFormatterModule` with output guardrails | ✅ Phase 6 Complete |
25
+
|**ATC Response Cache**| Two-tier Redis cache (L1 exact-match + L2 follow-up context) that eliminates redundant API calls and enables intelligent follow-up handling without re-running the agentic loop | ✅ Complete |
25
26
26
27
---
27
28
@@ -1042,7 +1043,6 @@ Same API call steps, then `asyncio.to_thread(formatter.forward, ...)` for the sy
1042
1043
1043
1044
---
1044
1045
1045
-
1046
1046
### Constants and Feature Flags
1047
1047
1048
1048
Defined in [src/tool_classifier/constants.py](../src/tool_classifier/constants.py) and [src/llm_orchestrator_config/feature_flags.py](../src/llm_orchestrator_config/feature_flags.py):
@@ -1185,3 +1185,290 @@ Bot: "Here's what I found: The address Viru tn 4 is located in Tallinn city cent
1185
1185
```
1186
1186
1187
1187
---
1188
+
1189
+
1190
+
1191
+
1192
+
## Part 8 — ATC Response Cache
1193
+
1194
+
### Overview
1195
+
1196
+
The ATC Response Cache is a two-tier Redis cache that sits inside `_compute_loop_step()` in
│ API called directly — entire agentic loop skipped
1330
+
│
1331
+
│ missing != []
1332
+
│ → context["seeded_params"] = merged
1333
+
│ fall through to agentic loop — only asks for gaps
1334
+
│
1335
+
└─ "new_intent"
1336
+
→ ignore L2; fall through to normal agentic loop
1337
+
1338
+
On any FollowUpDetectorModule exception → fall through to normal loop (fail-open)
1339
+
```
1340
+
1341
+
---
1342
+
1343
+
### Component: `FollowUpDetectorModule`
1344
+
1345
+
Defined in [src/tool_classifier/follow_up_detector.py](../src/tool_classifier/follow_up_detector.py).
1346
+
1347
+
DSPy `Predict` module that classifies the relationship between the new user query
1348
+
and the previous API call. Run via `asyncio.to_thread` to avoid blocking the event loop.
1349
+
1350
+
**Inputs:**
1351
+
1352
+
| Input | Description |
1353
+
|---|---|
1354
+
|`user_query`| The new user message |
1355
+
|`previous_query`| The user's original question that triggered the last API call |
1356
+
|`previous_params`| JSON of param values from the last call |
1357
+
|`params_schema`| JSON of the endpoint's param schema |
1358
+
1359
+
**Output — three possible values for `follow_up_type`:**
1360
+
1361
+
| Value | Meaning | Action |
1362
+
|---|---|---|
1363
+
|`response_question`| User is asking about the data already returned | Pass L2 `raw_response` to formatter; no API call |
1364
+
|`param_update`| User wants the same endpoint with different/additional params | Merge new params into previous; go to API directly if complete, else seed the loop |
1365
+
|`new_intent`| Completely unrelated query | Ignore L2; run normal agentic loop from scratch |
1366
+
1367
+
**Security:**`updated_params` from the LLM is validated against the endpoint's param
1368
+
schema — keys not in the schema are silently dropped to prevent injection.
1369
+
1370
+
**Fail-open:** any exception returns `{follow_up_type: "new_intent", updated_params: {}}`
1371
+
so the user is never blocked.
1372
+
1373
+
---
1374
+
1375
+
### Param Seeding
1376
+
1377
+
When the L2 `param_update` path finds that merged params are still incomplete,
1378
+
it sets `context["seeded_params"] = merged` before falling through to the agentic loop.
1379
+
1380
+
`AgenticLoop.run_turn()` and `stream_run_turn()` accept an optional `seeded_params` argument.
1381
+
On turn 0, the seeds are merged into `collected_params`**before** any extraction runs:
|`ATC_RESPONSE_CACHE_ENABLED`|`true` (env) | Master kill-switch — disables all reads and writes when `false`|
1428
+
1429
+
---
1430
+
1431
+
### Cache Component Reference
1432
+
1433
+
| Class / File | Responsibility |
1434
+
|---|---|
1435
+
|`ATCCacheStore` ([src/utils/atc_cache_store.py](../src/utils/atc_cache_store.py)) | All Redis operations for L1 and L2; param normalisation and hashing |
1436
+
|`FollowUpDetectorModule` ([src/tool_classifier/follow_up_detector.py](../src/tool_classifier/follow_up_detector.py)) | DSPy classifier for follow-up type detection |
1437
+
|`LastCallContext` ([src/models/session_models.py](../src/models/session_models.py)) | Pydantic model stored in L2 |
1438
+
|`_compute_loop_step` ([src/tool_classifier/workflows/api_tool_workflow.py](../src/tool_classifier/workflows/api_tool_workflow.py)) | Where L1 + L2 are read and routing decisions are made |
1439
+
|`_execute_api_and_format` / `_stream_api_and_format`| Where L1 + L2 are written after single-intent calls |
1440
+
|`_execute_multi_api_and_format` / `_stream_multi_api_and_format`| Where L1 + L2 are written after parallel calls |
1441
+
|`ToolClassifier.classify` ([src/tool_classifier/classifier.py](../src/tool_classifier/classifier.py)) | L2 invalidation on intent switch |
1442
+
1443
+
---
1444
+
1445
+
### End-to-End Cache Example
1446
+
1447
+
```
1448
+
Turn 1 — "What are public holidays in Estonia in 2026?"
Copy file name to clipboardExpand all lines: pyproject.toml
+1Lines changed: 1 addition & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -117,6 +117,7 @@ unfixable = []
117
117
118
118
119
119
"src/utils/api_tool_session_store.py" = ["ANN401"] # Dynamic Pydantic model field updates via **kwargs
120
+
"src/utils/atc_cache_store.py" = ["ANN401"] # raw_response / return type are parsed JSON (dict or list) — Any is the correct annotation
120
121
"src/tool_classifier/workflows/api_tool_workflow.py" = ["ANN401", "N815"] # Dynamic guardrails adapter + orchestration service Any types; camelCase _MinimalRequest field for API contract
0 commit comments