Skip to content

Commit 7fc9d73

Browse files
committed
Refactor framework for extensibility and remove abstractions
Remove unnecessary abstraction layers to simplify codebase: - Delete AgentAPI wrapper (use SessionStore directly) - Delete SessionNotFoundError (use None checking pattern) - Delete api/models.py (unused) Move agent config and prompts into agent hierarchy for self-containment: - Agent configs: agents/{agent}/config.yaml (package) + ~/.agent-kit/{agent}.yaml (user) - Agent prompts: agents/{agent}/prompts/ (moved from data/prompts/) - Agent tools: moved to agents/hello/tools.py (example-specific) Implement command registry pattern for extensible slash commands: - Commands registered with handler + description + help text - Tab completion and help auto-generated from registry - Subclasses extend via registration, not method overriding - Framework code no longer imports example agents Fix circular imports and clean up console architecture: - Move console singleton to server.py to break import cycle - Lazy-load HelloCommands to prevent circular dependencies - Remove @ prefix file completion (legacy code) Rewrite README for clarity: - Reduce from 387 to 148 lines (62% reduction) - Focus on essentials: quick start, usage, extension pattern - Point to agents/hello/ as reference implementation - Show new command registry pattern Net result: -100 lines of code, cleaner architecture, better extensibility
1 parent 99fafe1 commit 7fc9d73

25 files changed

Lines changed: 582 additions & 498 deletions

README.md

Lines changed: 75 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,167 +1,147 @@
11
# Agent Kit
22

3-
Developer toolkit for building AI agents with OpenAI Responses API.
3+
Framework for building AI agents with OpenAI Responses API.
44

55
## Features
66

7-
- OpenAI Responses API with `previous_response_id` for conversation continuation
8-
- YAML-based configuration and prompt templates
9-
- Connection pooling with retry logic
7+
- Conversation continuation via `previous_response_id` (no message array juggling)
108
- Session management with automatic cleanup
9+
- YAML-based prompts and configuration
10+
- Connection pooling with retry logic
1111
- Tool integration (web search, time utilities)
12-
- Interactive console with rich terminal interface
12+
- Interactive console with slash commands
1313

1414
## Quick Start
1515

1616
```bash
17-
# Install and setup
1817
uv sync
1918
uv run agent-kit init
2019
export OPENAI_API_KEY="sk-..."
21-
22-
# Run console
2320
uv run agent-kit
24-
25-
# Try commands
26-
/hello Alice
2721
```
2822

29-
## Programmatic Usage
23+
Try `/hello Alice` or ask questions in chat mode.
3024

31-
```python
32-
from agent_kit.api import AgentAPI
25+
## Usage
3326

34-
api = AgentAPI()
35-
session_id = await api.session_store.create_session()
27+
```python
28+
from agent_kit import SessionStore
29+
from agent_kit.agents.hello.agent import HelloAgent
30+
from agent_kit.config import get_openai_client
3631

37-
# Fresh conversation
38-
greeting = await api.hello("Alice", session_id)
32+
# Create session
33+
session_store = SessionStore(get_openai_client())
34+
session_id = await session_store.create_session()
35+
session = await session_store.get_session(session_id)
3936

40-
# Continued conversation (uses previous_response_id)
41-
response = await api.chat("What's the weather?", session_id)
42-
followup = await api.chat("How about tomorrow?", session_id)
43-
```
37+
# Use agent
38+
agent = await session.use_agent(HelloAgent)
4439

45-
## Architecture
40+
# Fresh conversation
41+
response = await agent.process("Greet Alice", continue_conversation=False)
4642

43+
# Continue conversation (uses previous_response_id)
44+
followup = await agent.process("What's the weather?", continue_conversation=True)
4745
```
48-
agent_kit/
49-
├── agents/ # BaseAgent and implementations
50-
├── api/ # AgentAPI, SessionStore, Console
51-
├── clients/ # OpenAI client with pooling
52-
├── config/ # YAML configuration system
53-
├── prompts/ # Prompt template loader
54-
├── utils/ # Tool definitions and utilities
55-
└── data/ # Config and prompt templates
56-
```
57-
58-
## Building Custom Agents
5946

60-
### 1. Define Models
47+
## Extending
6148

62-
```python
63-
from pydantic import BaseModel
64-
65-
class MyResponse(BaseModel):
66-
model_config = {"extra": "forbid"} # Required for structured outputs
67-
result: str
68-
```
49+
Agent-Kit is designed for extension. See `agent_kit/agents/hello/` for the complete pattern.
6950

70-
### 2. Implement Agent
51+
### Custom Agent
7152

7253
```python
73-
from agent_kit.agents.base_agent import BaseAgent
74-
from agent_kit.utils.tools import get_tool_definitions, execute_tool
54+
from agent_kit import BaseAgent
7555

7656
class MyAgent(BaseAgent):
7757
async def process(self, query: str, continue_conversation: bool = False) -> str:
78-
prompts = self.render_prompt("myagent", "orchestrator")
58+
prompts = self.render_prompt("my_agent", "orchestrator")
7959

8060
response = await self.execute_tool_conversation(
8161
instructions=prompts["instructions"],
8262
initial_input=[{"role": "user", "content": query}],
8363
tools=get_tool_definitions(),
8464
tool_executor=execute_tool,
8565
previous_response_id=self.last_response_id if continue_conversation else None,
86-
response_format=MyResponse, # structured output
87-
max_iterations=10
8866
)
8967

90-
return response if isinstance(response, str) else response.result
68+
return response.output_text or "Error"
9169
```
9270

93-
### 3. Create Prompt Template
94-
95-
`data/prompts/myagent/orchestrator.yaml`:
71+
### Agent Structure
9672

97-
```yaml
98-
agent: myagent
99-
function: orchestrator
100-
prompt:
101-
instructions: |
102-
# Role
103-
You are a helpful assistant.
104-
105-
## Available Tools
106-
- `web_search`: Search the web
107-
- `get_current_time`: Get current time
108-
parameters: []
73+
```
74+
my_agents/analyzer/
75+
├── agent.py # Implements BaseAgent
76+
├── tools.py # Tool definitions and executor
77+
├── console.py # Optional: slash commands
78+
├── config.yaml # Agent-specific config
79+
└── prompts/
80+
└── orchestrator.yaml
10981
```
11082

111-
### 4. Add to API
83+
### Console Commands
11284

11385
```python
114-
async def my_action(self, query: str, session_id: str) -> str:
115-
session = self._get_session(session_id)
116-
agent = session.get_or_create_agent(AgentType.MY_AGENT)
117-
result = await agent.process(query, continue_conversation=True)
118-
session.store_result(AgentType.MY_AGENT, result, query=query)
119-
return result
120-
```
86+
from agent_kit.api.console.server import SlashCommands
87+
from rich.console import Console
88+
89+
class MyCommands(SlashCommands):
90+
def __init__(self, console: Console):
91+
super().__init__(console)
92+
93+
# Register commands
94+
self.register_command(
95+
"/analyze",
96+
self._handle_analyze,
97+
"Run analysis",
98+
"Analyze data\nUsage: /analyze <topic>"
99+
)
121100

122-
## Configuration
101+
async def _handle_analyze(self, args: list[str]) -> None:
102+
# Your implementation
103+
pass
123104

124-
`~/.agent-kit/config.yaml`:
105+
# Run console
106+
await run_console(MyCommands)
107+
```
125108

109+
### Configuration
110+
111+
Framework config (`~/.agent-kit/config.yaml`):
126112
```yaml
127113
openai:
128114
api_key: "${OPENAI_API_KEY}"
129-
model: "gpt-5-nano"
115+
model: "gpt-4o"
130116
pool_size: 8
131117

132118
agents:
133119
max_iterations: 20
134120
```
135121
136-
## Development
137-
138-
```bash
139-
# Lint and format
140-
uv run ruff check . && uv run ruff format .
122+
Agent config (`~/.agent-kit/my_agent.yaml`):
123+
```yaml
124+
max_iterations: 15
125+
```
141126

142-
# Type check
143-
uv run pyright
127+
Configs auto-loaded from:
128+
- `agent_kit/agents/{agent}/config.yaml` (package defaults)
129+
- `~/.agent-kit/{agent}.yaml` (user overrides)
144130

145-
# Test
146-
uv run pytest
131+
## Development
147132

148-
# Run CI checks locally
149-
make ci
133+
```bash
134+
uv run ruff check . && uv run ruff format . # Lint and format
135+
uv run pyright # Type check
136+
uv run pytest # Test
137+
make ci # Run all checks
150138
```
151139

152-
## Key Patterns
153-
154-
- **Conversation Continuation**: Uses `previous_response_id` instead of message arrays
155-
- **Single Method Pattern**: One `process(query, continue_conversation)` method per agent
156-
- **Connection Pooling**: Efficient OpenAI API usage with retry logic
157-
- **YAML Prompts**: Markdown-formatted prompts with parameter injection
158-
- **Tool-First Design**: Centralized tool definitions in `utils/tools.py`
159-
160140
## Requirements
161141

162142
- Python 3.13+
163143
- OpenAI API key
164-
- `uv` package manager
144+
- uv package manager
165145

166146
## License
167147

agent_kit/__init__.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1-
"""Hello Agent - Common agent frameworks extracted from ray-agent."""
1+
"""Agent Kit - AI agents with OpenAI Responses API."""
2+
3+
from agent_kit.agents.base_agent import BaseAgent
4+
from agent_kit.api.core import AgentSession, SessionStore
5+
from agent_kit.clients.openai_client import OpenAIClient
6+
from agent_kit.config.config import get_config, get_openai_client
7+
from agent_kit.prompts.loader import PromptLoader
8+
9+
__all__ = [
10+
"AgentSession",
11+
"BaseAgent",
12+
"OpenAIClient",
13+
"PromptLoader",
14+
"SessionStore",
15+
"get_config",
16+
"get_openai_client",
17+
]
218

319
__version__ = "0.1.0"

agent_kit/agents/base_agent.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ def __init__(self, openai_client: OpenAIClient):
3232
self.prompt_cache_key: str | None = None
3333
self.agent_type = self.__class__.__name__.replace("Agent", "").lower()
3434

35+
def get_agent_config(self, key: str, default: Any = None) -> Any:
36+
"""Get agent-specific config value with fallback to default.
37+
38+
Args:
39+
key: Config key to retrieve
40+
default: Default value if key not found
41+
42+
Returns:
43+
Config value or default
44+
"""
45+
return get_config().agent_configs.get(self.agent_type, {}).get(key, default)
46+
3547
def _process_response_metadata(
3648
self,
3749
response: Any,

agent_kit/agents/hello/agent.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from agent_kit.agents.base_agent import BaseAgent
66
from agent_kit.clients.openai_client import OpenAIClient
77
from agent_kit.config.config import get_config
8-
from agent_kit.utils.tools import execute_tool, get_tool_definitions
8+
9+
from .tools import execute_tool, get_tool_definitions
910

1011
logger = logging.getLogger(__name__)
1112

@@ -34,8 +35,8 @@ async def process(self, query: str, continue_conversation: bool = False) -> str:
3435
# Render orchestrator prompt
3536
prompts = self.render_prompt("hello", "orchestrator")
3637

37-
# Get max iterations from config
38-
max_iterations = get_config().hello.max_iterations
38+
# Get max iterations from agent config (with fallback to global default)
39+
max_iterations = self.get_agent_config("max_iterations", get_config().agents.max_iterations)
3940

4041
# Execute the conversation with tools
4142
response = await self.execute_tool_conversation(

agent_kit/agents/hello/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Hello Agent Configuration
2+
max_iterations: 10 # Override global default for web search capability

agent_kit/agents/hello/console.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""Console integration for Hello agent - demonstrates extension pattern."""
2+
3+
from typing import cast
4+
5+
from rich.console import Console
6+
7+
from agent_kit.agents.hello.agent import HelloAgent
8+
from agent_kit.api.console.server import SlashCommands
9+
10+
11+
class HelloCommands(SlashCommands):
12+
"""Extended commands with Hello agent integration."""
13+
14+
def __init__(self, console: Console):
15+
"""Initialize Hello commands and register agent-specific commands."""
16+
super().__init__(console)
17+
18+
# Register hello agent commands
19+
self.register_command(
20+
"/hello",
21+
self._handle_hello,
22+
"Generate a personalized greeting",
23+
"Generate a personalized greeting\nUsage: /hello <name>",
24+
)
25+
26+
async def handle_input(self, user_input: str) -> bool:
27+
"""Handle commands via registry, then fall back to chat."""
28+
# Try registered commands first (framework + agent)
29+
if await super().handle_input(user_input):
30+
return True
31+
32+
# If it's an unknown slash command, show error
33+
if user_input.startswith("/"):
34+
cmd = user_input.split()[0] if user_input.split() else "/"
35+
self.console.print(f"[red]Unknown command: {cmd}[/red]")
36+
self.console.print("Type [cyan]/help[/cyan] to see available commands")
37+
return True
38+
39+
# Handle non-command input as chat with HelloAgent
40+
await self._handle_chat(user_input)
41+
return True
42+
43+
async def _handle_hello(self, args: list[str]) -> None:
44+
"""Handle /hello command using HelloAgent."""
45+
if not args:
46+
self.console.print("[dim]Usage: /hello <name>[/dim]")
47+
return
48+
49+
if not self.session_id:
50+
self.console.print("[red]Session not initialized[/red]")
51+
return
52+
53+
name = args[0]
54+
self.console.print(f"[dim]▶ Generating greeting for {name}[/dim]")
55+
56+
try:
57+
session = await self.session_store.get_session(self.session_id)
58+
if not session:
59+
self.console.print("[red]Session not found[/red]")
60+
return
61+
62+
agent = cast(HelloAgent, await session.use_agent(HelloAgent))
63+
greeting = await agent.process(f"Greet {name}", continue_conversation=False)
64+
65+
self.console.print("\n[bold green]Hello Agent:[/bold green]")
66+
self.console.print(greeting, markup=False)
67+
self.console.print()
68+
except Exception as e:
69+
self.console.print(f"[red]Error: {e}[/red]")
70+
71+
async def _handle_chat(self, user_input: str) -> None:
72+
"""Handle chat input using HelloAgent."""
73+
if not self.session_id:
74+
self.console.print("[red]Session not initialized[/red]")
75+
return
76+
77+
try:
78+
session = await self.session_store.get_session(self.session_id)
79+
if not session:
80+
self.console.print("[red]Session not found[/red]")
81+
return
82+
83+
agent = cast(HelloAgent, await session.use_agent(HelloAgent))
84+
response = await agent.process(user_input, continue_conversation=True)
85+
86+
self.console.print("\n[bold green]Hello Agent:[/bold green]")
87+
self.console.print(response, markup=False)
88+
self.console.print()
89+
except Exception as e:
90+
self.console.print(f"[red]Error: {e}[/red]")
File renamed without changes.

0 commit comments

Comments
 (0)