From 2fa06ec0af266e2f4f46b667e1cc78fe119626e1 Mon Sep 17 00:00:00 2001 From: opencode Date: Mon, 23 Mar 2026 16:20:37 +0100 Subject: [PATCH] fix: migliora parsing e system prompt per tool calling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nuovo formato TOOL:toolname|arg=val più semplice - System prompt semplificato con lista chiara tool - Parsing supporta sia formato nuovo che vecchio - Validazione tool names esistenti --- ollama_agent.py | 81 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/ollama_agent.py b/ollama_agent.py index fdee0bb..95df299 100644 --- a/ollama_agent.py +++ b/ollama_agent.py @@ -21,7 +21,12 @@ from langchain_core.tools import Tool # TOOL CALL PATTERNS # ============================================================================= -TOOL_CALL_PATTERN = r'\[TOOL_CALL\]\s*(.*?)\s*\[\/TOOL_CALL\]' +# Old format: [TOOL_CALL]...[/TOOL_CALL] +TOOL_CALL_PATTERN_OLD = r'\[TOOL_CALL\]\s*(.*?)\s*\[\/TOOL_CALL\]' + +# New format: TOOL:toolname|arg1=val1|arg2=val2 +TOOL_CALL_PATTERN_NEW = r'^TOOL:(\w+)(?:\|(.*))?$' + TOOL_RESULT_PATTERN = r'\[TOOL_RESULT\]\s*(.*?)\s*\[\/TOOL_RESULT\]' @@ -227,17 +232,29 @@ class OllamaToolAgent: def _default_system_prompt(self) -> str: """System prompt di default""" - return """You are LangCode, a helpful AI assistant with access to tools. + return """You are LangCode AI. You have access to these tools: -When you need to use a tool, you MUST respond with this EXACT format: +1. system_info - Get system info (OS, Python version, etc). No arguments needed. +2. ls(path=".") - List directory contents. Use path="." for current directory. +3. execute(command) - Run shell commands. Example: execute("ls -la") +4. read_file(path) - Read file contents. Example: read_file("/path/to/file") +5. write_file(path, content) - Write to file. +6. search(directory, pattern) - Search for files. Example: search(".", "*.py") -[TOOL_CALL] -{"tool": "tool_name", "arguments": {"arg1": "value1", "arg2": "value2"}} -[/TOOL_CALL] +IMPORTANT RULES: +- ONLY use these exact tool names: system_info, ls, execute, read_file, write_file, search +- NEVER invent tool names +- When you need a tool, output ONLY this format (no other text): -After receiving the tool result, continue your response normally. +TOOL:system_info +TOOL:ls|path=. +TOOL:execute|command=ls -la +TOOL:read_file|path=/path/to/file +TOOL:write_file|path=/file.txt|content=hello +TOOL:search|directory=.|pattern=*.py -Available tools:""" +- After the tool result, you can respond normally. +- If no tool is needed, just respond normally.""" def _tool_instructions(self) -> str: """Genera le istruzioni per i tool disponibili""" @@ -286,17 +303,45 @@ Available tools:""" def _parse_tool_calls(self, text: str) -> List[ToolCall]: """Parse le chiamate tool dalla risposta del modello""" tool_calls = [] - matches = re.finditer(TOOL_CALL_PATTERN, text, re.DOTALL) + # Try new format first: TOOL:toolname|arg1=val1 + lines = text.strip().split('\n') + for line in lines: + line = line.strip() + if line.startswith('TOOL:'): + # Parse: TOOL:toolname|arg1=val1|arg2=val2 + match = re.match(r'^TOOL:(\w+)(?:\|(.+))?$', line) + if match: + tool_name = match.group(1) + if tool_name in self.tools: + args = {} + if match.group(2): + # Parse arguments: arg1=val1|arg2=val2 + for arg_part in match.group(2).split('|'): + if '=' in arg_part: + key, val = arg_part.split('=', 1) + args[key] = val + tool_calls.append(ToolCall( + id=str(uuid.uuid4())[:8], + name=tool_name, + arguments=args + )) + # If we found a valid tool call, return only that one + return tool_calls + + # Try old format: [TOOL_CALL]{...}[/TOOL_CALL] + matches = re.finditer(TOOL_CALL_PATTERN_OLD, text, re.DOTALL) for match in matches: try: data = json.loads(match.group(1)) - tool_calls.append(ToolCall( - id=str(uuid.uuid4())[:8], - name=data.get('tool'), - arguments=data.get('arguments', {}) - )) - except json.JSONDecodeError: + tool_name = data.get('tool') + if tool_name and tool_name in self.tools: + tool_calls.append(ToolCall( + id=str(uuid.uuid4())[:8], + name=tool_name, + arguments=data.get('arguments', {}) + )) + except (json.JSONDecodeError, TypeError): continue return tool_calls @@ -304,7 +349,11 @@ Available tools:""" def _execute_tool(self, tool_call: ToolCall) -> Any: """Esegui un singolo tool""" if tool_call.name not in self.tools: - return {"error": f"Tool '{tool_call.name}' not found"} + available = ", ".join(self.tools.keys()) + return { + "error": f"Tool '{tool_call.name}' not found", + "available_tools": available + } try: tool = self.tools[tool_call.name]