feat(tool): implement tool memory management and mock search tools- Added RetrieveToolMemoryOp for retrieving tool memory guidelines- Implemented LLM-based mock search tools with configurable complexity levels

- Enhanced tool call result parsing with improved scoring logic (0.0 or 1.0)
- Updated tool memory schema to reflect binary success/failure scoring
- Added deterministic behavior support via seed configuration in mock tools
- Improved evaluation prompts to focus on result quality over success flags
- Extended README with tool memory documentation and usage examples- Added utility functions for generating mock tool call results
- Removed deprecated test file for UseMockSearchOp- Updated default configurations to include use_mock_search operation- Bumped version to0.1.10 and updated flowllm dependency requirement
- Moved deprecation warnings to main init file
- Simplified tool memory summary formatting by removing redundant statistics
- Fixed tool call result processing to handle multiple tool names concurrently
This commit is contained in:
jinli.yl 2025-10-21 21:03:14 +08:00
parent 12f0b7d124
commit 49819a8e54
23 changed files with 2386 additions and 2773 deletions

171
README.md
View file

@ -21,15 +21,16 @@ ReMe provides AI agents with a unified memory system—enabling the ability to e
users, tasks, and agents.
```
Personal Memory + Task Memory = Agent Memory
Personal Memory + Task Memory + Tool Memory = Agent Memory
```
Personal memory helps "**understand user preferences**", while task memory helps agents "**perform better**".
Personal memory helps "**understand user preferences**", task memory helps agents "**perform better**", and tool memory enables "**smarter tool usage**".
---
## 📰 Latest Updates
- **[2025-10]** 🔧 Tool Memory support is now available! Enables data-driven tool selection and parameter optimization through historical performance tracking. Check out the [Tool Memory Guide](docs/tool_memory/tool_memory.md) and [benchmark results](docs/tool_memory/tool_bench.md).
- **[2025-09-25]** 🎉 ReMe is exploring the directions of tool memory and Personal Memory Application/Agent.
- **[2025-09]** 🎉 ReMe v0.1.9 has been officially released, adding support for asynchronous operations. It has also been
integrated into the memory service of agentscope-runtime.
@ -54,7 +55,7 @@ Personal memory helps "**understand user preferences**", while task memory helps
<img src="docs/figure/reme_structure.jpg" alt="ReMe Logo" width="100%">
</p>
ReMe integrates two complementary memory capabilities:
ReMe integrates three complementary memory capabilities:
#### 🧠 **Task Memory/Experience**
@ -78,6 +79,17 @@ Contextualized memory for specific users
Learn more about how to use personal memory from [personal memory](docs/personal_memory/personal_memory.md)
#### 🔧 **Tool Memory**
Data-driven tool selection and usage optimization
- **Historical Performance Tracking**: Success rates, execution times, and token costs from real usage
- **LLM-as-Judge Evaluation**: Qualitative insights on why tools succeed or fail
- **Parameter Optimization**: Learn optimal parameter configurations from successful calls
- **Dynamic Guidelines**: Transform static tool descriptions into living, learned manuals
Learn more about how to use tool memory from [tool memory](docs/tool_memory/tool_memory.md)
---
## 🛠️ Installation
@ -316,6 +328,140 @@ fetch("http://localhost:8002/retrieve_personal_memory", {
</details>
#### Tool Memory Management
```python
import requests
# Record tool execution results
response = requests.post("http://localhost:8002/add_tool_call_result", json={
"workspace_id": "tool_workspace",
"tool_call_results": [
{
"create_time": "2025-10-21 10:30:00",
"tool_name": "web_search",
"input": {"query": "Python asyncio tutorial", "max_results": 10},
"output": "Found 10 relevant results...",
"token_cost": 150,
"success": True,
"time_cost": 2.3
}
]
})
# Generate usage guidelines from history
response = requests.post("http://localhost:8002/summary_tool_memory", json={
"workspace_id": "tool_workspace",
"tool_names": "web_search"
})
# Retrieve tool guidelines before use
response = requests.post("http://localhost:8002/retrieve_tool_memory", json={
"workspace_id": "tool_workspace",
"tool_names": "web_search"
})
```
<details>
<summary>curl version</summary>
```bash
# Record tool execution results
curl -X POST http://localhost:8002/add_tool_call_result \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "tool_workspace",
"tool_call_results": [
{
"create_time": "2025-10-21 10:30:00",
"tool_name": "web_search",
"input": {"query": "Python asyncio tutorial", "max_results": 10},
"output": "Found 10 relevant results...",
"token_cost": 150,
"success": true,
"time_cost": 2.3
}
]
}'
# Generate usage guidelines from history
curl -X POST http://localhost:8002/summary_tool_memory \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "tool_workspace",
"tool_names": "web_search"
}'
# Retrieve tool guidelines before use
curl -X POST http://localhost:8002/retrieve_tool_memory \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "tool_workspace",
"tool_names": "web_search"
}'
```
</details>
<details>
<summary>Node.js version</summary>
```javascript
// Record tool execution results
fetch("http://localhost:8002/add_tool_call_result", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspace_id: "tool_workspace",
tool_call_results: [
{
create_time: "2025-10-21 10:30:00",
tool_name: "web_search",
input: {query: "Python asyncio tutorial", max_results: 10},
output: "Found 10 relevant results...",
token_cost: 150,
success: true,
time_cost: 2.3
}
]
})
})
.then(response => response.json())
.then(data => console.log(data));
// Generate usage guidelines from history
fetch("http://localhost:8002/summary_tool_memory", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspace_id: "tool_workspace",
tool_names: "web_search"
})
})
.then(response => response.json())
.then(data => console.log(data));
// Retrieve tool guidelines before use
fetch("http://localhost:8002/retrieve_tool_memory", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspace_id: "tool_workspace",
tool_names: "web_search"
})
})
.then(response => response.json())
.then(data => console.log(data));
```
</details>
---
## 📦 Ready-to-Use Libraries
@ -387,12 +533,29 @@ We tested ReMe on BFCL-V3 multi-turn-base (randomly split 50train/150val) using
| without ReMe | 0.2472 | 0.2733 | 0.2922 |
| with ReMe | 0.3061 **(+5.89%)** | 0.3500 **(+7.67%)** | 0.3888 **(+9.66%)** |
### 🛠️ [Tool Memory Benchmark](docs/tool_memory/tool_bench.md)
We evaluated Tool Memory effectiveness using a controlled benchmark with three mock search tools using Qwen3-30B-Instruct:
| Scenario | Avg Score | Improvement |
|-----------------------|-----------|--------------------|
| Train (No Memory) | 0.650 | - |
| Test (No Memory) | 0.672 | Baseline |
| **Test (With Memory)** | **0.772** | **+14.88%** |
**Key Findings:**
- Tool Memory enables data-driven tool selection based on historical performance
- Success rates improved by ~15% with learned parameter configurations
- Consistent improvement across all epochs (9.90% → 17.39% → 17.13%)
You can find more details in [tool_bench.md](docs/tool_memory/tool_bench.md) and the implementation at [run_reme_tool_bench.py](cookbook/tool_memory/run_reme_tool_bench.py).
## 📚 Resources
- **[Quick Start](./cookbook/simple_demo)**: Get started quickly with practical examples
- **[Vector Storage Setup](docs/vector_store_api_guide.md)**: Configure local/vector databases and usage
- **[MCP Guide](docs/mcp_quick_start.md)**: Create MCP services
- **[personal memory](docs/personal_memory)** & **[task memory](docs/task_memory)** : Operators used in personal memory and task memory, You can modify the config to customize the pipelines.
- **[Personal Memory](docs/personal_memory)**, **[Task Memory](docs/task_memory)** & **[Tool Memory](docs/tool_memory)**: Operators used in personal memory, task memory and tool memory. You can modify the config to customize the pipelines.
- **[Example Collection](./cookbook)**: Real use cases and best practices
---

View file

@ -0,0 +1,238 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Tool Memory Demo - 展示工具记忆的完整生命周期"""
import json
import time
from typing import List, Dict, Any, Optional
import requests
from dotenv import load_dotenv
from reme_ai.utils.tool_memory_utils import create_mock_tool_call_results
load_dotenv()
BASE_URL = "http://0.0.0.0:8002/"
WORKSPACE_ID = "test_tool_memory_workspace"
def api_call(endpoint: str, data: dict) -> Optional[Dict[str, Any]]:
"""统一的API调用处理"""
response = requests.post(f"{BASE_URL}{endpoint}", json=data)
if response.status_code != 200:
print(f"Error: {response.status_code} - {response.text}")
return None
return response.json()
def delete_workspace() -> None:
"""删除工作空间
curl example:
curl -X POST http://0.0.0.0:8002/vector_store \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "test_tool_memory_workspace",
"action": "delete"
}'
"""
result = api_call("vector_store", {"workspace_id": WORKSPACE_ID, "action": "delete"})
if result:
print(f"✓ Workspace '{WORKSPACE_ID}' deleted")
def add_tool_call_results(tool_call_results: List[Dict[str, Any]]) -> bool:
"""添加工具调用结果到记忆库
curl example:
curl -X POST http://0.0.0.0:8002/add_tool_call_result \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "test_tool_memory_workspace",
"tool_call_results": [
{
"tool_name": "web_search",
"tool_input": "Python tutorials",
"tool_output": "Found 100 results about Python",
"execution_time": 0.5
}
]
}'
"""
# 统计不同的工具
tool_names = set(r.get("tool_name") for r in tool_call_results)
print(f"\n[ADD] {len(tool_call_results)} results for {len(tool_names)} tools: {', '.join(sorted(tool_names))}")
result = api_call("add_tool_call_result", {
"workspace_id": WORKSPACE_ID,
"tool_call_results": tool_call_results
})
if result:
memory_list = result.get("metadata", {}).get("memory_list", [])
print(f"✓ Added successfully, created/updated {len(memory_list)} tool memories")
return True
return False
def summarize_tool_memory(tool_names: str) -> Optional[Dict[str, Any]]:
"""总结工具使用模式
curl example:
curl -X POST http://0.0.0.0:8002/summary_tool_memory \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "test_tool_memory_workspace",
"tool_names": "web_search,database_query"
}'
"""
print(f"\n[SUMMARIZE] {tool_names}")
result = api_call("summary_tool_memory", {
"workspace_id": WORKSPACE_ID,
"tool_names": tool_names
})
if result:
memory_list = result.get("metadata", {}).get("memory_list", [])
print(f"✓ Summarized {len(memory_list)} tool memories")
for memory in memory_list:
print(f"\n{'=' * 60}")
print(f"Tool: {memory.get('when_to_use', 'N/A')}")
print(f"{'=' * 60}")
print(memory.get('content', 'No content'))
return result
def retrieve_tool_memory(tool_names: str, save_to_file: bool = False) -> str:
"""检索工具记忆
curl example:
curl -X POST http://0.0.0.0:8002/retrieve_tool_memory \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "test_tool_memory_workspace",
"tool_names": "web_search"
}'
"""
print(f"\n[RETRIEVE] {tool_names}")
result = api_call("retrieve_tool_memory", {
"workspace_id": WORKSPACE_ID,
"tool_names": tool_names
})
if not result:
return ""
memory_list = result.get("metadata", {}).get("memory_list", [])
if not memory_list:
print("No memories found")
return ""
print(f"✓ Retrieved {len(memory_list)} memories")
formatted_memories = []
for memory in memory_list:
content = f"\nTool: {memory.get('when_to_use', 'N/A')}\n" \
f"Calls: {len(memory.get('tool_call_results', []))}\n" \
f"{'-' * 60}\n{memory.get('content', 'No content')}\n"
formatted_memories.append(content)
print(content)
if save_to_file:
with open("tool_memory.json", "w") as f:
json.dump(memory_list, f, indent=2, ensure_ascii=False)
print("✓ Saved to tool_memory.json")
return "\n".join(formatted_memories)
def dump_memory(path: str = "./") -> None:
"""导出记忆到磁盘
curl example:
curl -X POST http://0.0.0.0:8002/vector_store \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "test_tool_memory_workspace",
"action": "dump",
"path": "./"
}'
"""
result = api_call("vector_store", {
"workspace_id": WORKSPACE_ID,
"action": "dump",
"path": path
})
if result:
print(f"✓ Memory dumped to {path}")
def load_memory(path: str = "./") -> None:
"""从磁盘加载记忆
curl example:
curl -X POST http://0.0.0.0:8002/vector_store \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "test_tool_memory_workspace",
"action": "load",
"path": "./"
}'
"""
result = api_call("vector_store", {
"workspace_id": WORKSPACE_ID,
"action": "load",
"path": path
})
if result:
print(f"✓ Memory loaded from {path}")
def main() -> None:
# 1. 清理工作空间
print("\n[1] Cleaning workspace...")
delete_workspace()
time.sleep(1)
# 2. 创建和添加模拟工具调用结果
print("\n[2] Adding mock tool call results...")
tools_to_test = [
("web_search", 30),
("database_query", 22),
("file_processor", 18)
]
# 收集所有工具的结果,然后一次性添加
all_mock_results = []
for tool_name, count in tools_to_test:
mock_results = create_mock_tool_call_results(tool_name, count)
all_mock_results.extend(mock_results)
if not add_tool_call_results(all_mock_results):
print("✗ Failed to add results")
else:
time.sleep(1)
# 3. 总结工具记忆
print("\n[3] Summarizing tool memories...")
all_tool_names = ",".join([tool[0] for tool in tools_to_test])
summarize_tool_memory(all_tool_names)
time.sleep(1)
# 4. 检索工具记忆
print("\n[4] Retrieving tool memories...")
for tool_name, _ in tools_to_test:
retrieve_tool_memory(tool_name, save_to_file=True)
time.sleep(0.5)
# 5. 测试记忆持久化
print("\n[5] Testing memory persistence...")
dump_memory()
load_memory()
print("\n" + "=" * 60)
print("DEMO COMPLETE ✓")
print("=" * 60)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,633 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Tool Memory Benchmark Script
评估工具记忆在不同场景下的效果包括有记忆和无记忆的对比
Dependencies:
pip install requests python-dotenv loguru tabulate
"""
import json
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import List, Dict, Any, Optional
import requests
from dotenv import load_dotenv
from loguru import logger
from tabulate import tabulate
from reme_ai.schema.memory import ToolCallResult, ToolMemory
load_dotenv()
BASE_URL = "http://0.0.0.0:8002/"
TRAIN_WORKSPACE = "train_tool_workspace"
TEST_WORKSPACE = "test_tool_workspace"
class BenchmarkStats:
"""统计数据收集器"""
def __init__(self, name: str):
self.name = name
self.total_count = 0
self.scores = []
def add_result(self, result: Dict[str, Any]):
"""添加一个工具调用结果
Note:
- score: Quality/relevance of the result (0.0 or 1.0)
"""
self.total_count += 1
# Collect score
score = result.get("score", 0)
self.scores.append(score)
def get_summary(self) -> Dict[str, Any]:
"""获取统计摘要"""
if self.total_count == 0:
return {
"name": self.name,
"total_calls": 0,
"avg_score": 0.0
}
return {
"name": self.name,
"total_calls": self.total_count,
"avg_score": round(sum(self.scores) / len(self.scores), 3)
}
def api_call(endpoint: str, data: dict) -> Optional[Dict[str, Any]]:
"""统一的API调用处理"""
try:
response = requests.post(f"{BASE_URL}{endpoint}", json=data, timeout=120)
if response.status_code != 200:
logger.error(f"API Error: {response.status_code} - {response.text}")
return None
return response.json()
except Exception as e:
logger.error(f"API call failed: {e}")
return None
def delete_workspace(workspace_id: str) -> bool:
"""删除工作空间"""
logger.info(f"Deleting workspace: {workspace_id}")
result = api_call("vector_store", {"workspace_id": workspace_id, "action": "delete"})
return result is not None
def load_queries(query_file: str = "query.json") -> Dict[str, Any]:
"""加载查询数据"""
query_path = Path(__file__).parent / query_file
with open(query_path, 'r', encoding='utf-8') as f:
return json.load(f)
def run_use_mock_search(workspace_id: str, queries: List[str], prompt_template: str = "") -> List[ToolCallResult]:
"""运行use_mock_search并收集结果支持并发
Args:
workspace_id: 工作空间ID
queries: 查询列表
prompt_template: 提示模板
Returns:
工具调用结果列表
"""
logger.info(f"Running use_mock_search on {workspace_id} with {len(queries)} queries (max concurrency: 4)")
results: List[ToolCallResult] = []
def process_single_query(idx: int, query: str) -> Optional[ToolCallResult]:
"""处理单个查询"""
logger.info(f"[{idx + 1}/{len(queries)}] Processing: {query}")
# 提交之前sleep 1秒
time.sleep(1)
result = api_call("use_mock_search", {
"workspace_id": workspace_id,
"query": prompt_template.format(query=query),
})
if result:
tool_call_result = result.get("answer")
return ToolCallResult(**json.loads(tool_call_result))
else:
logger.warning(f"No result for query: {query}")
return None
# 使用线程池并发处理最大并发数为4
with ThreadPoolExecutor(max_workers=4) as executor:
# 提交所有任务
future_to_query = {
executor.submit(process_single_query, idx, query): (idx, query)
for idx, query in enumerate(queries)
}
# 收集结果(按完成顺序)
completed_results = []
for future in as_completed(future_to_query):
idx, query = future_to_query[future]
try:
result = future.result()
if result:
completed_results.append((idx, result))
except Exception as e:
logger.error(f"Error processing query [{idx + 1}]: {query}, error: {e}")
# 按原始顺序排序结果
completed_results.sort(key=lambda x: x[0])
results = [result for _, result in completed_results]
logger.info(f"Collected {len(results)} results out of {len(queries)} queries")
return results
def add_tool_call_results(workspace_id: str, results: List[ToolCallResult]) -> List[ToolCallResult]:
"""批量添加工具调用结果到记忆库,并返回带评分的结果
Args:
workspace_id: 工作空间ID
results: 工具调用结果列表
Returns:
从API返回的memory_list中提取的带评分的ToolCallResult列表
"""
if not results:
logger.warning("No results to add")
return []
# 转换为字典用于API调用
tool_call_results = [result.model_dump() for result in results]
logger.info(f"Adding tool call results to {workspace_id}: {len(tool_call_results)} results")
# 统一调用API让后端自动按tool_name分组处理
api_result = api_call("add_tool_call_result", {
"workspace_id": workspace_id,
"tool_call_results": tool_call_results
})
if not api_result:
logger.error("Failed to add results")
return []
# 收集所有带评分的结果
all_scored_results: List[ToolCallResult] = []
# 解析返回的memory_list可能包含多个工具的记忆
memory_list = api_result.get("metadata", {}).get("memory_list", [])
logger.info(f"Received {len(memory_list)} tool memories from API")
for memory_dict in memory_list:
tool_memory = ToolMemory(**memory_dict)
tool_name = tool_memory.when_to_use
scored_results = tool_memory.tool_call_results
all_scored_results.extend(scored_results)
logger.info(f"Extracted {len(scored_results)} scored results from {tool_name}")
# 打印一些评分示例
for idx, result in enumerate(scored_results[:3]):
logger.info(f" Result #{idx + 1}: score={result.score}, success={result.success}")
logger.info(f"Total scored results collected: {len(all_scored_results)}")
return all_scored_results
def summarize_tool_memory(workspace_id: str, tool_names: str) -> bool:
"""总结工具记忆"""
logger.info(f"Summarizing tool memory for {workspace_id}: {tool_names}")
result = api_call("summary_tool_memory", {
"workspace_id": workspace_id,
"tool_names": tool_names
})
if result:
memory_list = result.get("metadata", {}).get("memory_list", [])
logger.info(f"Summarized {len(memory_list)} tool memories")
return True
return False
def retrieve_tool_memory(workspace_id: str, tool_names: str) -> str:
"""检索工具记忆并返回格式化的内容
Args:
workspace_id: 工作空间ID
tool_names: 逗号分隔的工具名称
Returns:
格式化的工具记忆内容每个工具名称作为一级markdown标题
"""
logger.info(f"Retrieving tool memory for {workspace_id}: {tool_names}")
result = api_call("retrieve_tool_memory", {
"workspace_id": workspace_id,
"tool_names": tool_names
})
if not result:
logger.error("Failed to retrieve tool memory")
return ""
memory_list = result.get("metadata", {}).get("memory_list", [])
logger.info(f"Retrieved {len(memory_list)} tool memories")
# 提取每个工具记忆的content字段并格式化为markdown
formatted_contents = []
for memory_dict in memory_list:
tool_memory = ToolMemory(**memory_dict)
if tool_memory.content:
# 使用工具名称作为一级markdown标题
tool_name = tool_memory.when_to_use or "Unknown Tool"
formatted_section = f"# {tool_name}\n\n{tool_memory.content}"
formatted_contents.append(formatted_section)
logger.info(f"Retrieved content for tool: {tool_name}, "
f"content_length={len(tool_memory.content)}")
# 用两个换行符分隔不同工具的记忆
joined_content = "\n\n".join(formatted_contents)
logger.info(f"Total content length: {len(joined_content)}")
return joined_content
def collect_statistics(results: List[ToolCallResult], stats: BenchmarkStats) -> None:
"""从结果列表中收集统计数据"""
for result in results:
# 转换为字典用于统计
result_dict = result.model_dump() if hasattr(result, 'model_dump') else result
stats.add_result(result_dict)
def print_comparison_table(stats_list: List[BenchmarkStats]) -> None:
"""打印对比表格"""
headers = ["Scenario", "Total Calls", "Avg Score"]
rows = []
for stats in stats_list:
summary = stats.get_summary()
rows.append([
summary["name"],
summary["total_calls"],
summary["avg_score"]
])
print("\n" + "=" * 100)
print("BENCHMARK RESULTS COMPARISON")
print("=" * 100)
print("Note: Avg Score = average quality score")
print(tabulate(rows, headers=headers, tablefmt="grid"))
print("=" * 100)
def calculate_improvements(baseline_stats: BenchmarkStats, improved_stats: BenchmarkStats) -> Dict[str, float]:
"""计算改进百分比"""
baseline = baseline_stats.get_summary()
improved = improved_stats.get_summary()
improvements = {}
# 平均分数改进(相对提升百分比)
if baseline["avg_score"] > 0:
improvements["avg_score"] = ((improved["avg_score"] - baseline["avg_score"])
/ baseline["avg_score"] * 100)
else:
improvements["avg_score"] = 0.0
return improvements
def print_improvements(improvements: Dict[str, float]) -> None:
"""打印改进情况"""
print("\n" + "=" * 100)
print("IMPROVEMENTS WITH TOOL MEMORY (Baseline: Test without memory)")
print("=" * 100)
metric_labels = {
"avg_score": "Average Score"
}
for metric, improvement in improvements.items():
label = metric_labels.get(metric, metric)
direction = "" if improvement > 0 else ""
print(f"{label:25s}: {improvement:+7.2f}% {direction}")
print("=" * 100)
def save_results(results: Dict[str, Any], filename: str = "benchmark_results.json") -> None:
"""保存结果到文件"""
output_path = Path(__file__).parent / filename
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(results, f, indent=2, ensure_ascii=False)
logger.info(f"Results saved to {output_path}")
def run_single_epoch(epoch_num: int, train_queries: List[str], test_queries: List[str]) -> Dict[str, Any]:
"""运行单个epoch的benchmark
Args:
epoch_num: epoch编号从1开始
train_queries: 训练查询列表
test_queries: 测试查询列表
Returns:
包含该epoch统计结果的字典
"""
logger.info("\n" + "=" * 100)
logger.info(f"EPOCH {epoch_num} - START")
logger.info("=" * 100)
# 初始化统计收集器
train_no_memory_stats = BenchmarkStats(f"Epoch{epoch_num} - Train (No Memory)")
test_no_memory_stats = BenchmarkStats(f"Epoch{epoch_num} - Test (No Memory)")
test_with_memory_stats = BenchmarkStats(f"Epoch{epoch_num} - Test (With Memory)")
all_results = {}
# ==================== 步骤1: 无记忆在train上的效果 ====================
print("\n" + "=" * 100)
print(f"[EPOCH {epoch_num}] [STEP 1/5] Running on TRAIN without memory...")
print("=" * 100)
logger.info("Deleting workspace and starting fresh...")
delete_workspace(TRAIN_WORKSPACE)
time.sleep(2)
prompt_template = "必须选择一个工具来回答问题\n 问题\n{query}"
train_results_no_memory = run_use_mock_search(TRAIN_WORKSPACE, train_queries, prompt_template)
# 添加结果到记忆库并获取带评分的结果
train_scored_results = add_tool_call_results(TRAIN_WORKSPACE, train_results_no_memory)
time.sleep(2)
# 使用带评分的结果进行统计(如果有的话)
if train_scored_results:
logger.info(f"Using {len(train_scored_results)} scored results for statistics")
all_results["train_no_memory"] = train_scored_results
collect_statistics(train_scored_results, train_no_memory_stats)
else:
logger.warning("No scored results returned, using original results")
all_results["train_no_memory"] = train_results_no_memory
collect_statistics(train_results_no_memory, train_no_memory_stats)
print(f"✓ Train (no memory) completed: {len(train_results_no_memory)}/{len(train_queries)} results collected")
summary = train_no_memory_stats.get_summary()
print(f" Avg Score: {summary['avg_score']:.3f}")
# ==================== 步骤2: 无记忆在test上的效果 ====================
print("\n" + "=" * 100)
print(f"[EPOCH {epoch_num}] [STEP 2/5] Running on TEST without memory...")
print("=" * 100)
logger.info("Deleting workspace and starting fresh...")
delete_workspace(TEST_WORKSPACE)
time.sleep(2)
prompt_template = "必须选择一个工具来回答问题\n 问题\n{query}"
test_results_no_memory = run_use_mock_search(TEST_WORKSPACE, test_queries, prompt_template)
# 添加结果到记忆库并获取带评分的结果
# 注意这些结果会作为TEST_WORKSPACE的初始记忆在步骤4会被复用
test_scored_results_no_memory = add_tool_call_results(TEST_WORKSPACE, test_results_no_memory)
time.sleep(2)
# 使用带评分的结果进行统计(如果有的话)
if test_scored_results_no_memory:
logger.info(f"Using {len(test_scored_results_no_memory)} scored results for statistics")
all_results["test_no_memory"] = test_scored_results_no_memory
collect_statistics(test_scored_results_no_memory, test_no_memory_stats)
else:
logger.warning("No scored results returned, using original results")
all_results["test_no_memory"] = test_results_no_memory
collect_statistics(test_results_no_memory, test_no_memory_stats)
print(f"✓ Test (no memory) completed: {len(test_results_no_memory)}/{len(test_queries)} results collected")
summary = test_no_memory_stats.get_summary()
print(f" Avg Score: {summary['avg_score']:.3f}")
# ==================== 步骤3: 总结train的工具记忆 ====================
print("\n" + "=" * 100)
print(f"[EPOCH {epoch_num}] [STEP 3/5] Summarizing tool memory from TRAIN...")
print("=" * 100)
# 获取所有工具名称(使用带评分的结果)
tool_names_set = set()
results_to_use = train_scored_results if train_scored_results else train_results_no_memory
for result in results_to_use:
tool_name = result.tool_name if hasattr(result, 'tool_name') else None
if tool_name:
tool_names_set.add(tool_name)
tool_names_str = ",".join(sorted(tool_names_set))
print(f"Tools to summarize: {tool_names_str}")
success = summarize_tool_memory(TRAIN_WORKSPACE, tool_names_str)
if not success:
logger.error("Failed to summarize tool memory")
return {}
time.sleep(3)
print("✓ Tool memory summarized successfully")
# 检索工具记忆内容
memories = retrieve_tool_memory(TRAIN_WORKSPACE, tool_names_str)
if not memories:
logger.error("Failed to retrieve tool memory content")
return {}
logger.info(f"Retrieved tool memory content, total length: {len(memories)}")
print("\n" + "-" * 100)
print("Retrieved Tool Memory Content:")
print("-" * 100)
print(memories)
print("-" * 100)
# ==================== 步骤4: 有记忆在test上的效果 ====================
print("\n" + "=" * 100)
print(f"[EPOCH {epoch_num}] [STEP 4/5] Running on TEST with memory (after clearing existing memory)...")
print("=" * 100)
# 先清理TEST_WORKSPACE中已有的记忆记录Step 2的60条结果
print("Deleting existing memory records from TEST workspace...")
delete_workspace(TEST_WORKSPACE)
time.sleep(2) # 等待删除完成
print("✓ TEST workspace memory cleared")
# 通过prompt注入train阶段总结的记忆测量记忆增强的效果
# 注意此时workspace是空的只通过prompt提供记忆信息
prompt_template = f"工具信息\n{memories}\n必须选择一个工具来回答问题\n 问题\n" + "{query}"
test_results_with_memory = run_use_mock_search(TEST_WORKSPACE, test_queries, prompt_template)
# 添加这些结果到记忆库并获取带评分的结果
# 此时workspace已清空返回的就是本次新增的60条结果
test_all_results_with_memory = add_tool_call_results(TEST_WORKSPACE, test_results_with_memory)
time.sleep(2)
# workspace已清空所有返回的结果都是本次新增的
if test_all_results_with_memory:
test_scored_results_with_memory = test_all_results_with_memory
logger.info(f"Using {len(test_scored_results_with_memory)} scored results for statistics")
all_results["test_with_memory"] = test_scored_results_with_memory
collect_statistics(test_scored_results_with_memory, test_with_memory_stats)
else:
logger.warning("No scored results returned, using original results")
all_results["test_with_memory"] = test_results_with_memory
collect_statistics(test_results_with_memory, test_with_memory_stats)
print(f"✓ Test (with memory) completed: {len(test_results_with_memory)}/{len(test_queries)} results collected")
summary = test_with_memory_stats.get_summary()
print(f" Avg Score: {summary['avg_score']:.3f}")
# ==================== 步骤5: 打印对比结果 ====================
print("\n" + "=" * 100)
print(f"[EPOCH {epoch_num}] [STEP 5/5] Generating comparison report and analysis...")
print("=" * 100)
# 打印统计表格
print_comparison_table([train_no_memory_stats, test_no_memory_stats, test_with_memory_stats])
# 计算并打印改进情况
improvements = calculate_improvements(test_no_memory_stats, test_with_memory_stats)
print_improvements(improvements)
logger.info(f"EPOCH {epoch_num} - COMPLETE")
return {
"epoch": epoch_num,
"statistics": {
"train_no_memory": train_no_memory_stats.get_summary(),
"test_no_memory": test_no_memory_stats.get_summary(),
"test_with_memory": test_with_memory_stats.get_summary()
},
"improvements": improvements
}
def main(test_mode: bool = False, run_epoch: int = 3):
"""主函数运行完整的benchmark流程
Args:
test_mode: 如果为True只使用每个难度级别的前3个查询进行快速测试
run_epoch: 运行的epoch数量默认为3
"""
logger.info("=" * 100)
logger.info("TOOL MEMORY BENCHMARK - START")
if test_mode:
logger.info("Running in TEST MODE (limited queries)")
logger.info(f"Total Epochs: {run_epoch}")
logger.info("=" * 100)
# 加载查询数据
queries_data = load_queries()
train_queries = []
test_queries = []
# 合并所有难度级别的查询
for difficulty in ["simple", "moderate", "complex"]:
train_data = queries_data["train"].get(difficulty, [])
test_data = queries_data["test"].get(difficulty, [])
if test_mode:
train_queries.extend(train_data[:5])
test_queries.extend(test_data[:5])
else:
train_queries.extend(train_data)
test_queries.extend(test_data)
logger.info(f"Loaded {len(train_queries)} train queries and {len(test_queries)} test queries")
# 运行多个epoch并收集结果
all_epoch_results = []
for epoch in range(1, run_epoch + 1):
epoch_result = run_single_epoch(epoch, train_queries, test_queries)
if epoch_result:
all_epoch_results.append(epoch_result)
else:
logger.error(f"Epoch {epoch} failed, skipping...")
# ==================== 计算多轮平均效果 ====================
if not all_epoch_results:
logger.error("No successful epochs, cannot calculate averages")
return
print("\n" + "=" * 100)
print("MULTI-EPOCH AVERAGE RESULTS")
print("=" * 100)
# 计算每个场景的平均分数
avg_train_no_memory = sum(e["statistics"]["train_no_memory"]["avg_score"] for e in all_epoch_results) / len(
all_epoch_results)
avg_test_no_memory = sum(e["statistics"]["test_no_memory"]["avg_score"] for e in all_epoch_results) / len(
all_epoch_results)
avg_test_with_memory = sum(e["statistics"]["test_with_memory"]["avg_score"] for e in all_epoch_results) / len(
all_epoch_results)
# 计算平均改进
avg_improvement = sum(e["improvements"]["avg_score"] for e in all_epoch_results) / len(all_epoch_results)
# 打印汇总表格
headers = ["Scenario", "Avg Score (across epochs)"]
rows = [
["Train (No Memory)", f"{avg_train_no_memory:.3f}"],
["Test (No Memory)", f"{avg_test_no_memory:.3f}"],
["Test (With Memory)", f"{avg_test_with_memory:.3f}"]
]
print(tabulate(rows, headers=headers, tablefmt="grid"))
print("\n" + "-" * 100)
print(f"Average Improvement (Test with memory vs without): {avg_improvement:+.2f}%")
print("-" * 100)
# 打印每个epoch的详细结果
print("\n" + "=" * 100)
print("PER-EPOCH BREAKDOWN")
print("=" * 100)
headers = ["Epoch", "Train (No Mem)", "Test (No Mem)", "Test (With Mem)", "Improvement %"]
rows = []
for e in all_epoch_results:
rows.append([
f"Epoch {e['epoch']}",
f"{e['statistics']['train_no_memory']['avg_score']:.3f}",
f"{e['statistics']['test_no_memory']['avg_score']:.3f}",
f"{e['statistics']['test_with_memory']['avg_score']:.3f}",
f"{e['improvements']['avg_score']:+.2f}%"
])
print(tabulate(rows, headers=headers, tablefmt="grid"))
# 保存最终结果
benchmark_summary = {
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
"total_epochs": run_epoch,
"successful_epochs": len(all_epoch_results),
"average_results": {
"train_no_memory": avg_train_no_memory,
"test_no_memory": avg_test_no_memory,
"test_with_memory": avg_test_with_memory,
"improvement": avg_improvement
},
"per_epoch_results": all_epoch_results
}
save_results(benchmark_summary, "tool_memory_benchmark_results.json")
logger.info("\n" + "=" * 100)
logger.info("TOOL MEMORY BENCHMARK - COMPLETE")
logger.info(f"Successfully completed {len(all_epoch_results)}/{run_epoch} epochs")
logger.info("=" * 100)
if __name__ == "__main__":
main(test_mode=False, run_epoch=3)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -12,537 +12,8 @@ Retrieves tool memories from the vector database based on tool names, providing
- Searches the vector store for exact tool name matches
- Validates that retrieved memories are of type "tool"
- Returns complete tool memories including usage guidelines and call history
- Provides detailed logging for debugging and monitoring
### Processing Flow
```mermaid
graph TB
A[Receive Tool Names] --> B[Validate Input]
B --> C[Split by Comma]
C --> D[Trim Whitespace]
D --> E[For Each Tool Name]
E --> F[Search Vector Store]
F --> G{Results Found?}
G -->|Yes| H[Get Top Result]
G -->|No| I[Log Warning: Not Found]
H --> J{Type = tool?}
J -->|Yes| K{Name Matches?}
J -->|No| L[Log Warning: Wrong Type]
K -->|Yes| M[Add to Results]
K -->|No| N[Log Warning: Name Mismatch]
I --> O[Continue Next Tool]
L --> O
N --> O
M --> O
O --> P{More Tools?}
P -->|Yes| E
P -->|No| Q{Any Matches?}
Q -->|Yes| R[Return Memory List]
Q -->|No| S[Return Empty]
```
1. **Input Validation**:
- Check if `tool_names` parameter is provided
- Return error if empty
- Log workspace_id and tool count
2. **Tool Name Processing**:
- Split input by comma delimiter
- Strip whitespace from each tool name
- Filter out empty strings
- Log the list of tools to retrieve
3. **Vector Store Search**:
- For each tool name, search with `top_k=1`
- Use tool name as the query (exact match preferred)
- Retrieve the top matching result
4. **Result Validation**:
- Verify result is of type `ToolMemory`
- Check that `when_to_use` field exactly matches tool name
- Log match details (memory_id, total_calls)
- Warn if no match or mismatch found
5. **Response Preparation**:
- Collect all matched tool memories
- Set success status based on matches found
- Return memory list in metadata
### Parameters
This operation has no configurable parameters. It uses the default vector store configuration.
### Input Schema
```yaml
input_schema:
tool_names:
type: string
description: "Comma-separated tool names (e.g., 'tool_name1,tool_name2')"
required: true
```
### Output Format
The operation sets the following in `context.response`:
```python
{
"success": True,
"answer": "Successfully retrieved 2 tool memories",
"metadata": {
"memory_list": [
{
"workspace_id": "demo_workspace",
"memory_id": "abc123def456",
"memory_type": "tool",
"when_to_use": "web_search",
"content": "Core Function: The web_search tool retrieves...",
"score": 0.85,
"time_created": "2025-10-15 10:00:00",
"time_modified": "2025-10-15 14:30:00",
"author": "qwen3-30b-a3b-instruct-2507",
"tool_call_results": [
{
"create_time": "2025-10-15 14:30:00",
"tool_name": "web_search",
"input": {...},
"output": "...",
"summary": "...",
"evaluation": "...",
"score": 1.0,
"success": True,
"time_cost": 2.3,
"token_cost": 150
}
],
"metadata": {}
}
]
}
}
```
### Usage Example
#### Basic Retrieval
```python
import requests
BASE_URL = "http://0.0.0.0:8002/"
WORKSPACE_ID = "demo_workspace"
# Retrieve memory for a single tool
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": "web_search"
}
)
result = response.json()
if result.get("success"):
memory_list = result.get("metadata", {}).get("memory_list", [])
for memory in memory_list:
print(f"Tool: {memory['when_to_use']}")
print(f"Guidelines:\n{memory['content']}")
print(f"Total Calls: {len(memory['tool_call_results'])}")
```
#### Multiple Tools Retrieval
```python
# Retrieve memories for multiple tools at once
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": "web_search,file_reader,api_caller,database_query"
}
)
result = response.json()
if result.get("success"):
memory_list = result.get("metadata", {}).get("memory_list", [])
print(f"Retrieved {len(memory_list)} tool memories")
for memory in memory_list:
print(f"\n{'='*60}")
print(f"Tool: {memory['when_to_use']}")
print(f"{'='*60}")
print(memory['content'])
```
#### Extracting Specific Information
```python
def get_tool_statistics(tool_name):
"""Get statistical information for a specific tool"""
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": tool_name
}
)
result = response.json()
if not result.get("success"):
return None
memory_list = result.get("metadata", {}).get("memory_list", [])
if not memory_list:
return None
memory = memory_list[0]
tool_calls = memory['tool_call_results']
# Calculate statistics
total_calls = len(tool_calls)
successful_calls = sum(1 for call in tool_calls if call['success'])
avg_score = sum(call['score'] for call in tool_calls) / total_calls if total_calls > 0 else 0
avg_time = sum(call['time_cost'] for call in tool_calls) / total_calls if total_calls > 0 else 0
avg_tokens = sum(call['token_cost'] for call in tool_calls) / total_calls if total_calls > 0 else 0
return {
"tool_name": tool_name,
"total_calls": total_calls,
"success_rate": successful_calls / total_calls if total_calls > 0 else 0,
"avg_score": avg_score,
"avg_time_cost": avg_time,
"avg_token_cost": avg_tokens,
"guidelines": memory['content']
}
# Usage
stats = get_tool_statistics("web_search")
if stats:
print(f"Tool: {stats['tool_name']}")
print(f"Total Calls: {stats['total_calls']}")
print(f"Success Rate: {stats['success_rate']:.1%}")
print(f"Avg Score: {stats['avg_score']:.2f}")
print(f"Avg Time: {stats['avg_time_cost']:.2f}s")
print(f"Avg Tokens: {stats['avg_token_cost']:.0f}")
```
### Integration with Agent Workflows
#### Pre-Execution Retrieval
```python
def execute_tool_with_guidelines(tool_name, input_params):
"""Execute a tool after retrieving its usage guidelines"""
# 1. Retrieve tool memory
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": tool_name
}
)
# 2. Extract guidelines
guidelines = ""
if response.json().get("success"):
memory_list = response.json().get("metadata", {}).get("memory_list", [])
if memory_list:
guidelines = memory_list[0]['content']
print(f"Guidelines for {tool_name}:")
print(guidelines)
# 3. Adjust parameters based on guidelines
# (This would be done by an LLM or rule-based system)
adjusted_params = adjust_parameters(input_params, guidelines)
# 4. Execute tool
result = execute_tool(tool_name, adjusted_params)
# 5. Record result
record_tool_call(tool_name, adjusted_params, result)
return result
```
#### Batch Retrieval for Agent Initialization
```python
def initialize_agent_with_tool_memories(available_tools):
"""Load all tool memories at agent initialization"""
# Retrieve all tool memories at once
tool_names = ",".join(available_tools)
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": tool_names
}
)
# Build tool memory cache
tool_memory_cache = {}
if response.json().get("success"):
memory_list = response.json().get("metadata", {}).get("memory_list", [])
for memory in memory_list:
tool_memory_cache[memory['when_to_use']] = {
"guidelines": memory['content'],
"total_calls": len(memory['tool_call_results']),
"last_modified": memory['time_modified']
}
return tool_memory_cache
# Usage
available_tools = ["web_search", "file_reader", "api_caller"]
tool_cache = initialize_agent_with_tool_memories(available_tools)
# Agent can now quickly access guidelines
if "web_search" in tool_cache:
print(tool_cache["web_search"]["guidelines"])
```
### Error Handling
#### Tool Not Found
```python
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": "nonexistent_tool"
}
)
result = response.json()
if not result.get("success"):
print(f"Error: {result.get('answer')}")
# Output: "No matching tool memories found"
```
#### Empty Tool Names
```python
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": ""
}
)
result = response.json()
if not result.get("success"):
print(f"Error: {result.get('answer')}")
# Output: "tool_names is required"
```
#### Partial Matches
```python
# Request 3 tools, but only 2 exist
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": "web_search,file_reader,nonexistent_tool"
}
)
result = response.json()
if result.get("success"):
memory_list = result.get("metadata", {}).get("memory_list", [])
print(f"Found {len(memory_list)} out of 3 requested tools")
# Output: "Found 2 out of 3 requested tools"
```
### Retrieval Workflow
```mermaid
sequenceDiagram
participant Agent
participant RetrieveOp
participant VectorStore
Agent->>RetrieveOp: retrieve_tool_memory(tool_names)
RetrieveOp->>RetrieveOp: Split and validate names
loop For each tool name
RetrieveOp->>VectorStore: search(tool_name, top_k=1)
VectorStore-->>RetrieveOp: Return top result
RetrieveOp->>RetrieveOp: Validate type and name
alt Valid match
RetrieveOp->>RetrieveOp: Add to results
else No match
RetrieveOp->>RetrieveOp: Log warning
end
end
RetrieveOp-->>Agent: Return memory_list
Agent->>Agent: Apply guidelines
```
### Use Cases
#### Use Case 1: Pre-Execution Guidance
**Scenario**: Before executing a tool, the agent retrieves usage guidelines to optimize parameters.
```python
# Agent needs to search the web
tool_name = "web_search"
query = "machine learning basics"
# Retrieve guidelines
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": tool_name
}
)
# Guidelines suggest: max_results=10-20, use filter_type="technical_docs"
# Agent adjusts parameters accordingly
optimized_params = {
"query": query,
"max_results": 15, # Within recommended range
"filter_type": "technical_docs", # As suggested
"language": "en"
}
# Execute with optimized parameters
result = execute_tool(tool_name, optimized_params)
```
#### Use Case 2: Performance Monitoring
**Scenario**: Monitor tool performance trends over time.
```python
def monitor_tool_performance(tool_name):
"""Monitor tool performance and detect degradation"""
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": tool_name
}
)
if not response.json().get("success"):
return None
memory = response.json()["metadata"]["memory_list"][0]
calls = memory["tool_call_results"]
# Analyze recent vs historical performance
recent_calls = calls[-20:] # Last 20 calls
historical_calls = calls[:-20] if len(calls) > 20 else []
recent_success_rate = sum(1 for c in recent_calls if c['success']) / len(recent_calls)
historical_success_rate = (sum(1 for c in historical_calls if c['success']) / len(historical_calls)
if historical_calls else recent_success_rate)
# Detect degradation
if recent_success_rate < historical_success_rate - 0.1:
print(f"Warning: {tool_name} performance degraded!")
print(f"Recent: {recent_success_rate:.1%}, Historical: {historical_success_rate:.1%}")
return "degraded"
return "healthy"
# Usage
status = monitor_tool_performance("web_search")
```
#### Use Case 3: Tool Selection
**Scenario**: Choose the best tool for a task based on historical performance.
```python
def select_best_tool(task_description, candidate_tools):
"""Select the best tool based on historical performance"""
# Retrieve memories for all candidate tools
tool_names = ",".join(candidate_tools)
response = requests.post(
url=f"{BASE_URL}retrieve_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": tool_names
}
)
if not response.json().get("success"):
return candidate_tools[0] # Default to first
memory_list = response.json()["metadata"]["memory_list"]
# Score each tool
tool_scores = {}
for memory in memory_list:
calls = memory["tool_call_results"]
if not calls:
continue
# Calculate composite score
success_rate = sum(1 for c in calls if c['success']) / len(calls)
avg_score = sum(c['score'] for c in calls) / len(calls)
avg_time = sum(c['time_cost'] for c in calls) / len(calls)
# Composite: prioritize success and quality, penalize slow tools
composite = (success_rate * 0.4 + avg_score * 0.4 + (1 / (1 + avg_time)) * 0.2)
tool_scores[memory['when_to_use']] = composite
# Select best tool
if tool_scores:
best_tool = max(tool_scores, key=tool_scores.get)
print(f"Selected {best_tool} with score {tool_scores[best_tool]:.2f}")
return best_tool
return candidate_tools[0]
# Usage
best = select_best_tool(
"Search for technical documentation",
["web_search", "doc_search", "api_search"]
)
```
## Best Practices
1. **Retrieval Timing**:
- Retrieve guidelines before first use of a tool
- Cache retrieved memories for the duration of a task
- Re-retrieve after tool memory updates (post-summarization)
2. **Batch Retrieval**:
- Retrieve multiple tool memories in a single request
- Use comma-separated tool names for efficiency
- Initialize agent with all available tool memories
3. **Error Handling**:
- Always check `success` status in response
- Handle cases where tool memory doesn't exist
- Provide fallback behavior for missing guidelines
4. **Guidelines Application**:
- Parse guidelines to extract parameter recommendations
- Use LLM to interpret guidelines in context
- Combine guidelines with task-specific requirements
5. **Performance Optimization**:
- Cache retrieved memories to avoid repeated calls
- Invalidate cache after summarization updates
- Monitor retrieval latency and optimize if needed
6. **Monitoring**:
- Log retrieval requests for debugging
- Track which tools are frequently retrieved
- Identify tools without memory (candidates for recording)

View file

@ -10,202 +10,19 @@ Evaluates individual tool invocations and adds them to the tool memory database
- Receives tool call results with input parameters, output, and metadata
- Uses LLM to evaluate each tool call based on success and parameter alignment
- Generates summary, evaluation, and score (0.0, 0.5, or 1.0) for each call
- Generates summary, evaluation, and score (0.0 or 1.0) for each call
- Appends evaluated results to existing tool memory or creates new memory
- Maintains a sliding window of recent tool calls (configurable limit)
### Processing Flow
```mermaid
graph TB
A[Receive Tool Call Results] --> B[Validate Input]
B --> C[Search for Existing Memory]
C --> D{Memory Exists?}
D -->|Yes| E[Load Existing Memory]
D -->|No| F[Create New Memory]
E --> G[Concurrent Evaluation]
F --> G
G --> H[Evaluate Call #1]
G --> I[Evaluate Call #2]
G --> J[Evaluate Call #N]
H --> K[Generate Summary & Score]
I --> K
J --> K
K --> L[Append to Memory]
L --> M[Trim to Max History]
M --> N[Update Modified Time]
N --> O[Prepare for Vector Store]
```
1. **Input Validation**:
- Verify `tool_name` is provided
- Convert dict objects to `ToolCallResult` instances
- Check if `tool_call_results` list is not empty
2. **Memory Lookup**:
- Search vector store for existing tool memory by tool name
- Verify exact match (memory type = "tool" and when_to_use = tool_name)
- Create new `ToolMemory` if no match found
3. **Concurrent Evaluation**:
- Submit all tool call results for parallel evaluation
- Each evaluation uses LLM to analyze the call
- Generate structured evaluation with summary, assessment, and score
4. **Memory Update**:
- Append evaluated results to tool memory
- Trim to `max_history_tool_call_cnt` if limit exceeded
- Update modification timestamp
- Prepare for vector store update
### Parameters
Configure in `default.yaml`:
```yaml
op:
parse_tool_call_result_op:
backend: parse_tool_call_result_op
llm: default
params:
max_history_tool_call_cnt: 100
evaluation_sleep_interval: 1.0
```
- `max_history_tool_call_cnt` (integer, default: `100`):
- `op.parse_tool_call_result_op.params.max_history_tool_call_cnt` (integer, default: `100`):
- Maximum number of historical tool call results to retain per tool
- When exceeded, oldest results are removed (FIFO)
- Balances memory size with historical context
- Recommended: 50-200 depending on tool usage frequency
- `evaluation_sleep_interval` (float, default: `1.0`):
- `op.parse_tool_call_result_op.params.evaluation_sleep_interval` (float, default: `1.0`):
- Delay in seconds between concurrent evaluations
- Prevents rate limiting when evaluating multiple calls
- Set to 0 for maximum speed (if no rate limits)
- Increase if encountering API throttling
### Evaluation Criteria
The LLM evaluates each tool call based on two dimensions:
1. **Success Evaluation**:
- Check if the success flag indicates successful execution
- Verify output contains no error messages
- Assess if time and token costs are reasonable
- Identify any error indicators in the output
2. **Parameter Alignment Evaluation**:
- Evaluate if output matches expected behavior given input
- Consider if input parameters are appropriate for the tool
- Check for parameter mismatches or unexpected behaviors
- Verify output is consistent with tool's intended purpose
### Scoring Guidelines
- **1.0 (Success)**: Tool executed successfully with good parameter alignment
- Example: Query returned relevant results, parameters were appropriate
- **0.5 (Partial Success)**: Tool executed but with issues
- Example: Query succeeded but parameters were suboptimal (e.g., too generic)
- Example: Results returned but with warnings about invalid parameters
- **0.0 (Failure)**: Tool execution failed or severe parameter misalignment
- Example: Timeout due to excessive max_results parameter
- Example: Error due to invalid parameter format
### Output Format
The operation sets the following in `context.response.metadata`:
```python
{
"deleted_memory_ids": ["memory_id_if_updating"],
"memory_list": [
{
"memory_id": "abc123",
"when_to_use": "web_search",
"tool_call_results": [
{
"create_time": "2025-10-15 14:30:00",
"tool_name": "web_search",
"input": {...},
"output": "...",
"summary": "Successfully retrieved 10 relevant results",
"evaluation": "Good parameter alignment...",
"score": 1.0,
"success": True,
"time_cost": 2.3,
"token_cost": 150
}
]
}
]
}
```
### Usage Example
```python
import requests
from datetime import datetime
BASE_URL = "http://0.0.0.0:8002/"
WORKSPACE_ID = "demo_workspace"
# Prepare tool call results
tool_call_results = [
{
"create_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": "web_search",
"input": {
"query": "Python asyncio tutorial",
"max_results": 10,
"language": "en",
"filter_type": "technical_docs"
},
"output": "Found 10 relevant results including official documentation and tutorials",
"token_cost": 150,
"success": True,
"time_cost": 2.3
},
{
"create_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": "web_search",
"input": {
"query": "test", # Too generic
"max_results": 100, # Too many
"language": "unknown" # Invalid
},
"output": "Warning: language 'unknown' not supported. Query too generic, limited results.",
"token_cost": 80,
"success": True,
"time_cost": 3.5
}
]
# Add tool call results
response = requests.post(
url=f"{BASE_URL}add_tool_call_result",
json={
"workspace_id": WORKSPACE_ID,
"tool_name": "web_search",
"tool_call_results": tool_call_results
}
)
result = response.json()
print(f"Success: {result.get('success')}")
print(f"Answer: {result.get('answer')}")
# Check evaluated results
memory_list = result.get("metadata", {}).get("memory_list", [])
if memory_list:
tool_memory = memory_list[0]
for call_result in tool_memory["tool_call_results"]:
print(f"\nCall Summary: {call_result['summary']}")
print(f"Evaluation: {call_result['evaluation']}")
print(f"Score: {call_result['score']}")
```
## SummaryToolMemoryOp
@ -221,245 +38,13 @@ Analyzes accumulated tool call history and generates comprehensive usage pattern
- Uses LLM to synthesize actionable usage guidelines
- Updates tool memory content with generated insights
### Processing Flow
```mermaid
graph TB
A[Receive Tool Names] --> B[Split by Comma]
B --> C[For Each Tool Name]
C --> D[Search Vector Store]
D --> E{Exact Match?}
E -->|Yes| F[Load Tool Memory]
E -->|No| G[Log Warning]
F --> H[Extract Recent N Calls]
H --> I[Calculate Statistics]
I --> J[Format Call Summaries]
J --> K[Format Statistics]
K --> L[Concurrent Summarization]
L --> M[LLM Analysis #1]
L --> N[LLM Analysis #2]
L --> O[LLM Analysis #N]
M --> P[Generate Guidelines]
N --> P
O --> P
P --> Q[Update Memory Content]
Q --> R[Update Modified Time]
R --> S[Prepare for Vector Store]
```
1. **Tool Name Processing**:
- Split comma-separated tool names
- Trim whitespace from each name
- Log the list of tools to process
2. **Memory Retrieval**:
- Search vector store for each tool name
- Verify exact match (memory type and when_to_use)
- Skip tools without existing memory
3. **Data Preparation**:
- Extract the most recent N tool call results
- Calculate statistical metrics:
- Total calls vs recent calls analyzed
- Success rate (overall and recent)
- Average score (overall and recent)
- Average time cost
- Average token cost
- Format call summaries as markdown
- Format statistics as markdown
4. **Concurrent Summarization**:
- Submit all tools for parallel summarization
- Each summarization uses LLM to analyze patterns
- Generate structured usage guidelines
5. **Memory Update**:
- Update tool memory content with new guidelines
- Update modification timestamp
- Prepare for vector store update
### Parameters
Configure in `default.yaml`:
```yaml
op:
summary_tool_memory_op:
backend: summary_tool_memory_op
llm: default
params:
recent_call_count: 20
summary_sleep_interval: 1.0
```
- `recent_call_count` (integer, default: `20`):
- `op.summary_tool_memory_op.params.recent_call_count` (integer, default: `30`):
- Number of most recent tool calls to analyze
- Focuses on recent usage patterns
- Recommended: 10-50 depending on tool usage frequency
- Higher values provide more context but may dilute recent patterns
- `summary_sleep_interval` (float, default: `1.0`):
- `op.summary_tool_memory_op.params.summary_sleep_interval` (float, default: `1.0`):
- Delay in seconds between concurrent summarizations
- Prevents rate limiting when summarizing multiple tools
- Set to 0 for maximum speed (if no rate limits)
- Increase if encountering API throttling
### Statistical Metrics
The operation calculates the following metrics:
```python
{
"total_calls": 100, # Total number of calls in history
"recent_calls": 20, # Number of recent calls analyzed
"success_rate": 0.85, # Overall success rate (85%)
"recent_success_rate": 0.90, # Recent success rate (90%)
"avg_score": 0.78, # Average evaluation score
"recent_avg_score": 0.82, # Recent average score
"avg_time_cost": 2.45, # Average time in seconds
"avg_token_cost": 125.3 # Average token consumption
}
```
### Generated Guidelines Structure
The LLM generates guidelines following this structure:
1. **Core Function**: What the tool does and when to use it
2. **Success Patterns**: Parameter patterns and scenarios that work well
3. **Common Issues**: Main pitfalls to avoid and why they fail
4. **Best Practices**: 2-3 actionable recommendations
### Usage Example
```python
import requests
BASE_URL = "http://0.0.0.0:8002/"
WORKSPACE_ID = "demo_workspace"
# After accumulating tool call history, generate guidelines
response = requests.post(
url=f"{BASE_URL}summary_tool_memory",
json={
"workspace_id": WORKSPACE_ID,
"tool_names": "web_search,file_reader,api_caller" # Multiple tools
}
)
result = response.json()
print(f"Success: {result.get('success')}")
print(f"Answer: {result.get('answer')}")
# Display generated guidelines
memory_list = result.get("metadata", {}).get("memory_list", [])
for memory in memory_list:
print(f"\n{'='*60}")
print(f"Tool: {memory['when_to_use']}")
print(f"{'='*60}")
print(memory['content'])
# Display statistics
stats = memory.get('metadata', {}).get('statistics', {})
print(f"\nStatistics:")
print(f" Total Calls: {stats.get('total_calls', 0)}")
print(f" Success Rate: {stats.get('success_rate', 0):.1%}")
print(f" Avg Score: {stats.get('avg_score', 0):.2f}")
```
### Example Generated Guidelines
```
Core Function:
The web_search tool retrieves information from the internet based on query parameters.
Use it when you need up-to-date information, documentation, or external data.
Success Patterns:
- Specific queries (e.g., "Python asyncio tutorial") achieve 95% success rate
- Setting max_results=10-20 balances quality and performance
- Using language="en" and filter_type="technical_docs" improves relevance
- Average successful call: 2.3s, 150 tokens
Common Issues:
- Generic queries (e.g., "test") return poor results (score: 0.5)
- max_results > 50 often leads to timeouts (avg: 8.2s vs 2.3s)
- Invalid language codes default to English but add latency
- Missing filter_type returns mixed-quality results
Best Practices:
1. Use specific, descriptive queries with clear intent
2. Set max_results=10-20 for optimal balance
3. Always specify language and filter_type for technical searches
```
### When to Run Summarization
- **Initial Setup**: After accumulating 20-30 tool calls
- **Regular Updates**: Every 50-100 new calls
- **Pattern Changes**: When success rate changes significantly
- **Tool Updates**: After tool version changes or parameter updates
- **Performance Issues**: When investigating declining performance
### Integration with Retrieval
```mermaid
sequenceDiagram
participant Agent
participant AddOp as add_tool_call_result
participant SummaryOp as summary_tool_memory
participant RetrieveOp as retrieve_tool_memory
loop Every Tool Call
Agent->>AddOp: Record tool call result
AddOp->>AddOp: Evaluate and store
end
Note over Agent,SummaryOp: After 20+ calls
Agent->>SummaryOp: Generate guidelines
SummaryOp->>SummaryOp: Analyze patterns
SummaryOp->>SummaryOp: Update content
Note over Agent,RetrieveOp: Before next use
Agent->>RetrieveOp: Get tool memory
RetrieveOp-->>Agent: Return guidelines + history
Agent->>Agent: Apply recommendations
```
The summarization operation works in conjunction with retrieval:
1. `add_tool_call_result` continuously records invocations
2. `summary_tool_memory` periodically generates guidelines
3. `retrieve_tool_memory` provides guidelines before tool use
4. Agent applies recommendations to improve success rates
## Best Practices
1. **Recording Strategy**:
- Record every tool invocation, including failures
- Include detailed input parameters and complete output
- Capture accurate performance metrics
- Add relevant metadata for context
2. **Evaluation Quality**:
- Ensure LLM has sufficient context for evaluation
- Monitor score distribution (should not be all 1.0 or 0.0)
- Review evaluations periodically for quality
- Adjust evaluation prompts if needed
3. **Summarization Timing**:
- Wait for 20-30 calls before first summarization
- Re-summarize after significant new data (50+ calls)
- Update when usage patterns change
- Regenerate after tool updates
4. **Parameter Tuning**:
- Adjust `max_history_tool_call_cnt` based on tool usage frequency
- Increase `recent_call_count` for tools with diverse patterns
- Balance `sleep_interval` between speed and rate limits
- Monitor vector store size and adjust retention limits
5. **Quality Maintenance**:
- Review generated guidelines for accuracy
- Validate statistical metrics match expectations
- Clean up deprecated tools from vector store
- Archive historical data before major changes

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "reme_ai"
version = "0.1.9"
version = "0.1.10"
description = "Remember me"
authors = [
{ name = "jinli.yl", email = "jinli.yl@alibaba-inc.com" },
@ -24,7 +24,7 @@ classifiers = [
keywords = ["llm", "memory", "experience", "memoryscope", "ai", "mcp", "http"]
dependencies = [
"flowllm==0.1.9",
"flowllm>=0.1.11.1",
]
[tool.setuptools.packages.find]

View file

@ -1,6 +1,14 @@
import warnings
from pydantic.warnings import PydanticDeprecatedSince20
warnings.filterwarnings("ignore", category=DeprecationWarning, module="websockets")
warnings.filterwarnings("ignore", category=DeprecationWarning, module="uvicorn")
warnings.filterwarnings("ignore", category=PydanticDeprecatedSince20)
from . import agent
from . import retrieve
from . import summary
from . import vector_store
__version__ = "0.1.9"
__version__ = "0.1.10"

View file

@ -34,6 +34,7 @@ class LLMMockSearchOp(BaseAsyncToolOp):
simple_config: Dict[str, Any] = None,
medium_config: Dict[str, Any] = None,
complex_config: Dict[str, Any] = None,
seed: int = 0,
**kwargs):
"""
Initialize the LLM Mock Search Op.
@ -52,8 +53,13 @@ class LLMMockSearchOp(BaseAsyncToolOp):
- success_rate: float (0-1), default 0.70
- extra_time: float (seconds), default 1.5
- relevance_ratio: float (0-1), default 0.80
seed: Random seed for deterministic behavior, default 0
"""
super().__init__(llm=llm, **kwargs)
# Set random seed for deterministic behavior
self.seed = seed
random.seed(self.seed)
# Default configurations for each scenario
self.simple_config = {
@ -221,13 +227,16 @@ class LLMMockSearchOp(BaseAsyncToolOp):
# Step 5: Check relevance ratio
if random.random() > config["relevance_ratio"]:
# Generate random/irrelevant result
logger.info("Generating low relevance result")
# NOTE: success=True because technically the tool executed without errors,
# but the content is irrelevant (low quality), which should result in score=0.0 during evaluation
logger.info("Generating low relevance result (success=True but low quality)")
content = await self.generate_random_result()
result_dict = {
"success": False,
"success": True, # Technical execution succeeded
"content": content,
"query": query,
"complexity": complexity
"complexity": complexity,
"is_relevant": False # Mark as irrelevant for debugging
}
else:
# Generate relevant result
@ -237,7 +246,8 @@ class LLMMockSearchOp(BaseAsyncToolOp):
"success": True,
"content": content,
"query": query,
"complexity": complexity
"complexity": complexity,
"is_relevant": True # Mark as relevant for debugging
}
self.set_result(json.dumps(result_dict, ensure_ascii=False))

View file

@ -9,23 +9,23 @@ class SearchToolA(LLMMockSearchOp):
def __init__(self, llm: str = "qwen3_30b_instruct", **kwargs):
# Configure for fast but shallow performance
simple_config = {
"success_rate": 0.95, # High success rate for simple queries
"extra_time": 0.2, # Very fast (0.2-0.5s range)
"relevance_ratio": 0.92, # High relevance
"success_rate": 0.9, # High success rate for simple queries
"extra_time": 0, # Very fast (0.2-0.5s range)
"relevance_ratio": 0.9, # High relevance
"content_length": "short" # Concise answers
}
medium_config = {
"success_rate": 0.75, # Lower success for medium queries
"extra_time": 0.3, # Still fast
"relevance_ratio": 0.70, # Moderate relevance
"success_rate": 0.2, # Lower success for medium queries
"extra_time": 0, # Still fast
"relevance_ratio": 0.2, # Moderate relevance
"content_length": "short" # Limited depth
}
complex_config = {
"success_rate": 0.50, # Poor success rate for complex queries
"extra_time": 0.4, # Fast but insufficient
"relevance_ratio": 0.50, # Low relevance (often misses key aspects)
"success_rate": 0.5, # Poor success rate for complex queries
"extra_time": 0, # Fast but insufficient
"relevance_ratio": 0.5, # Low relevance (often misses key aspects)
"content_length": "short" # Too shallow for complex topics
}
@ -45,23 +45,23 @@ class SearchToolB(LLMMockSearchOp):
def __init__(self, llm: str = "qwen3_30b_instruct", **kwargs):
# Configure for balanced performance
simple_config = {
"success_rate": 0.8, # Very high success rate
"extra_time": 0.8, # Moderate speed (1.0-1.5s range)
"relevance_ratio": 0.8, # High relevance
"success_rate": 0.3, # Very high success rate
"extra_time": 0, # Moderate speed (1.0-1.5s range)
"relevance_ratio": 0.3, # High relevance
"content_length": "medium" # More detailed than needed for simple
}
medium_config = {
"success_rate": 0.8, # Excellent success rate
"extra_time": 1.0, # Balanced speed
"relevance_ratio": 0.8, # High relevance
"success_rate": 0.9, # Excellent success rate
"extra_time": 0, # Balanced speed
"relevance_ratio": 0.9, # High relevance
"content_length": "medium" # Perfect depth for medium queries
}
complex_config = {
"success_rate": 0.8, # Good success rate
"extra_time": 1.2, # Still reasonable speed
"relevance_ratio": 0.8, # Decent relevance but not exhaustive
"success_rate": 0.5, # Good success rate
"extra_time": 0, # Still reasonable speed
"relevance_ratio": 0.5, # Decent relevance but not exhaustive
"content_length": "medium" # Covers main points but lacks depth
}
@ -83,23 +83,23 @@ class SearchToolC(LLMMockSearchOp):
def __init__(self, llm: str = "qwen3_30b_instruct", **kwargs):
# Configure for comprehensive but costly performance
simple_config = {
"success_rate": 0.7, # Good but not optimal (over-processing)
"extra_time": 2.5, # Slow (3.0-4.0s range)
"relevance_ratio": 0.7, # High relevance but unnecessary depth
"success_rate": 0.3, # Good but not optimal (over-processing)
"extra_time": 0, # Slow (3.0-4.0s range)
"relevance_ratio": 0.3, # High relevance but unnecessary depth
"content_length": "long" # Too detailed for simple queries
}
medium_config = {
"success_rate": 0.7, # High success rate
"extra_time": 2.8, # Slow but thorough
"relevance_ratio": 0.7, # High relevance with extra context
"success_rate": 0.4, # High success rate
"extra_time": 0, # Slow but thorough
"relevance_ratio": 0.4, # High relevance with extra context
"content_length": "long" # More depth than needed
}
complex_config = {
"success_rate": 0.95, # Excellent success rate
"extra_time": 3.5, # Slow but comprehensive (3.5-5.0s range)
"relevance_ratio": 0.94, # Very high relevance
"success_rate": 0.9, # Excellent success rate
"extra_time": 0, # Slow but comprehensive (3.5-5.0s range)
"relevance_ratio": 0.9, # Very high relevance
"content_length": "long" # Perfect depth for complex queries
}

View file

@ -1,132 +0,0 @@
"""
Test script for UseMockSearchOp
This script demonstrates how to use the UseMockSearchOp to intelligently
select and execute search tools based on query complexity.
"""
import asyncio
import json
from flowllm.context import FlowContext
from flowllm.app import FlowLLMApp
from reme_ai.agent.tools.use_mock_search_op import UseMockSearchOp
async def test_single_query(op: UseMockSearchOp, query: str):
"""Test a single query and display results."""
print(f"\n{'=' * 100}")
print(f"Testing Query: {query}")
print(f"{'=' * 100}")
context = FlowContext(query=query)
await op.async_call(context=context)
result = json.loads(context.use_mock_search_result)
print(f"\n📊 Results:")
print(f" Selected Tool: {result['selected_tool']}")
print(f" Reasoning: {result['reasoning']}")
print(f" Query Complexity: {result['complexity']}")
print(f" Success: {result['success']}")
print(f"\n📝 Content:")
print(f" {result['content'][:500]}..." if len(result['content']) > 500 else f" {result['content']}")
print(f"\n{'=' * 100}\n")
return result
async def main():
"""Run comprehensive tests of UseMockSearchOp."""
print("\n" + "=" * 100)
print("UseMockSearchOp Test Suite")
print("=" * 100)
print("\nThis test demonstrates the intelligent tool selection capability.")
print("The LLM analyzes each query and selects the most appropriate search tool:\n")
print(" • SearchToolA: Fast & shallow (best for simple factual queries)")
print(" • SearchToolB: Balanced (best for medium complexity queries)")
print(" • SearchToolC: Comprehensive & slow (best for complex research queries)")
print("=" * 100)
async with FlowLLMApp(load_default_config=True):
op = UseMockSearchOp()
# Test cases covering different complexities
test_queries = [
# Simple queries (should select SearchToolA)
{
"query": "What is the capital of France?",
"expected_tool": "SearchToolA",
"description": "Simple factual query"
},
{
"query": "When was Python programming language created?",
"expected_tool": "SearchToolA",
"description": "Simple historical fact"
},
# Medium complexity queries (should select SearchToolB)
{
"query": "How does quantum computing work?",
"expected_tool": "SearchToolB",
"description": "Medium complexity technical explanation"
},
{
"query": "What are the main causes of climate change?",
"expected_tool": "SearchToolB",
"description": "Medium complexity scientific question"
},
# Complex queries (should select SearchToolC)
{
"query": "Analyze the impact of artificial intelligence on global economy, employment, and society",
"expected_tool": "SearchToolC",
"description": "Complex multi-dimensional analysis"
},
{
"query": "Compare and contrast different renewable energy solutions including their environmental impact, cost-effectiveness, and scalability",
"expected_tool": "SearchToolC",
"description": "Complex comparative analysis"
}
]
results = []
correct_selections = 0
for test_case in test_queries:
query = test_case["query"]
expected = test_case["expected_tool"]
description = test_case["description"]
print(f"\n🔍 Test Case: {description}")
print(f" Expected Tool: {expected}")
result = await test_single_query(op, query)
results.append({
"test_case": test_case,
"result": result
})
# Check if the selection was correct
if result["selected_tool"] == expected:
correct_selections += 1
print(f" ✅ Correct tool selected!")
else:
print(f" ⚠️ Different tool selected (this may still be valid)")
# Summary
print("\n" + "=" * 100)
print("Test Summary")
print("=" * 100)
print(f"Total Tests: {len(test_queries)}")
print(f"Expected Matches: {correct_selections}/{len(test_queries)}")
print(f"Success Rate: {correct_selections/len(test_queries)*100:.1f}%")
print("\nNote: Tool selection may vary based on LLM reasoning, and different")
print(" selections don't necessarily indicate errors.")
print("=" * 100)
if __name__ == "__main__":
asyncio.run(main())

View file

@ -1,17 +1,18 @@
import asyncio
import datetime
import json
from typing import Dict, Any
from flowllm.op.gallery.task_react_op import tools_schema_to_qwen_prompt
from flowllm.schema.tool_call import ToolCall
from loguru import logger
from flowllm.context import FlowContext, C
from flowllm.context import C
from flowllm.enumeration.role import Role
from flowllm.op.base_async_tool_op import BaseAsyncToolOp
from flowllm.schema.message import Message
from flowllm.schema.tool_call import ToolCall
from flowllm.utils.timer import Timer
from flowllm.utils.token_utils import TokenCounter
from loguru import logger
from reme_ai.agent.tools.mock_search_tools import SearchToolA, SearchToolB, SearchToolC
from reme_ai.schema.memory import ToolCallResult
@C.register_op()
@ -19,18 +20,30 @@ class UseMockSearchOp(BaseAsyncToolOp):
file_path: str = __file__
def __init__(self, llm: str = "qwen3_30b_instruct", **kwargs):
super().__init__(llm=llm, **kwargs)
super().__init__(llm=llm, save_answer=True, **kwargs)
def build_tool_call(self) -> ToolCall:
return ToolCall(**{
"description": "Intelligently selects and executes the most appropriate search tool based on query complexity. "
"Automatically tracks performance metrics and records tool usage for optimization.",
"input_schema": {
"query": {
"type": "string",
"description": "query",
"required": True
}
}
})
async def select_tool(self, query: str, tool_ops: list[BaseAsyncToolOp]) -> ToolCall | None:
assistant_message = await self.llm.achat(messages=[Message(role=Role.USER, content=query)],
tools=[x.tool_call for x in tool_ops])
logger.info(f"assistant_message={assistant_message.model_dump_json()}")
if assistant_message.tool_calls:
return assistant_message.tool_calls[0]
return None
async def async_execute(self):
query: str = self.input_dict["query"]
logger.info(f"query={query}")
@ -41,46 +54,85 @@ class UseMockSearchOp(BaseAsyncToolOp):
SearchToolC(),
]
tool_result = await self.select_tool(query, tool_ops)
if tool_result is None:
...
# Step 1: Select the appropriate tool using LLM
tool_call = await self.select_tool(query, tool_ops)
if tool_call is None:
# No tool selected
error_result = ToolCallResult(
create_time=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
tool_name="None",
input={"query": query},
output="No appropriate tool was selected for the query",
token_cost=0,
success=False,
time_cost=0.0
)
self.set_result(error_result.model_dump_json())
return
# Step 2: Execute the selected tool
selected_op = None
for op in tool_ops:
op.tool_call.name == tool_result.name
if op.tool_call.name == tool_call.name:
selected_op = op
break
if selected_op is None:
# Tool not found (should not happen)
error_result = ToolCallResult(
create_time=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
tool_name=tool_call.name,
input=tool_call.arguments,
output=f"Tool {tool_call.name} not found in available tools",
token_cost=0,
success=False,
time_cost=0.0
)
self.set_result(error_result.model_dump_json())
return
# Step 3: Execute the tool with timer
timer = Timer("tool execute")
with timer:
await selected_op.async_call(query=query)
selected_op_output = json.loads(selected_op.output)
content = selected_op_output["content"]
success = selected_op_output["success"]
token_cost = TokenCounter().count(content)
time_cost = timer.time_cost
# Create ToolCallResult
tool_call_result = ToolCallResult(
create_time=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
tool_name=tool_call.name,
input={"query": query},
output=content,
token_cost=token_cost,
success=success,
time_cost=round(time_cost, 3)
)
self.set_result(tool_call_result.model_dump_json())
async def async_main():
"""Test the UseMockSearchOp with different query types."""
from flowllm.app import FlowLLMApp
async with FlowLLMApp(load_default_config=True):
# Test queries of different complexities
test_queries = [
"What is the capital of France?", # Simple - should select SearchToolA
"How does quantum computing work?", # Medium - should select SearchToolB
"Analyze the impact of artificial intelligence on global economy, employment, and society", # Complex - should select SearchToolC
"When was Python programming language created?", # Simple
"Compare different types of renewable energy sources", # Complex
"What is the capital of France?",
"How does quantum computing work?",
"Analyze the impact of artificial intelligence on global economy, employment, and society",
"When was Python programming language created?",
"Compare different types of renewable energy sources",
]
op = UseMockSearchOp()
for query in test_queries:
print(f"\n{'=' * 100}")
print(f"Query: {query}")
print(f"{'=' * 100}")
context = FlowContext(query=query)
await op.async_call(context=context)
result = json.loads(context.use_mock_search_result)
print(f"\nSelected Tool: {result['selected_tool']}")
print(f"Reasoning: {result['reasoning']}")
print(f"Complexity: {result['complexity']}")
print(f"Success: {result['success']}")
print(f"\nContent:\n{result['content']}")
print(f"\n{'=' * 100}\n")
op = UseMockSearchOp()
await op.async_call(query=query)
print(op.output)
if __name__ == "__main__":

View file

@ -1,64 +0,0 @@
tool_selection_prompt: |
You are an intelligent tool selection assistant. You have access to three different search tools with different characteristics:
**SearchToolA** - Fast but shallow search tool
- Best for: Simple factual queries
- Strengths: Very fast response (0.2-0.5s), high success rate for simple queries (95%)
- Weaknesses: Poor performance on complex queries (50% success rate), limited depth
**SearchToolB** - Balanced search tool
- Best for: Medium complexity queries
- Strengths: Good balance of speed and quality (1.0-1.5s), consistent 80% success rate across all query types
- Weaknesses: May be slower than needed for simple queries, lacks depth for very complex topics
**SearchToolC** - Comprehensive but slow search tool
- Best for: Complex research queries
- Strengths: Excellent for complex queries (95% success rate), comprehensive and detailed results
- Weaknesses: Very slow (3.0-5.0s), overkill for simple queries (70% success rate)
Given the user's query, you need to:
1. Analyze the complexity and requirements of the query
2. Select the most appropriate tool (SearchToolA, SearchToolB, or SearchToolC)
3. Provide the query parameter
User Query: {query}
Respond with a JSON object in the following format:
{{
"selected_tool": "SearchToolA" or "SearchToolB" or "SearchToolC",
"reasoning": "Brief explanation of why this tool was selected",
"query": "The search query to use"
}}
tool_selection_prompt_zh: |
你是一个智能工具选择助手。你可以访问三个具有不同特性的搜索工具:
**SearchToolA** - 快速但浅层的搜索工具
- 最适合:简单的事实查询
- 优势响应非常快0.2-0.5秒简单查询的成功率高95%
- 劣势复杂查询表现不佳成功率50%),深度有限
**SearchToolB** - 平衡的搜索工具
- 最适合:中等复杂度的查询
- 优势速度和质量平衡良好1.0-1.5秒所有查询类型的成功率一致为80%
- 劣势:对于简单查询可能比需要的慢,对于非常复杂的主题缺乏深度
**SearchToolC** - 全面但慢速的搜索工具
- 最适合:复杂的研究查询
- 优势对复杂查询效果极佳成功率95%),结果全面详细
- 劣势非常慢3.0-5.0秒对简单查询来说过度成功率70%
给定用户的查询,你需要:
1. 分析查询的复杂度和要求
2. 选择最合适的工具SearchToolA、SearchToolB 或 SearchToolC
3. 提供查询参数
用户查询:{query}
用 JSON 对象格式回复:
{{
"selected_tool": "SearchToolA" 或 "SearchToolB" 或 "SearchToolC",
"reasoning": "选择此工具的简要说明",
"query": "要使用的搜索查询"
}}

View file

@ -1,8 +1,4 @@
import sys
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning, module="websockets")
warnings.filterwarnings("ignore", category=DeprecationWarning, module="uvicorn")
from flowllm.app import FlowLLMApp

View file

@ -62,10 +62,6 @@ flow:
flow_content: parse_tool_call_result_op >> update_vector_store_op
description: "Evaluates and adds tool call results to the tool memory database, creating new memory or updating existing memory for the specified tool"
input_schema:
tool_name:
type: string
description: "The name of the tool to add call results for"
required: true
tool_call_results:
type: array
description: "List of tool call result objects, each containing: tool_name, input, output, success, time_cost, token_cost, create_time"
@ -80,6 +76,15 @@ flow:
description: "Comma-separated tool names to summarize (e.g., 'tool_name1,tool_name2')"
required: true
use_mock_search:
flow_content: use_mock_search_op
description: "Simulates intelligent search tool selection and execution based on query complexity, with automatic tool memory recording"
input_schema:
query:
type: string
description: "User search query to process"
required: true
summary_task_memory_simple:
flow_content: simple_summary_op >> update_vector_store_op
description: "Summarizes conversation trajectories or messages into memories using a simplified approach"

View file

@ -0,0 +1 @@
from .retrieve_tool_memory_op import RetrieveToolMemoryOp

View file

@ -117,7 +117,7 @@ class ToolCallResult(BaseModel):
time_cost: float = Field(default=0, description="Time consumed by the tool invocation, in seconds")
summary: str = Field(default="", description="Brief summary of the tool call result")
evaluation: str = Field(default="", description="Detailed evaluation for the tool invocation")
score: float = Field(default=0, description="Score of the Evaluation (0.0, 0.5, or 1.0)")
score: float = Field(default=0, description="Score of the Evaluation (0.0 for failure, 1.0 for complete success)")
metadata: dict = Field(default_factory=dict)
@ -193,11 +193,9 @@ class ToolMemory(BaseMemory):
avg_score = total_score / recent_calls_count if recent_calls_count > 0 else 0.0
return {
"total_calls": total_calls,
"recent_calls_analyzed": recent_calls_count,
"avg_token_cost": round(avg_token_cost, 2),
"success_rate": round(success_rate, 4),
"avg_time_cost": round(avg_time_cost, 3),
"success_rate": round(success_rate, 4),
"avg_score": round(avg_score, 3)
}

View file

@ -1,4 +1,5 @@
import asyncio
from collections import defaultdict
from typing import List
from flowllm import C, BaseAsyncOp
@ -44,18 +45,19 @@ class ParseToolCallResultOp(BaseAsyncOp):
tool_call_result.evaluation = eval_data.get("evaluation", "")
tool_call_result.score = float(eval_data.get("score", 0.0))
# 验证 score 是否符合 3 档要求 (0.0, 0.5, 1.0)
if tool_call_result.score not in [0.0, 0.5, 1.0]:
logger.warning(f"Score {tool_call_result.score} not in [0.0, 0.5, 1.0], rounding to nearest")
if tool_call_result.score < 0.25:
# 验证 score 是否符合 2 档要求 (0.0, 1.0)
if tool_call_result.score not in [0.0, 1.0]:
if tool_call_result.score < 0.5:
tool_call_result.score = 0.0
elif tool_call_result.score < 0.75:
tool_call_result.score = 0.5
else:
tool_call_result.score = 1.0
logger.info(f"Evaluated tool call {index}: tool_name={tool_call_result.tool_name}, "
f"score={tool_call_result.score}, summary={tool_call_result.summary[:50]}...")
# 打印完整的prompt和result
logger.info(f"\n{'='*80}\nLLM Evaluation [Index {index}]\n{'='*80}\n"
f"PROMPT:\n{prompt}\n\n"
f"RESULT:\n{content}\n"
f"{'='*80}\n")
return tool_call_result
# 调用 LLM 进行评估
@ -66,79 +68,68 @@ class ParseToolCallResultOp(BaseAsyncOp):
async def async_execute(self):
tool_call_results: list = self.context.get("tool_call_results", [])
tool_call_results = [ToolCallResult(**x) if isinstance(x, dict) else x for x in tool_call_results]
tool_name: str = self.context.get("tool_name", "")
workspace_id: str = self.context.workspace_id
logger.info(f"workspace_id={workspace_id} count={len(tool_call_results)} tool_name={tool_name}")
if not tool_name:
logger.warning("tool_name is empty, skipping processing")
self.context.response.answer = "tool_name is required"
self.context.response.success = False
return
if not tool_call_results:
logger.info("No valid tool_call_results to process")
self.context.response.answer = "No valid tool_call_results"
self.context.response.success = False
return
# 并发评估所有 tool_call_results
logger.info(f"Starting concurrent evaluation of {len(tool_call_results)} tool call results")
# 使用基类的 submit_async_task 提交所有评估任务
for index, tool_call_result in enumerate(tool_call_results):
self.submit_async_task(self._evaluate_single_tool_call, tool_call_result, index)
# 使用基类的 join_async_task 等待所有任务完成
# 注意: 基类已经过滤掉异常,返回的只包含成功的结果
tool_call_results = await self.join_async_task(return_exceptions=True)
logger.info(f"Completed evaluation of {len(tool_call_results)} tool call results")
evaluated_results = await self.join_async_task(return_exceptions=True)
nodes: List[VectorNode] = await self.vector_store.async_search(query=tool_name,
workspace_id=workspace_id,
top_k=1)
tool_results_by_name = defaultdict(list)
for result in evaluated_results:
tool_results_by_name[result.tool_name].append(result)
tool_memory: ToolMemory | None = None
exist_node: bool = False
# 处理每个 tool_name 的结果
all_memory_list = []
all_deleted_memory_ids = []
if nodes:
top_node = nodes[0]
memory: ToolMemory = vector_node_to_memory(top_node)
for tool_name, tool_call_results in tool_results_by_name.items():
nodes: List[VectorNode] = await self.vector_store.async_search(query=tool_name,
workspace_id=workspace_id,
top_k=1)
# 确保是 ToolMemory 类型且 when_to_use 与 tool_name 匹配
if isinstance(memory, ToolMemory) and memory.when_to_use == tool_name:
tool_memory = memory
exist_node = True
logger.info(f"Found existing tool_memory for tool_name={tool_name}, memory_id={tool_memory.memory_id}")
else:
logger.info(f"Top result does not match tool_name={tool_name}, will create new memory")
tool_memory: ToolMemory | None = None
exist_node: bool = False
# 如果没有找到匹配的 memory创建新的
if tool_memory is None:
tool_memory = ToolMemory(workspace_id=workspace_id, when_to_use=tool_name)
logger.info(f"Created new tool_memory for tool_name={tool_name}, memory_id={tool_memory.memory_id}")
if nodes:
top_node = nodes[0]
memory: ToolMemory = vector_node_to_memory(top_node)
tool_memory.tool_call_results.extend(tool_call_results)
# 确保是 ToolMemory 类型且 when_to_use 与 tool_name 匹配
if isinstance(memory, ToolMemory) and memory.when_to_use == tool_name:
tool_memory = memory
exist_node = True
# 保留最近的 n 个
if len(tool_memory.tool_call_results) > self.max_history_tool_call_cnt:
tool_memory.tool_call_results = tool_memory.tool_call_results[-self.max_history_tool_call_cnt:]
logger.info(f"Trimmed tool_call_results to {self.max_history_tool_call_cnt} most recent entries")
# 如果没有找到匹配的 memory创建新的
if tool_memory is None:
tool_memory = ToolMemory(workspace_id=workspace_id, when_to_use=tool_name)
# 更新修改时间
tool_memory.update_modified_time()
tool_memory.tool_call_results.extend(tool_call_results)
# 4. 将更新后的 memory 保存到向量数据库
# 如果是更新现有的 memory需要先删除旧的
deleted_memory_ids = []
if exist_node:
deleted_memory_ids = [tool_memory.memory_id]
# 保留最近的 n 个
if len(tool_memory.tool_call_results) > self.max_history_tool_call_cnt:
tool_memory.tool_call_results = tool_memory.tool_call_results[-self.max_history_tool_call_cnt:]
# 更新修改时间
tool_memory.update_modified_time()
# 如果是更新现有的 memory需要先删除旧的
if exist_node:
all_deleted_memory_ids.append(tool_memory.memory_id)
all_memory_list.append(tool_memory)
# 设置返回结果
self.context.response.metadata["deleted_memory_ids"] = deleted_memory_ids
self.context.response.metadata["memory_list"] = [tool_memory]
logger.info(f"Updated tool_memory: tool_name={tool_name}, total_results={len(tool_memory.tool_call_results)}")
self.context.response.metadata["deleted_memory_ids"] = all_deleted_memory_ids
self.context.response.metadata["memory_list"] = all_memory_list
async def main():
@ -166,10 +157,9 @@ async def main():
"time_cost": 2.3
}
]
tool_name = "test_tool"
workspace_id = "test_workspace1"
await op.async_call(tool_call_results=tool_call_results, tool_name=tool_name, workspace_id=workspace_id)
await op.async_call(tool_call_results=tool_call_results, workspace_id=workspace_id)
logger.info(f"Response: {op.context.response.model_dump_json()}")

View file

@ -14,17 +14,23 @@ evaluate_tool_call_prompt: |
## Evaluation Criteria:
### 1. Success Evaluation
- Check if the success flag indicates successful execution
- Verify if the output contains error messages or indicates failure
- Consider if time and token costs are reasonable
- Assess if there are any error indicators in the output
### Important: Score independently from the success flag
The `success_flag` indicates whether the tool executed without technical errors.
The `score` should evaluate the QUALITY and RELEVANCE of the result.
### 2. Parameter Alignment Evaluation
- Evaluate if the output matches what would be expected given the input parameters
- Consider if the input parameters are appropriate for the tool
- Check for any parameter mismatches or unexpected behaviors
- Verify the output is consistent with the tool's intended purpose
A tool can execute successfully (success=True) but still produce low-quality or irrelevant results (score=0.0).
### Evaluation Dimensions:
1. **Technical Execution** (reflected in success_flag):
- Did the tool run without errors?
- Are there error messages or exceptions in the output?
2. **Result Quality** (what you should score):
- Is the output relevant to the input query/parameters?
- Does the output provide meaningful and useful information?
- Is the result appropriate given the input parameters?
- Does the output match the tool's intended purpose?
## Response Format:
Please provide your evaluation in the following JSON format:
@ -32,15 +38,20 @@ evaluate_tool_call_prompt: |
```json
{{
"summary": "A brief one-sentence summary of the tool call result",
"evaluation": "A brief evaluation (2-3 sentences) explaining the success status and parameter alignment.",
"evaluation": "A brief evaluation (2-3 sentences) explaining the result quality and relevance, NOT just repeating the success flag.",
"score": 1.0
}}
```
## Scoring Guidelines:
- **1.0**: Success - Tool executed successfully with good parameter alignment
- **0.5**: Partial Success - Tool executed but with parameter alignment issues or minor errors
- **0.0**: Failure - Tool execution failed or severe parameter misalignment
## Scoring Guidelines (Focus on Result Quality):
- **1.0**: High Quality - The output is relevant, useful, and appropriate for the given input parameters
- **0.0**: Low Quality - The output is irrelevant, incorrect, unhelpful, or inappropriate for the input
Please evaluate carefully and provide a score of exactly 0.0, 0.5, or 1.0.
## Examples:
- success=True, but output is "No results found" for a reasonable query → score=0.0 (technically succeeded but unhelpful)
- success=True, but output contains generic/irrelevant information → score=0.0 (poor quality)
- success=True, and output provides relevant, useful information → score=1.0 (good quality)
- success=False, with error messages → score=0.0 (failed execution)
Please evaluate carefully and provide a score of exactly 0.0 or 1.0 based on OUTPUT QUALITY, not just the success flag.

View file

@ -45,12 +45,8 @@ class SummaryToolMemoryOp(BaseAsyncOp):
@staticmethod
def _format_statistics_markdown(statistics: dict) -> str:
"""Format statistics as markdown."""
lines = [f"- **Total Calls**: {statistics.get('total_calls', 0)}",
f"- **Recent Calls**: {statistics.get('recent_calls', 0)}",
f"- **Success Rate**: {statistics.get('success_rate', 0):.2%}",
f"- **Recent Success Rate**: {statistics.get('recent_success_rate', 0):.2%}",
lines = [f"- **Success Rate**: {statistics.get('success_rate', 0):.2%}",
f"- **Average Score**: {statistics.get('avg_score', 0):.3f}",
f"- **Recent Average Score**: {statistics.get('recent_avg_score', 0):.3f}",
f"- **Average Time Cost**: {statistics.get('avg_time_cost', 0):.3f}s",
f"- **Average Token Cost**: {statistics.get('avg_token_cost', 0):.1f}"]
@ -73,15 +69,18 @@ class SummaryToolMemoryOp(BaseAsyncOp):
call_summaries_md = self._format_call_summaries_markdown(recent_calls)
statistics_md = self._format_statistics_markdown(statistics)
# Don't include statistics in prompt - only call summaries
prompt = self.prompt_format(prompt_name="summarize_tool_usage_prompt",
tool_name=tool_memory.when_to_use,
call_summaries=call_summaries_md,
statistics=statistics_md)
call_summaries=call_summaries_md)
def parse_summary(message: Message) -> ToolMemory:
content = message.content.strip()
# Extract content from txt code block
tool_memory.content = extract_content(content, "txt")
llm_summary = extract_content(content, "txt")
# Append statistics markdown to LLM result
tool_memory.content = f"{llm_summary}\n\n## Statistics\n{statistics_md}"
# Update modified time
tool_memory.update_modified_time()
@ -334,7 +333,7 @@ async def main():
logger.info(f"\n工具名称: {summarized_memory.when_to_use}")
logger.info(f"\n统计信息:")
stats = summarized_memory.statistic(recent_frequency=30)
logger.info(f" 总调用次数: {stats['total_calls']}")
logger.info(f" 总调用次数: {len(summarized_memory.tool_call_results)}")
logger.info(f" 成功率: {stats['success_rate']:.1%}")
logger.info(f" 平均评分: {stats['avg_score']:.2f}")
logger.info(f" 平均耗时: {stats['avg_time_cost']:.2f}s")

View file

@ -7,14 +7,11 @@ summarize_tool_usage_prompt: |
## Recent Tool Call History:
{call_summaries}
## Statistical Summary:
{statistics}
## Your Task:
Based on the tool call history, generate a concise and logical tool usage description following this structure:
1. **Core Function**: What this tool does and when to use it
2. **Success Patterns**: Parameter patterns and usage scenarios that work well (with key metrics)
2. **Success Patterns**: Parameter patterns and usage scenarios that work well
3. **Common Issues**: Main pitfalls to avoid and why they fail
4. **Best Practices**: 2-3 actionable recommendations

View file

@ -0,0 +1,195 @@
import random
from datetime import datetime, timedelta
from typing import List, Dict, Any
def create_mock_tool_call_results(tool_name: str, count: int = 30) -> List[Dict[str, Any]]:
"""
Create mock tool call results for testing
Args:
tool_name: Name of the tool to create results for
count: Number of mock results to create
Returns:
List of tool call result dictionaries
"""
base_time = datetime.now() - timedelta(days=7)
tool_call_results = []
if tool_name == "web_search":
# Scenario 1: Successful searches (15 calls)
success_queries = [
"Python asyncio tutorial", "machine learning basics", "React hooks guide",
"Docker best practices", "SQL optimization tips", "Git workflow strategies",
"RESTful API design", "microservices architecture", "Redis caching patterns",
"Kubernetes deployment", "GraphQL advantages", "MongoDB schema design",
"JWT authentication", "OAuth2 flow", "WebSocket real-time"
]
for i, query in enumerate(success_queries):
tool_call_results.append({
"create_time": (base_time + timedelta(hours=i * 2)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"query": query,
"max_results": random.randint(5, 20),
"language": "en",
"filter_type": "technical_docs"
},
"output": f"Found {random.randint(8, 20)} relevant results for '{query}'. Top results include official documentation, tutorials, and best practice guides.",
"token_cost": random.randint(100, 300),
"success": True,
"time_cost": round(random.uniform(1.5, 3.5), 2)
})
# Scenario 2: Poor parameters (8 calls)
for i in range(8):
tool_call_results.append({
"create_time": (base_time + timedelta(hours=30 + i * 3)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"query": f"test query {i}",
"max_results": 100,
"language": "unknown",
},
"output": f"Warning: language 'unknown' not supported, using default. Query too generic, returning limited results. Found {random.randint(2, 5)} results.",
"token_cost": random.randint(50, 150),
"success": True,
"time_cost": round(random.uniform(2.0, 4.0), 2)
})
# Scenario 3: Timeouts or failures (5 calls)
for i in range(5):
tool_call_results.append({
"create_time": (base_time + timedelta(hours=54 + i * 4)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"query": f"extremely complex query with many conditions {i}",
"max_results": 50,
"language": "en",
"filter_type": "all"
},
"output": "Error: Request timeout after 10 seconds. Try simplifying the query or reducing max_results.",
"token_cost": 20,
"success": False,
"time_cost": 10.0
})
# Scenario 4: Empty results (2 calls)
for i in range(2):
tool_call_results.append({
"create_time": (base_time + timedelta(hours=74 + i * 5)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"query": f"xyzabc123nonexistent{i}",
"max_results": 10,
"language": "en",
},
"output": "No results found for the given query. Please try different keywords.",
"token_cost": 30,
"success": True,
"time_cost": 1.2
})
elif tool_name == "database_query":
# Scenario 1: Successful queries (12 calls)
for i in range(12):
tool_call_results.append({
"create_time": (base_time + timedelta(hours=i * 3)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"table": f"users_{i % 3}",
"query": f"SELECT * FROM table WHERE id > {i * 10}",
"limit": random.randint(10, 100)
},
"output": f"Query executed successfully. Returned {random.randint(5, 50)} rows in {round(random.uniform(0.1, 0.5), 3)}s.",
"token_cost": random.randint(20, 80),
"success": True,
"time_cost": round(random.uniform(0.1, 0.5), 3)
})
# Scenario 2: Slow queries (6 calls)
for i in range(6):
tool_call_results.append({
"create_time": (base_time + timedelta(hours=36 + i * 4)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"table": "large_table",
"query": f"SELECT * FROM large_table WHERE name LIKE '%pattern%' ORDER BY created_at",
"limit": 1000
},
"output": f"Query executed but took longer than expected. Returned {random.randint(100, 1000)} rows. Consider adding indexes.",
"token_cost": random.randint(50, 150),
"success": True,
"time_cost": round(random.uniform(5.0, 10.0), 2)
})
# Scenario 3: Query errors (4 calls)
for i in range(4):
tool_call_results.append({
"create_time": (base_time + timedelta(hours=60 + i * 5)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"table": "invalid_table",
"query": f"SELECT * FROM invalid_table WHERE bad_column = {i}",
"limit": 10
},
"output": f"Error: Table 'invalid_table' does not exist or column 'bad_column' not found.",
"token_cost": 10,
"success": False,
"time_cost": 0.05
})
elif tool_name == "file_processor":
# Scenario 1: Successful file processing (10 calls)
file_types = ["csv", "json", "xml", "txt", "pdf"]
for i in range(10):
file_type = file_types[i % len(file_types)]
tool_call_results.append({
"create_time": (base_time + timedelta(hours=i * 4)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"file_path": f"/data/file_{i}.{file_type}",
"operation": "read",
"encoding": "utf-8"
},
"output": f"Successfully processed {file_type.upper()} file. Size: {random.randint(100, 5000)}KB, Records: {random.randint(100, 10000)}",
"token_cost": random.randint(50, 200),
"success": True,
"time_cost": round(random.uniform(1.0, 3.0), 2)
})
# Scenario 2: Large file warnings (5 calls)
for i in range(5):
tool_call_results.append({
"create_time": (base_time + timedelta(hours=40 + i * 6)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"file_path": f"/data/large_file_{i}.csv",
"operation": "read",
"encoding": "utf-8"
},
"output": f"Warning: Large file detected ({random.randint(50, 200)}MB). Processing may take longer. Consider using streaming mode.",
"token_cost": random.randint(200, 500),
"success": True,
"time_cost": round(random.uniform(10.0, 30.0), 2)
})
# Scenario 3: File not found (3 calls)
for i in range(3):
tool_call_results.append({
"create_time": (base_time + timedelta(hours=70 + i * 8)).strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": tool_name,
"input": {
"file_path": f"/invalid/path/file_{i}.txt",
"operation": "read",
"encoding": "utf-8"
},
"output": "Error: File not found. Please check the file path and ensure the file exists.",
"token_cost": 10,
"success": False,
"time_cost": 0.01
})
return tool_call_results[:count]