Files
zeekay a3b58c011f chore: fix ruff linting issues across all hanzo-tools packages
- Fix import sorting (I001) in all packages
- Fix bare except (E722) in lsp_tool.py
- Fix duplicate set item (B033) in refactor_tool.py
2025-12-26 04:11:10 -08:00

179 lines
6.6 KiB
Python

"""Prompt generation utilities for agent tool.
This module provides functions for generating effective prompts for sub-agents,
including filtering tools based on permissions and formatting system instructions.
"""
import os
from typing import Any
from hanzo_tools.core import BaseTool, PermissionManager
def get_allowed_agent_tools(
tools: list[BaseTool],
permission_manager: PermissionManager,
) -> list[BaseTool]:
"""Filter tools available to the agent based on permissions.
Args:
tools: List of available tools
permission_manager: Permission manager for checking tool access
Returns:
Filtered list of tools available to the agent
"""
# Get all tools except for the agent tool itself (avoid recursion)
filtered_tools = [tool for tool in tools if tool.name != "agent"]
return filtered_tools
def get_system_prompt(
tools: list[BaseTool],
permission_manager: PermissionManager,
) -> str:
"""Generate system prompt for the sub-agent.
Args:
tools: List of available tools
permission_manager: Permission manager for checking tool access
Returns:
System prompt for the sub-agent
"""
# Get filtered tools
filtered_tools = get_allowed_agent_tools(tools, permission_manager)
# Extract tool names for display
tool_names = ", ".join(f"`{tool.name}`" for tool in filtered_tools)
# Base system prompt - agents always have edit tools
system_prompt = f"""You are a Claude sub-agent with access to these tools: {tool_names}.
CAPABILITIES:
1. You have FULL read and write access - you can create, edit, and modify files
2. You can ask clarifying questions if needed - your response goes to the coordinating agent
3. You work as part of a team of specialized agents
4. Other agents may be available via MCP tools (look for tools named after agents)
5. When relevant, share file names and code snippets
6. Any file paths you return MUST be absolute. DO NOT use relative paths.
7. You can only work with the absolute paths provided in your task prompt.
CLARIFICATION:
- You can request clarification ONCE per task using the request_clarification tool
- Use this when instructions are ambiguous or you need additional context
- Types: AMBIGUOUS_INSTRUCTION, MISSING_CONTEXT, MULTIPLE_OPTIONS, CONFIRMATION_NEEDED, ADDITIONAL_INFO
- The main loop will provide automated guidance based on context
CRITICAL REVIEW (Devil's Advocate):
- Use the critic tool to get harsh, challenging feedback that attacks assumptions
- The critic will find flaws and push for improvements aggressively
- You can request up to 2 critic reviews per task
- Review types: CODE_QUALITY, CORRECTNESS, PERFORMANCE, SECURITY, COMPLETENESS, BEST_PRACTICES, GENERAL
- Use when you need someone to find what's wrong with your approach
BALANCED REVIEW:
- Use the review tool for constructive, balanced code review
- Provides objective assessment without predetermined bias
- You can request up to 3 reviews per task
- Focus areas: GENERAL, FUNCTIONALITY, READABILITY, MAINTAINABILITY, TESTING, DOCUMENTATION, ARCHITECTURE
- Use for regular code review and feedback
CREATIVE GUIDANCE:
- Use the iching tool when you need creative problem-solving approaches
- Combines ancient I Ching wisdom with Hanzo engineering principles
- Provides unique perspectives and actionable guidance
- Use when stuck, need fresh ideas, or want philosophical alignment
EDITING GUIDELINES:
- ALWAYS read the file first before attempting any edits
- For edit tool: The old_string must match EXACTLY including all whitespace, tabs, and newlines
- When copying text from read output, be careful with line numbers and indentation
- If an edit fails due to whitespace mismatch, try reading the specific lines again
- Prefer multi_edit when making multiple changes to the same file
- Test your edits by verifying the exact string exists in the file first
COLLABORATION:
- If you see MCP tools named after other agents, you can communicate with them
- Use agent MCP tools to delegate specialized tasks or get expert opinions
- Share context when communicating with other agents
RESPONSE FORMAT:
- Begin with a summary of what you did or found
- If you have questions, ask them clearly
- Include details of any edits performed
- Report any errors encountered
- End with clear conclusions or next steps
"""
return system_prompt
def get_default_model(model_override: str | None = None) -> str:
"""Get the default model for agent execution.
Args:
model_override: Optional model override string in LiteLLM format (e.g., "openai/gpt-4o")
Returns:
Model identifier string with provider prefix
"""
# Use model override if provided
if model_override:
# If in testing mode and using a test model, return as-is
if model_override.startswith("test-model") or "TEST_MODE" in os.environ:
return model_override
# If the model already has a provider prefix, return as-is
if "/" in model_override:
return model_override
# Otherwise, add the default provider prefix
provider = os.environ.get("AGENT_PROVIDER", "openai")
return f"{provider}/{model_override}"
# Fall back to environment variables
# Default to Sonnet for cost efficiency
model = os.environ.get("AGENT_MODEL", "claude-3-5-sonnet-20241022")
# Special cases for tests
if model.startswith("test-model") or "TEST_MODE" in os.environ and model == "claude-3-5-sonnet-20241022":
return model
provider = os.environ.get("AGENT_PROVIDER", "anthropic")
# Only add provider prefix if it's not already in the model name
if "/" not in model and provider != "anthropic":
return f"{provider}/{model}"
elif "/" not in model and provider == "anthropic":
return f"anthropic/{model}"
elif "/" not in model:
return f"openai/{model}"
else:
# Model already has a provider prefix
return model
def get_model_parameters(max_tokens: int | None = None) -> dict[str, Any]:
"""Get model parameters from environment variables.
Args:
max_tokens: Optional maximum tokens parameter override
Returns:
Dictionary of model parameters
"""
params = {
"temperature": float(os.environ.get("AGENT_TEMPERATURE", "0.7")),
"timeout": int(os.environ.get("AGENT_API_TIMEOUT", "60")),
}
# Add max_tokens if provided or if set in environment variable
if max_tokens is not None:
params["max_tokens"] = max_tokens
elif os.environ.get("AGENT_MAX_TOKENS"):
params["max_tokens"] = int(os.environ.get("AGENT_MAX_TOKENS", "1000"))
return params