Unfucked gemini's mess
This commit is contained in:
+114
-109
@@ -1,31 +1,36 @@
|
||||
"""Prompt builder for the agent system."""
|
||||
|
||||
from typing import Dict, List, Any
|
||||
import json
|
||||
|
||||
from infrastructure.persistence import get_memory
|
||||
|
||||
from .parameters import format_parameters_for_prompt, get_missing_required_parameters
|
||||
from .registry import Tool
|
||||
from infrastructure.persistence import get_memory
|
||||
|
||||
|
||||
class PromptBuilder:
|
||||
"""Builds system prompts for the agent with memory context.
|
||||
"""Builds system prompts for the agent with memory context."""
|
||||
|
||||
Attributes:
|
||||
tools: Dictionary of available tools.
|
||||
"""
|
||||
|
||||
def __init__(self, tools: dict[str, Tool]):
|
||||
"""
|
||||
Initialize the prompt builder.
|
||||
|
||||
Args:
|
||||
tools: Dictionary mapping tool names to Tool instances.
|
||||
"""
|
||||
def __init__(self, tools: Dict[str, Tool]):
|
||||
self.tools = tools
|
||||
|
||||
def build_tools_spec(self) -> List[Dict[str, Any]]:
|
||||
"""Build the tool specification for the LLM API."""
|
||||
tool_specs = []
|
||||
for tool in self.tools.values():
|
||||
spec = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"parameters": tool.parameters,
|
||||
},
|
||||
}
|
||||
tool_specs.append(spec)
|
||||
return tool_specs
|
||||
|
||||
def _format_tools_description(self) -> str:
|
||||
"""Format tools with their descriptions and parameters."""
|
||||
if not self.tools:
|
||||
return ""
|
||||
return "\n".join(
|
||||
f"- {tool.name}: {tool.description}\n"
|
||||
f" Parameters: {json.dumps(tool.parameters, ensure_ascii=False)}"
|
||||
@@ -37,134 +42,134 @@ class PromptBuilder:
|
||||
memory = get_memory()
|
||||
lines = []
|
||||
|
||||
# Last search results
|
||||
if memory.episodic.last_search_results:
|
||||
search = memory.episodic.last_search_results
|
||||
lines.append(f"LAST SEARCH: '{search.get('query')}'")
|
||||
results = search.get("results", [])
|
||||
if results:
|
||||
lines.append(f" {len(results)} results available:")
|
||||
for r in results[:5]:
|
||||
name = r.get("name", r.get("title", "Unknown"))
|
||||
lines.append(f" {r.get('index')}. {name}")
|
||||
if len(results) > 5:
|
||||
lines.append(f" ... and {len(results) - 5} more")
|
||||
results = memory.episodic.last_search_results
|
||||
result_list = results.get('results', [])
|
||||
lines.append(f"\nLAST SEARCH: '{results.get('query')}' ({len(result_list)} results)")
|
||||
# Show first 5 results
|
||||
for i, result in enumerate(result_list[:5]):
|
||||
name = result.get('name', 'Unknown')
|
||||
lines.append(f" {i+1}. {name}")
|
||||
if len(result_list) > 5:
|
||||
lines.append(f" ... and {len(result_list) - 5} more")
|
||||
|
||||
# Pending question
|
||||
if memory.episodic.pending_question:
|
||||
q = memory.episodic.pending_question
|
||||
lines.append(f"\nPENDING QUESTION: {q.get('question')}")
|
||||
for opt in q.get("options", []):
|
||||
lines.append(f" {opt.get('index')}. {opt.get('label')}")
|
||||
question = memory.episodic.pending_question
|
||||
lines.append(f"\nPENDING QUESTION: {question.get('question')}")
|
||||
lines.append(f" Type: {question.get('type')}")
|
||||
if question.get('options'):
|
||||
lines.append(f" Options: {len(question.get('options'))}")
|
||||
|
||||
# Active downloads
|
||||
if memory.episodic.active_downloads:
|
||||
lines.append(f"\nACTIVE DOWNLOADS: {len(memory.episodic.active_downloads)}")
|
||||
for dl in memory.episodic.active_downloads[:3]:
|
||||
lines.append(f" - {dl.get('name')}: {dl.get('progress', 0)}%")
|
||||
lines.append(f" - {dl.get('name')}: {dl.get('progress', 0)}%")
|
||||
|
||||
# Recent errors
|
||||
if memory.episodic.recent_errors:
|
||||
last_error = memory.episodic.recent_errors[-1]
|
||||
lines.append(
|
||||
f"\nLAST ERROR: {last_error.get('error')} "
|
||||
f"(action: {last_error.get('action')})"
|
||||
)
|
||||
lines.append("\nRECENT ERRORS (up to 3):")
|
||||
for error in memory.episodic.recent_errors[-3:]:
|
||||
lines.append(f" - Action '{error.get('action')}' failed: {error.get('error')}")
|
||||
|
||||
# Unread events
|
||||
unread = [e for e in memory.episodic.background_events if not e.get("read")]
|
||||
unread = [e for e in memory.episodic.background_events if not e.get('read')]
|
||||
if unread:
|
||||
lines.append(f"\nUNREAD EVENTS: {len(unread)}")
|
||||
for e in unread[:3]:
|
||||
lines.append(f" - {e.get('type')}: {e.get('data', {})}")
|
||||
for event in unread[:3]:
|
||||
lines.append(f" - {event.get('type')}: {event.get('data')}")
|
||||
|
||||
return "\n".join(lines) if lines else ""
|
||||
return "\n".join(lines)
|
||||
|
||||
def _format_stm_context(self) -> str:
|
||||
"""Format short-term memory context for the prompt."""
|
||||
memory = get_memory()
|
||||
lines = []
|
||||
|
||||
# Current workflow
|
||||
if memory.stm.current_workflow:
|
||||
wf = memory.stm.current_workflow
|
||||
lines.append(f"CURRENT WORKFLOW: {wf.get('type')}")
|
||||
lines.append(f" Target: {wf.get('target', {}).get('title', 'Unknown')}")
|
||||
lines.append(f" Stage: {wf.get('stage')}")
|
||||
workflow = memory.stm.current_workflow
|
||||
lines.append(f"CURRENT WORKFLOW: {workflow.get('type')} (stage: {workflow.get('stage')})")
|
||||
if workflow.get('target'):
|
||||
lines.append(f" Target: {workflow.get('target')}")
|
||||
|
||||
# Current topic
|
||||
if memory.stm.current_topic:
|
||||
lines.append(f"CURRENT TOPIC: {memory.stm.current_topic}")
|
||||
|
||||
# Extracted entities
|
||||
if memory.stm.extracted_entities:
|
||||
entities_json = json.dumps(
|
||||
memory.stm.extracted_entities, ensure_ascii=False
|
||||
)
|
||||
lines.append(f"EXTRACTED ENTITIES: {entities_json}")
|
||||
lines.append("EXTRACTED ENTITIES:")
|
||||
for key, value in memory.stm.extracted_entities.items():
|
||||
lines.append(f" - {key}: {value}")
|
||||
|
||||
if memory.stm.language:
|
||||
lines.append(f"CONVERSATION LANGUAGE: {memory.stm.language}")
|
||||
|
||||
return "\n".join(lines) if lines else ""
|
||||
return "\n".join(lines)
|
||||
|
||||
def _format_config_context(self) -> str:
|
||||
"""Format configuration context."""
|
||||
memory = get_memory()
|
||||
|
||||
lines = ["CURRENT CONFIGURATION:"]
|
||||
if memory.ltm.config:
|
||||
for key, value in memory.ltm.config.items():
|
||||
lines.append(f" - {key}: {value}")
|
||||
else:
|
||||
lines.append(" (no configuration set)")
|
||||
return "\n".join(lines)
|
||||
|
||||
def build_system_prompt(self) -> str:
|
||||
"""
|
||||
Build the system prompt with context from memory.
|
||||
|
||||
Returns:
|
||||
The complete system prompt string.
|
||||
"""
|
||||
"""Build the complete system prompt."""
|
||||
memory = get_memory()
|
||||
|
||||
# Base instruction
|
||||
base = "You are a helpful AI assistant for managing a media library."
|
||||
|
||||
# Language instruction
|
||||
language_instruction = (
|
||||
"Your first task is to determine the user's language from their message "
|
||||
"and use the `set_language` tool if it's different from the current one. "
|
||||
"After that, proceed to help the user."
|
||||
)
|
||||
|
||||
# Available tools
|
||||
tools_desc = self._format_tools_description()
|
||||
params_desc = format_parameters_for_prompt()
|
||||
tools_section = f"\nAVAILABLE TOOLS:\n{tools_desc}" if tools_desc else ""
|
||||
|
||||
# Check for missing required parameters
|
||||
missing_params = get_missing_required_parameters({"config": memory.ltm.config})
|
||||
missing_info = ""
|
||||
if missing_params:
|
||||
missing_info = "\n\nMISSING REQUIRED PARAMETERS:\n"
|
||||
for param in missing_params:
|
||||
missing_info += f"- {param.key}: {param.description}\n"
|
||||
missing_info += f" Why needed: {param.why_needed}\n"
|
||||
# Configuration
|
||||
config_section = self._format_config_context()
|
||||
if config_section:
|
||||
config_section = f"\n{config_section}"
|
||||
|
||||
# Build context sections
|
||||
episodic_context = self._format_episodic_context()
|
||||
# STM context
|
||||
stm_context = self._format_stm_context()
|
||||
if stm_context:
|
||||
stm_context = f"\n{stm_context}"
|
||||
|
||||
config_json = json.dumps(memory.ltm.config, indent=2, ensure_ascii=False)
|
||||
|
||||
return f"""You are an AI agent helping a user manage their local media library.
|
||||
|
||||
{params_desc}
|
||||
|
||||
CURRENT CONFIGURATION:
|
||||
{config_json}
|
||||
{missing_info}
|
||||
|
||||
{f"SESSION CONTEXT:{chr(10)}{stm_context}" if stm_context else ""}
|
||||
|
||||
{f"CURRENT STATE:{chr(10)}{episodic_context}" if episodic_context else ""}
|
||||
# Episodic context
|
||||
episodic_context = self._format_episodic_context()
|
||||
|
||||
# Important rules
|
||||
rules = """
|
||||
IMPORTANT RULES:
|
||||
1. When the user refers to a number (e.g., "the 3rd one", "download number 2"), \
|
||||
use `add_torrent_by_index` or `get_torrent_by_index` with that number.
|
||||
2. If a torrent search was performed, results are numbered. \
|
||||
The user can reference them by number.
|
||||
3. To use a tool, respond STRICTLY with this JSON format:
|
||||
{{ "thought": "explanation", "action": {{ "name": "tool_name", "args": {{ }} }} }}
|
||||
- No text before or after the JSON
|
||||
4. You can use MULTIPLE TOOLS IN SEQUENCE.
|
||||
5. When you have all the information needed, respond in NATURAL TEXT (not JSON).
|
||||
6. If a required parameter is missing, ask the user for it.
|
||||
7. Respond in the same language as the user.
|
||||
|
||||
EXAMPLES:
|
||||
- After a torrent search, if the user says "download the 3rd one":
|
||||
{{ "thought": "User wants torrent #3", "action": {{ "name": "add_torrent_by_index", \
|
||||
"args": {{ "index": 3 }} }} }}
|
||||
|
||||
- To search for torrents:
|
||||
{{ "thought": "Searching torrents", "action": {{ "name": "find_torrents", \
|
||||
"args": {{ "media_title": "Inception 1080p" }} }} }}
|
||||
|
||||
AVAILABLE TOOLS:
|
||||
{tools_desc}
|
||||
- Use tools to accomplish tasks
|
||||
- When search results are available, reference them by index (e.g., "add_torrent_by_index")
|
||||
- Always confirm actions with the user before executing destructive operations
|
||||
- Provide clear, concise responses
|
||||
"""
|
||||
|
||||
# Examples
|
||||
examples = """
|
||||
EXAMPLES:
|
||||
- User: "Find Inception" → Use find_media_imdb_id, then find_torrent
|
||||
- User: "download the 3rd one" → Use add_torrent_by_index with index=3
|
||||
- User: "List my downloads" → Use list_folder with folder_type="download"
|
||||
"""
|
||||
|
||||
return f"""{base}
|
||||
|
||||
{language_instruction}
|
||||
{tools_section}
|
||||
{config_section}
|
||||
{stm_context}
|
||||
{episodic_context}
|
||||
{rules}
|
||||
{examples}
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user