fix: migliora parsing e system prompt per tool calling
- 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
This commit is contained in:
1 parent
a309c7e27c
commit
2fa06ec0af
1 file changed
+65
-16
+65
-16
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user