feat: universal tool support - inject instructions by default, add plan mode TODO, improve file handling
1. Tool instructions now ALWAYS injected by default: - Removed condition that only injected on first request - Any client (Continue, hollama) can now use tools without client-side setup - Added check to avoid duplicating instructions if already present 2. Updated tool instructions with file verification guidance: - Added 'FILE OPERATIONS - ALWAYS VERIFY FIRST' section - Instructs to use 'ls' and 'grep' to verify files exist before reading - Prevents blind file reads on non-existent paths 3. Added TODO to README: - Plan mode feature (disable tool execution for planning-only conversations) - Current status section showing what's implemented 4. Working directory extraction from prompts: - New _extract_working_dir_from_prompt() function - Extracts paths from patterns like 'in /path/to/dir', 'under /path/to/dir' - Validates paths exist before using - Falls back to auto-detection if not found in prompt - All 41 tests passing
This commit is contained in:
@@ -282,6 +282,22 @@ Major refactoring completed to improve modularity:
|
||||
|
||||
See `docs/ARCHITECTURE.md` for detailed architecture documentation.
|
||||
|
||||
## TODO / Roadmap
|
||||
|
||||
### Planned Features
|
||||
|
||||
- **Plan Mode**: Add a "plan mode" that disables tool execution for planning-only conversations. This would allow the model to discuss file changes without actually modifying them until explicitly confirmed.
|
||||
- Usage: `--plan-mode` flag or API parameter
|
||||
- When enabled: Model can see what tools would do but doesn't execute them
|
||||
- Use case: Review changes before applying them
|
||||
|
||||
### Current Status
|
||||
|
||||
- ✅ Tool instructions now injected by default for all clients
|
||||
- ✅ Improved file operation safety (verify with ls/grep before reading)
|
||||
- ✅ Working directory support (extracted from client context)
|
||||
- 🔄 Plan mode - coming soon
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please ensure:
|
||||
|
||||
@@ -15,6 +15,13 @@ CRITICAL RULES:
|
||||
6. NO explanations beyond necessary. Be concise.
|
||||
7. NO markdown formatting. Use plain text only.
|
||||
|
||||
FILE OPERATIONS - ALWAYS VERIFY FIRST:
|
||||
Before reading a file, ALWAYS verify it exists using 'ls' or 'grep':
|
||||
- Use 'ls' to list directory contents and confirm the file exists
|
||||
- Use 'grep' to search for files containing specific content
|
||||
- Only use 'read' AFTER confirming the file exists
|
||||
- If the user mentions a working directory, use paths relative to that directory
|
||||
|
||||
TOOL USAGE FORMAT:
|
||||
|
||||
For read operations:
|
||||
@@ -25,7 +32,7 @@ For write operations:
|
||||
TOOL: write
|
||||
ARGUMENTS: {"filePath": "path/to/file", "content": "content to write"}
|
||||
|
||||
For bash commands:
|
||||
For bash commands (including ls, grep):
|
||||
TOOL: bash
|
||||
ARGUMENTS: {"command": "your command here"}
|
||||
|
||||
|
||||
@@ -21,12 +21,53 @@ from api.tool_parser import parse_tool_calls
|
||||
from utils.token_counter import count_tokens
|
||||
from tools.executor import get_tool_executor
|
||||
from chatlog import get_chat_logger
|
||||
from chatlog import get_chat_logger
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _extract_working_dir_from_prompt(prompt: str) -> Optional[str]:
|
||||
"""Extract working directory from user prompt.
|
||||
|
||||
Looks for patterns like:
|
||||
- "in the /path/to/dir directory"
|
||||
- "in directory /path/to/dir"
|
||||
- "in /path/to/dir"
|
||||
- "under /path/to/dir"
|
||||
- "from /path/to/dir"
|
||||
|
||||
Args:
|
||||
prompt: User prompt text
|
||||
|
||||
Returns:
|
||||
Extracted directory path or None
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
|
||||
# Common patterns for directory mentions
|
||||
patterns = [
|
||||
r'in the\s+([/~]?[\w\-/.]+)\s+(?:directory|folder|dir)',
|
||||
r'in\s+(?:directory|folder|dir)\s+([/~]?[\w\-/.]+)',
|
||||
r'(?:in|under|from|at)\s+([/~]?[\w\-/.]{3,})', # At least 3 chars to avoid "in a"
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
match = re.search(pattern, prompt, re.IGNORECASE)
|
||||
if match:
|
||||
path = match.group(1)
|
||||
# Validate it looks like a path
|
||||
if path.startswith('/') or path.startswith('~') or '/' in path:
|
||||
# Expand home directory
|
||||
if path.startswith('~'):
|
||||
path = os.path.expanduser(path)
|
||||
# Check if it's a valid directory or parent exists
|
||||
if os.path.isdir(path) or os.path.isdir(os.path.dirname(path)):
|
||||
return os.path.abspath(path)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _sanitize_tools(tools: Optional[list]) -> Optional[list]:
|
||||
"""Sanitize tool definitions to fix invalid schemas.
|
||||
|
||||
@@ -290,6 +331,17 @@ async def handle_chat_completion(
|
||||
# Initialize chat logger (if enabled via LOCAL_SWARM_CHATLOG=1)
|
||||
chat_logger = get_chat_logger()
|
||||
|
||||
# Extract working directory from prompt if not provided by client
|
||||
if client_working_dir is None:
|
||||
# Try to extract from user messages
|
||||
for msg in reversed(request.messages):
|
||||
if msg.role == 'user':
|
||||
extracted_dir = _extract_working_dir_from_prompt(msg.content)
|
||||
if extracted_dir:
|
||||
client_working_dir = extracted_dir
|
||||
logger.info(f"📁 Extracted working directory from prompt: {client_working_dir}")
|
||||
break
|
||||
|
||||
# Log initial conversation history to chatlog
|
||||
for msg in request.messages:
|
||||
if msg.role == 'user':
|
||||
|
||||
+13
-7
@@ -153,7 +153,13 @@ def _filter_messages(messages: List[ChatMessage]) -> List[ChatMessage]:
|
||||
|
||||
|
||||
def _add_tool_instructions(messages: List[ChatMessage]) -> List[ChatMessage]:
|
||||
"""Add tool instructions to messages if needed.
|
||||
"""Add tool instructions to the beginning of messages.
|
||||
|
||||
Tool instructions are now ALWAYS injected by default so any client
|
||||
(Continue, hollama, etc.) can use tools without requiring client-side
|
||||
tool instruction injection.
|
||||
|
||||
TODO: Add a "plan mode" that disables tool use for planning-only conversations.
|
||||
|
||||
Args:
|
||||
messages: List of chat messages
|
||||
@@ -161,13 +167,13 @@ def _add_tool_instructions(messages: List[ChatMessage]) -> List[ChatMessage]:
|
||||
Returns:
|
||||
Messages with tool instructions added
|
||||
"""
|
||||
has_assistant = any(msg.role == "assistant" for msg in messages)
|
||||
|
||||
if has_assistant:
|
||||
return messages
|
||||
|
||||
tool_instructions = _load_tool_instructions()
|
||||
logger.debug(f"Using {'opencode' if _USE_OPENCODE_TOOLS else 'local'} tool mode: {len(tool_instructions)} chars")
|
||||
logger.debug(f"Injecting tool instructions: {len(tool_instructions)} chars")
|
||||
|
||||
# Check if instructions already present (avoid duplication)
|
||||
if messages and messages[0].role == "system" and "AVAILABLE TOOLS" in messages[0].content:
|
||||
logger.debug("Tool instructions already present, skipping injection")
|
||||
return messages
|
||||
|
||||
return [ChatMessage(role="system", content=tool_instructions)] + messages
|
||||
|
||||
|
||||
Reference in New Issue
Block a user