End-to-end practice—from knowledge base creation to multi-turn API calls—demonstrating how to pass tool call history to avoid redundant retrieval
This guide demonstrates how to pass tool call history across multi-turn conversations, enabling the model to answer continuously based on prior retrieval results and avoid redundant searches. All steps use the same sample document set for reproducible results.
Principle: Why Context Is Lost
RAG Agents are stateless services: each request is independent, and the server does not retain conversation context. A single response typically involves multiple stages:
If only textual history (user query + assistant reply) is passed in the next turn, the model cannot see intermediate tool calls or know what has already been retrieved—leading to redundant retrieval of the same question. This wastes tokens and may yield inconsistent answers.
The correct approach is to include the full tool call chain (assistant.tool_calls + corresponding tool return results) as part of the message history, allowing the model to build upon existing retrieval results.
Prepare Sample Documents
This guide uses the Bailian Technical Documentation Sample, containing 92 official technical documents from the Bailian platform (API usage, model descriptions, best practices, etc.). Download, extract, and upload them to your knowledge base to begin. For demonstration, we’ll use the document “First-Time Calling of Qwen API” to illustrate multi-turn follow-up:
- Turn 1: “How do I call the Qwen API?” (triggers multi-step tool-based retrieval)
- Turn 2: “How do I configure my API Key as an environment variable?” (verifies whether history is reused and redundant retrieval is avoided)
End-to-End Workflow
1. Create a Knowledge Base and Upload Sample Documents
- Go to Data Ingestion → Knowledge Management and click Create Knowledge Base
- Select Document Search as the knowledge base type and Basic Document Q&A as the use case
- Upload the sample documents, complete chunking and vectorization, and wait until the document status changes to Parsing Completed
2. Create a Knowledge Q&A Service and Bind the Knowledge Base
- Go to Knowledge Services → Knowledge Q&A and click Create Q&A Service
- Enter a service name and proceed to the configuration page after successful creation
- Click + Add, then bind the knowledge base created in Step 1
- Select a generation model and retrieval mode (Multi-turn Intelligent Retrieval is recommended for multi-turn conversations), then click Publish
3. Retrieve API Parameters
From the Q&A service list, click API Debugging for your target service to obtain the following parameters:
| Parameter | Where to Find It | Example |
|---|---|---|
| endpoint | Interface URL on the API Debugging page; {workspaceId} is your workspace ID | https://{workspaceId}.cn-beijing.maas.aliyuncs.com/api/v2/apps/knowledge/chat |
| agent_id | Request parameter on the API Debugging page—the Q&A service ID | aid-xxxxxxxx |
| API Key | Created on the Settings → API Key page | sk-xxxxxxxx |
agent_id uniquely identifies a knowledge Q&A service—each service corresponds to one agent_id.4. First Turn API Call
Use the retrieved parameters to make your first request. The request structure is as follows:
5. Second Turn: Follow-Up with Full Tool History
For the second query—“How do I configure my API Key as an environment variable?”—pass the full history from Turn 1, including the tool call chain:
6. Effect Comparison
Using the “First-Time Calling of Qwen API” document as a test case: After Turn 1 (“How do I call the Qwen API?”), Turn 2 asks “How do I configure my API Key as an environment variable?” Here's how two history-passing strategies compare:
| Full Tool History Passed | Text-Only History Passed | |
|---|---|---|
| Tool Calls in Turn 2 | 0 — direct answer using prior results | Redundant retrieval (2 semantic_search calls observed) |
| Response Speed | Fast — no retrieval overhead | Slower — full retrieval re-executed |
| Answer Consistency | Consistent — based on identical retrieved chunks | Inconsistent — new retrieval may yield different chunks |
semantic_search → obtain_file) generating 6 history messages. When Turn 2 passed full history (7 messages), the model answered directly with 0 tool calls. With text-only history (3 messages), it triggered 2 new semantic_search calls. Exact round counts and message numbers depend on document structure and query matching—but the conclusion holds reliably: passing full history avoids redundant retrieval.
Passing full history increases input length (including tool outputs), but eliminates retrieval overhead. Text-only history yields shorter inputs yet incurs full retrieval latency. While trade-offs exist, passing full history delivers superior speed and consistency for multi-turn follow-ups.
Extracting Tool History from SSE Stream
Agent streaming uses Server-Sent Events (SSE). Each data frame is data:{...}, and the relevant path is obj.output.choices[0].message. The field message.extra.step_change marks state transitions—focus only on three event types:
| Event | Identification Condition | Data to Extract |
|---|---|---|
| Tool Call | step_change == "tool_calling" | tool_calls array (all parallel calls in this round) |
| Tool Return | step_change == "tool_return" and role == "tool" | content + tool_call_id |
| Final Answer | Between generation_start and generation_end | Accumulate all content fragments |
State Flow
The Agent may perform multiple tool-call rounds until sufficient information is gathered before generating the final answer.
Message Format and Key Rules
Extracted history messages must be ordered by time and conform to these roles and fields:
| Role | Required Fields | Description |
|---|---|---|
user | role, content | User input |
assistant (tool call) | role, content (empty), tool_calls | All parallel tool calls in this round; function.arguments is a JSON string |
tool | role, content, tool_call_id | Tool result; tool_call_id must match tool_calls[].id |
assistant (final answer) | role, content | Model’s final answer |
assistant → tool pattern. Each new turn starts with a fresh user message and repeats the full flow—including all prior history.
Complete Python Implementation
The code below implements the full workflow: extracting history from SSE, auto-passing it across turns, and comparing outcomes. It’s tested against the sample documents and prompts used in this guide.
Configuration
Extract Tool History from SSE Stream
Multi-Turn Conversation Manager
Effect Comparison: Full History vs. Text-Only History
Important Notes
Controlling History Length
Tool return content can be lengthy; cumulative history may exceed context limits. Recommended mitigation strategies:
- Truncate older tool returns: Keep full tool content only for the most recent K rounds; truncate earlier
toolmessages to first N characters - Sliding window: For histories >3 turns, replace earlier tool results with summaries
Content Not Required in History
| Content | Reason |
|---|---|
reasoning_content | Internal reasoning—useful for UI display, but unnecessary for re-injection |
extra field | Streaming protocol metadata—used only for client-side rendering |
usage | Token usage stats—relevant only for monitoring and billing |
tool_call_chunks | Streaming fragments—already merged into tool_calls |
Common Questions
What happens if I omit tool call history?The model loses retrieval context and redundantly re-searches identical queries—degrading answer quality. Real-world test: full history enables zero tool calls on follow-up; omission triggers full re-retrieval. Can I skip intermediate tool calls and send only the final answer?
It works functionally but is strongly discouraged. Sending only
user + assistant(final answer) strips the model of memory about knowledge base content, significantly weakening follow-up performance.
Does order matter for parallel tool returns?Order doesn’t affect functionality—the model matches results via
tool_call_id. However, all tool messages must appear immediately after their corresponding assistant(tool_calls) message.