first commit
This commit is contained in:
0
src/agent/__init__.py
Normal file
0
src/agent/__init__.py
Normal file
204
src/agent/core.py
Normal file
204
src/agent/core.py
Normal file
@@ -0,0 +1,204 @@
|
||||
"""Core agent implementation using Claude Agent SDK via LiteLLM."""
|
||||
from typing import List, Dict, Any, Optional, Callable
|
||||
import anthropic
|
||||
from anthropic.types import MessageParam, TextBlock, ToolUseBlock
|
||||
from src.config import settings
|
||||
|
||||
|
||||
class AgentTool:
|
||||
"""Wrapper for agent tools that can be called by the LLM."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str,
|
||||
input_schema: Dict[str, Any],
|
||||
function: Callable[..., Any],
|
||||
):
|
||||
"""Initialize a tool.
|
||||
|
||||
Args:
|
||||
name: Tool name
|
||||
description: What the tool does
|
||||
input_schema: JSON schema for tool parameters
|
||||
function: Python function to execute
|
||||
"""
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.input_schema = input_schema
|
||||
self.function = function
|
||||
|
||||
def to_anthropic_tool(self) -> Dict[str, Any]:
|
||||
"""Convert to Anthropic tool format."""
|
||||
return {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"input_schema": self.input_schema,
|
||||
}
|
||||
|
||||
def execute(self, **kwargs: Any) -> Any:
|
||||
"""Execute the tool with given arguments."""
|
||||
return self.function(**kwargs)
|
||||
|
||||
|
||||
class MyOrgAgent:
|
||||
"""AI agent for managing myorg GTD system."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
system_prompt: str,
|
||||
tools: Optional[List[AgentTool]] = None,
|
||||
model: Optional[str] = None,
|
||||
):
|
||||
"""Initialize the agent.
|
||||
|
||||
Args:
|
||||
system_prompt: System instructions for the agent
|
||||
tools: List of tools the agent can use
|
||||
model: Model name (defaults to config)
|
||||
"""
|
||||
self.system_prompt = system_prompt
|
||||
self.tools = tools or []
|
||||
self.model = model or settings.litellm_model
|
||||
|
||||
# Initialize Anthropic client pointing to LiteLLM endpoint
|
||||
self.client = anthropic.Anthropic(
|
||||
api_key=settings.litellm_api_key,
|
||||
base_url=settings.litellm_endpoint,
|
||||
)
|
||||
|
||||
# Conversation history
|
||||
self.messages: List[MessageParam] = []
|
||||
|
||||
def add_tool(self, tool: AgentTool) -> None:
|
||||
"""Add a tool to the agent's toolkit.
|
||||
|
||||
Args:
|
||||
tool: Tool to add
|
||||
"""
|
||||
self.tools.append(tool)
|
||||
|
||||
def _get_tool_by_name(self, name: str) -> Optional[AgentTool]:
|
||||
"""Get tool by name.
|
||||
|
||||
Args:
|
||||
name: Tool name
|
||||
|
||||
Returns:
|
||||
AgentTool if found, None otherwise
|
||||
"""
|
||||
for tool in self.tools:
|
||||
if tool.name == name:
|
||||
return tool
|
||||
return None
|
||||
|
||||
def run(
|
||||
self,
|
||||
user_message: str,
|
||||
max_iterations: int = 10,
|
||||
) -> str:
|
||||
"""Run the agent with a user message.
|
||||
|
||||
The agent will process the message, use tools as needed,
|
||||
and return a final response.
|
||||
|
||||
Args:
|
||||
user_message: Message from the user
|
||||
max_iterations: Maximum number of agent iterations
|
||||
|
||||
Returns:
|
||||
Final response text
|
||||
"""
|
||||
# Add user message to history
|
||||
self.messages.append({
|
||||
"role": "user",
|
||||
"content": user_message,
|
||||
})
|
||||
|
||||
iteration = 0
|
||||
while iteration < max_iterations:
|
||||
iteration += 1
|
||||
|
||||
# Call Claude via LiteLLM
|
||||
response = self.client.messages.create(
|
||||
model=self.model,
|
||||
max_tokens=4096,
|
||||
system=self.system_prompt,
|
||||
messages=self.messages,
|
||||
tools=[tool.to_anthropic_tool()
|
||||
for tool in self.tools] if self.tools else anthropic.NOT_GIVEN,
|
||||
)
|
||||
|
||||
# Add assistant response to history
|
||||
self.messages.append({
|
||||
"role": "assistant",
|
||||
"content": response.content,
|
||||
})
|
||||
|
||||
# Check if we're done (no tool use)
|
||||
if response.stop_reason == "end_turn":
|
||||
# Extract text response
|
||||
text_blocks = [
|
||||
block for block in response.content if isinstance(block, TextBlock)]
|
||||
if text_blocks:
|
||||
return text_blocks[0].text
|
||||
return ""
|
||||
|
||||
# Process tool use
|
||||
if response.stop_reason == "tool_use":
|
||||
tool_results = []
|
||||
|
||||
for block in response.content:
|
||||
if isinstance(block, ToolUseBlock):
|
||||
# Execute the tool
|
||||
tool = self._get_tool_by_name(block.name)
|
||||
if tool:
|
||||
try:
|
||||
result = tool.execute(**block.input)
|
||||
tool_results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": str(result),
|
||||
})
|
||||
except Exception as e:
|
||||
tool_results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": f"Error: {str(e)}",
|
||||
"is_error": True,
|
||||
})
|
||||
else:
|
||||
tool_results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": f"Error: Unknown tool {block.name}",
|
||||
"is_error": True,
|
||||
})
|
||||
|
||||
# Add tool results to messages
|
||||
if tool_results:
|
||||
self.messages.append({
|
||||
"role": "user",
|
||||
"content": tool_results,
|
||||
})
|
||||
|
||||
# Continue iteration
|
||||
continue
|
||||
|
||||
# Unexpected stop reason
|
||||
break
|
||||
|
||||
# Max iterations reached
|
||||
return "I apologize, but I've reached the maximum number of processing steps. Please try a simpler request."
|
||||
|
||||
def reset_conversation(self) -> None:
|
||||
"""Clear conversation history."""
|
||||
self.messages = []
|
||||
|
||||
def get_conversation_history(self) -> List[MessageParam]:
|
||||
"""Get current conversation history.
|
||||
|
||||
Returns:
|
||||
List of messages
|
||||
"""
|
||||
return self.messages.copy()
|
||||
115
src/agent/prompts.py
Normal file
115
src/agent/prompts.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""System prompts for the MyOrg Agent."""
|
||||
|
||||
MYORG_SYSTEM_PROMPT = """You are a personal assistant managing a GTD-based organization system called "myorg".
|
||||
|
||||
## Your Role
|
||||
|
||||
You help the user capture, organize, and prioritize tasks, events, and projects. You can read and modify files in the myorg repository, and you understand the user's goals, projects, and daily context.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
The myorg repository contains:
|
||||
- **todo.txt**: Active tasks in todo.txt format
|
||||
- **calendar.txt**: Calendar events
|
||||
- **projects.txt**: Active projects with status and goals
|
||||
- **waiting.txt**: Items waiting on others
|
||||
- **telos.md**: User's vision and life missions
|
||||
- **goals/**: Quarterly and yearly goals
|
||||
- **working-memory.txt**: Recent activities and thoughts
|
||||
|
||||
## File Formats
|
||||
|
||||
### todo.txt Format
|
||||
Tasks follow this format:
|
||||
- Priority: `(A)`, `(B)`, `(C)` at the start (A = highest)
|
||||
- Completion: `x` at the start with optional completion date
|
||||
- Creation date: `YYYY-MM-DD` after priority
|
||||
- Description: Main task text
|
||||
- Projects: `+project-name` (e.g., `+myorg-assistant`)
|
||||
- Contexts: `@context-name` (e.g., `@computer-deep`, `@telefon`, `@bcn`)
|
||||
- Metadata: `key:value` format (e.g., `due:2026-02-15`)
|
||||
|
||||
Example:
|
||||
```
|
||||
(A) 2026-01-31 Write blog post +observability-blog @computer-deep due:2026-02-15
|
||||
```
|
||||
|
||||
### calendar.txt Format
|
||||
Events follow this format:
|
||||
- Timed event: `YYYY-MM-DD HH:MM Description @context +project`
|
||||
- All-day event: `YYYY-MM-DD Description @context +project`
|
||||
|
||||
Example:
|
||||
```
|
||||
2026-02-01 09:00 Team standup @telefon +work
|
||||
2026-02-15 Birthday party @personal
|
||||
```
|
||||
|
||||
### projects.txt Format
|
||||
Projects follow this format:
|
||||
- Format: `+project-tag Description [status] @context goal:goal-id metadata...`
|
||||
- Status: `[active]`, `[waiting]`, `[someday]`, `[completed]`
|
||||
|
||||
Example:
|
||||
```
|
||||
+myorg-assistant Personal assistant [active] @computer-deep goal:q1-2026 due:2026-02-28
|
||||
```
|
||||
|
||||
## Common Contexts
|
||||
|
||||
- `@computer-deep`: Deep focus work on computer
|
||||
- `@computer-light`: Light computer work
|
||||
- `@telefon`: Phone calls or video meetings
|
||||
- `@recados`: Errands (shopping, appointments)
|
||||
- `@bcn`: Location-specific (Barcelona)
|
||||
- `@personal`: Personal/family activities
|
||||
|
||||
## Your Capabilities
|
||||
|
||||
You can:
|
||||
1. **Read files** from the myorg repository to understand tasks, events, and goals
|
||||
2. **Add tasks** to todo.txt with proper formatting
|
||||
3. **Complete tasks** by marking them with `x` and completion date
|
||||
4. **Search and filter** tasks by project, context, priority, or due date
|
||||
5. **Add events** to calendar.txt
|
||||
6. **Manage projects** in projects.txt
|
||||
7. **Commit changes** to git with descriptive messages
|
||||
8. **Sync with remote** using git pull/push
|
||||
|
||||
## Guidelines
|
||||
|
||||
1. **Always read before write**: Check current file contents before modifying
|
||||
2. **Preserve formatting**: Maintain todo.txt, calendar.txt, and projects.txt format
|
||||
3. **Commit changes**: After modifying files, commit with descriptive messages
|
||||
4. **Be proactive**: Suggest tasks that align with user's goals
|
||||
5. **Respect context**: Filter tasks based on user's current context
|
||||
6. **Use proper metadata**: Include due dates, projects, and contexts when relevant
|
||||
7. **Update working-memory**: Note significant actions in working-memory.txt
|
||||
|
||||
## Natural Language Understanding
|
||||
|
||||
When the user says:
|
||||
- "Add task: Buy milk tomorrow" → Create task with due date tomorrow, context `@recados`
|
||||
- "What should I work on?" → Suggest tasks based on context, priority, and goals
|
||||
- "Show my calendar" → Read and display calendar.txt events
|
||||
- "Mark task X as done" → Complete the task with timestamp
|
||||
- "What are my Q1 goals?" → Read quarterly goals from goals/ directory
|
||||
|
||||
## Tone
|
||||
|
||||
- Be helpful, concise, and action-oriented
|
||||
- Use natural language in responses
|
||||
- Acknowledge task completion and changes made
|
||||
- Proactively suggest next steps when appropriate
|
||||
|
||||
Remember: You are a trusted assistant. The user relies on you to keep their GTD system organized and help them stay focused on what matters most.
|
||||
"""
|
||||
|
||||
|
||||
def get_system_prompt() -> str:
|
||||
"""Get the main system prompt for the agent.
|
||||
|
||||
Returns:
|
||||
System prompt string
|
||||
"""
|
||||
return MYORG_SYSTEM_PROMPT
|
||||
Reference in New Issue
Block a user