Skip to content

Commit cf0e177

Browse files
committed
wip: remove agent discovery and fix tests
1 parent 32f7e1d commit cf0e177

File tree

9 files changed

+174
-461
lines changed

9 files changed

+174
-461
lines changed

clai/README.md

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,50 @@ Either way, running `clai` will start an interactive session where you can chat
5151
- `/multiline`: Toggle multiline input mode (use Ctrl+D to submit)
5252
- `/cp`: Copy the last response to clipboard
5353

54+
## Web Chat UI
55+
56+
Launch a web-based chat interface for your agent:
57+
58+
```bash
59+
clai web module:agent_variable
60+
```
61+
62+
For example, if you have an agent defined in `my_agent.py`:
63+
64+
```python
65+
from pydantic_ai import Agent
66+
67+
my_agent = Agent('openai:gpt-5', system_prompt='You are a helpful assistant.')
68+
```
69+
70+
Launch the web UI with:
71+
72+
```bash
73+
clai web my_agent:my_agent
74+
```
75+
76+
This will start a web server (default: http://127.0.0.1:8000) with a chat interface for your agent.
77+
78+
### Web Command Options
79+
80+
- `--host`: Host to bind the server to (default: 127.0.0.1)
81+
- `--port`: Port to bind the server to (default: 8000)
82+
- `--config`: Path to custom `agent_options.py` config file
83+
- `--no-auto-config`: Disable auto-discovery of `agent_options.py` in current directory
84+
85+
You can also launch the web UI directly from an `Agent` instance using `Agent.to_web()`:
86+
87+
```python
88+
from pydantic_ai import Agent
89+
90+
agent = Agent('openai:gpt-5')
91+
app = agent.to_web() # Returns a FastAPI application
92+
```
93+
5494
## Help
5595

5696
```
57-
usage: clai [-h] [-m [MODEL]] [-a AGENT] [-l] [-t [CODE_THEME]] [--no-stream] [--version] {web} ... [prompt]
97+
usage: clai [-h] [-m [MODEL]] [-a AGENT] [-l] [-t [CODE_THEME]] [--no-stream] [--version] [prompt]
5898
5999
Pydantic AI CLI v...
60100
@@ -65,8 +105,6 @@ Special prompts:
65105
* `/cp` - copy the last response to clipboard
66106
67107
positional arguments:
68-
{web} Available commands
69-
web Launch web chat UI for discovered agents
70108
prompt AI Prompt, if omitted fall into interactive mode
71109
72110
options:

clai/clai/chat/__init__.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

clai/clai/chat/agent_discovery.py

Lines changed: 0 additions & 186 deletions
This file was deleted.

clai/clai/web/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Chat UI module for clai."""
2+
3+
from .cli import run_chat_command
4+
5+
__all__ = ['run_chat_command']

clai/clai/chat/cli.py renamed to clai/clai/web/cli.py

Lines changed: 25 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import importlib
56
import importlib.util
67
import sys
78
from pathlib import Path
@@ -10,8 +11,6 @@
1011
from pydantic_ai.builtin_tools import AbstractBuiltinTool
1112
from pydantic_ai.ui.web import AIModel, BuiltinTool, create_chat_app
1213

13-
from .agent_discovery import AgentInfo, find_agents
14-
1514

1615
def load_agent_options(
1716
config_path: Path,
@@ -47,89 +46,62 @@ def load_agent_options(
4746
return None, None, None
4847

4948

50-
def select_agent(agents: list[AgentInfo]) -> AgentInfo | None:
51-
"""Prompt user to select an agent from the list."""
52-
if not agents:
53-
print('No agents found in the current directory.')
54-
return None
55-
56-
if len(agents) == 1:
57-
print(f'Found agent: {agents[0].agent_name} in {agents[0].file_path}')
58-
return agents[0]
59-
60-
print('Multiple agents found:')
61-
for i, agent_info in enumerate(agents, 1):
62-
print(f' {i}. {agent_info.agent_name} ({agent_info.file_path})')
63-
64-
while True:
65-
try:
66-
choice = input('\nSelect an agent (enter number): ').strip()
67-
index = int(choice) - 1
68-
if 0 <= index < len(agents):
69-
return agents[index]
70-
else:
71-
print(f'Please enter a number between 1 and {len(agents)}')
72-
except (ValueError, KeyboardInterrupt):
73-
print('\nSelection cancelled.')
74-
return None
49+
def load_agent(agent_path: str) -> Agent | None:
50+
"""Load an agent from module path in uvicorn style.
7551
52+
Args:
53+
agent_path: Path in format 'module:variable', e.g. 'test_agent:my_agent'
7654
77-
def load_agent(agent_info: AgentInfo) -> Agent | None:
78-
"""Load an agent from the given agent info."""
55+
Returns:
56+
Agent instance or None if loading fails
57+
"""
7958
sys.path.insert(0, str(Path.cwd()))
8059

8160
try:
82-
spec = importlib.util.spec_from_file_location(agent_info.module_path, agent_info.file_path)
83-
if spec is None or spec.loader is None:
84-
print(f'Error: Could not load module from {agent_info.file_path}')
85-
return None
61+
module_path, variable_name = agent_path.split(':')
62+
except ValueError:
63+
print('Error: Agent must be specified in "module:variable" format')
64+
return None
8665

87-
module = importlib.util.module_from_spec(spec)
88-
sys.modules[agent_info.module_path] = module
89-
spec.loader.exec_module(module)
66+
try:
67+
module = importlib.import_module(module_path)
68+
agent = getattr(module, variable_name, None)
9069

91-
agent = getattr(module, agent_info.agent_name, None)
9270
if agent is None:
93-
print(f'Error: Agent {agent_info.agent_name} not found in module')
71+
print(f'Error: {variable_name} not found in module {module_path}')
9472
return None
9573

9674
if not isinstance(agent, Agent):
97-
print(f'Error: {agent_info.agent_name} is not an Agent instance')
75+
print(f'Error: {variable_name} is not an Agent instance')
9876
return None
9977

10078
return agent # pyright: ignore[reportUnknownVariableType]
10179

80+
except ImportError as e:
81+
print(f'Error: Could not import module {module_path}: {e}')
82+
return None
10283
except Exception as e:
10384
print(f'Error loading agent: {e}')
10485
return None
10586

10687

10788
def run_chat_command(
108-
root_dir: Path | None = None,
89+
agent_path: str,
10990
host: str = '127.0.0.1',
11091
port: int = 8000,
11192
config_path: Path | None = None,
11293
auto_config: bool = True,
11394
) -> int:
114-
"""Run the chat command to discover and serve an agent.
95+
"""Run the chat command to serve an agent via web UI.
11596
11697
Args:
117-
root_dir: Directory to search for agents (defaults to current directory)
98+
agent_path: Agent path in 'module:variable' format, e.g. 'test_agent:my_agent'
11899
host: Host to bind the server to
119100
port: Port to bind the server to
120101
config_path: Path to agent_options.py config file
121102
auto_config: Auto-discover agent_options.py in current directory
122103
"""
123-
search_dir = root_dir or Path.cwd()
124-
125-
print(f'Searching for agents in {search_dir}...')
126-
agents = find_agents(search_dir)
127-
128-
selected = select_agent(agents)
129-
if selected is None:
130-
return 1
131-
132-
agent = load_agent(selected)
104+
agent = load_agent(agent_path)
133105
if agent is None:
134106
return 1
135107

@@ -145,7 +117,7 @@ def run_chat_command(
145117

146118
app = create_chat_app(agent, models=models, builtin_tools=builtin_tools, builtin_tool_defs=builtin_tool_defs)
147119

148-
print(f'\nStarting chat UI for {selected.agent_name}...')
120+
print(f'\nStarting chat UI for {agent_path}...')
149121
print(f'Open your browser at: http://{host}:{port}')
150122
print('Press Ctrl+C to stop the server\n')
151123

0 commit comments

Comments
 (0)