Fixes issues with OpenRouter/Anthropic (disabling streaming until better Pydantic AI implementation)

This commit is contained in:
Cole Medin 2025-03-01 06:59:41 -06:00
parent 53cfd5e6a7
commit fcca718d3a
10 changed files with 492 additions and 101 deletions

View File

@ -2,16 +2,18 @@
# OpenAI: https://api.openai.com/v1
# Ollama (example): http://localhost:11434/v1
# OpenRouter: https://openrouter.ai/api/v1
# Anthropic: https://api.anthropic.com/v1
BASE_URL=
# For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
# For Anthropic: https://console.anthropic.com/account/keys
# For OpenRouter: https://openrouter.ai/keys
# For Ollama, no need to set this unless you specifically configured an API key
LLM_API_KEY=
# Get your Open AI API Key by following these instructions -
# https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
# Even if using OpenRouter, you still need to set this for the embedding model.
# Even if using Anthropic or OpenRouter, you still need to set this for the embedding model.
# No need to set this if using Ollama.
OPENAI_API_KEY=

View File

@ -42,7 +42,7 @@ Since V4 is the current version of Archon, all the code for V4 is in both the ma
- Docker (optional but preferred)
- Python 3.11+
- Supabase account (for vector database)
- OpenAI/OpenRouter API key or Ollama for local LLMs
- OpenAI/Anthropic/OpenRouter API key or Ollama for local LLMs (note that only OpenAI supports streaming in the Streamlit UI currently)
### Installation

View File

@ -1,3 +1,4 @@
from pydantic_ai.models.anthropic import AnthropicModel
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai import Agent, RunContext
from langgraph.graph import StateGraph, START, END
@ -31,21 +32,29 @@ logfire.configure(send_to_logfire='never')
base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1'
api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided'
is_ollama = "localhost" in base_url.lower()
reasoner_llm_model = get_env_var('REASONER_MODEL') or 'o3-mini'
is_anthropic = "anthropic" in base_url.lower()
is_openai = "openai" in base_url.lower()
reasoner_llm_model_name = get_env_var('REASONER_MODEL') or 'o3-mini'
reasoner_llm_model = AnthropicModel(reasoner_llm_model_name, api_key=api_key) if is_anthropic else OpenAIModel(reasoner_llm_model_name, base_url=base_url, api_key=api_key)
reasoner = Agent(
OpenAIModel(reasoner_llm_model, base_url=base_url, api_key=api_key),
reasoner_llm_model,
system_prompt='You are an expert at coding AI agents with Pydantic AI and defining the scope for doing so.',
)
primary_llm_model = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
primary_llm_model_name = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
primary_llm_model = AnthropicModel(primary_llm_model_name, api_key=api_key) if is_anthropic else OpenAIModel(primary_llm_model_name, base_url=base_url, api_key=api_key)
router_agent = Agent(
OpenAIModel(primary_llm_model, base_url=base_url, api_key=api_key),
primary_llm_model,
system_prompt='Your job is to route the user message either to the end of the conversation or to continue coding the AI agent.',
)
end_conversation_agent = Agent(
OpenAIModel(primary_llm_model, base_url=base_url, api_key=api_key),
primary_llm_model,
system_prompt='Your job is to end a conversation for creating an AI agent by giving instructions for how to execute the agent and they saying a nice goodbye to the user.',
)
@ -124,7 +133,7 @@ async def coder_agent(state: AgentState, writer):
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
# Run the agent in a stream
if is_ollama:
if not is_openai:
writer = get_stream_writer()
result = await pydantic_ai_coder.run(state['latest_user_message'], deps=deps, message_history= message_history)
writer(result.data)
@ -178,7 +187,7 @@ async def finish_conversation(state: AgentState, writer):
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
# Run the agent in a stream
if is_ollama:
if not is_openai:
writer = get_stream_writer()
result = await end_conversation_agent.run(state['latest_user_message'], message_history= message_history)
writer(result.data)

View File

@ -11,6 +11,7 @@ import json
from typing import Dict, Any, List, Optional
from pydantic import BaseModel
from pydantic_ai import Agent, ModelRetry, RunContext
from pydantic_ai.models.anthropic import AnthropicModel
from pydantic_ai.models.openai import OpenAIModel
from openai import AsyncOpenAI
from supabase import Client
@ -24,13 +25,15 @@ load_dotenv()
llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1'
api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided'
model = OpenAIModel(llm, base_url=base_url, api_key=api_key)
is_ollama = "localhost" in base_url.lower()
is_anthropic = "anthropic" in base_url.lower()
model = AnthropicModel(llm, api_key=api_key) if is_anthropic else OpenAIModel(llm, base_url=base_url, api_key=api_key)
embedding_model = get_env_var('EMBEDDING_MODEL') or 'text-embedding-3-small'
logfire.configure(send_to_logfire='if-token-present')
is_ollama = "localhost" in base_url.lower()
@dataclass
class PydanticAIDeps:
supabase: Client
@ -38,41 +41,217 @@ class PydanticAIDeps:
reasoner_output: str
system_prompt = """
~~ CONTEXT: ~~
[ROLE AND CONTEXT]
You are a specialized AI agent engineer focused on building robust Pydantic AI agents. You have comprehensive access to the Pydantic AI documentation, including API references, usage guides, and implementation examples.
You are an expert at Pydantic AI - a Python AI agent framework that you have access to all the documentation to,
including examples, an API reference, and other resources to help you build Pydantic AI agents.
[CORE RESPONSIBILITIES]
1. Agent Development
- Create new agents from user requirements
- Complete partial agent implementations
- Optimize and debug existing agents
- Guide users through agent specification if needed
~~ GOAL: ~~
2. Documentation Integration
- Systematically search documentation using RAG before any implementation
- Cross-reference multiple documentation pages for comprehensive understanding
- Validate all implementations against current best practices
- Notify users if documentation is insufficient for any requirement
Your only job is to help the user create an AI agent with Pydantic AI.
The user will describe the AI agent they want to build, or if they don't, guide them towards doing so.
You will take their requirements, and then search through the Pydantic AI documentation with the tools provided
to find all the necessary information to create the AI agent with correct code.
[CODE STRUCTURE AND DELIVERABLES]
All new agents must include these files with complete, production-ready code:
It's important for you to search through multiple Pydantic AI documentation pages to get all the information you need.
Almost never stick to just one page - use RAG and the other documentation tools multiple times when you are creating
an AI agent from scratch for the user.
1. agent.py
- Primary agent definition and configuration
- Core agent logic and behaviors
- No tool implementations allowed here
~~ STRUCTURE: ~~
2. agent_tools.py
- All tool function implementations
- Tool configurations and setup
- External service integrations
When you build an AI agent from scratch, split the agent into this files and give the code for each:
- `agent.py`: The main agent file, which is where the Pydantic AI agent is defined.
- `agent_tools.py`: A tools file for the agent, which is where all the tool functions are defined. Use this for more complex agents.
- `agent_prompts.py`: A prompts file for the agent, which includes all system prompts and other prompts used by the agent. Use this when there are many prompts or large ones.
- `.env.example`: An example `.env` file - specify each variable that the user will need to fill in and a quick comment above each one for how to do so.
- `requirements.txt`: Don't include any versions, just the top level package names needed for the agent.
3. agent_prompts.py
- System prompts
- Task-specific prompts
- Conversation templates
- Instruction sets
~~ INSTRUCTIONS: ~~
4. .env.example
- Required environment variables
- Clear setup instructions in a comment above the variable for how to do so
- API configuration templates
- Don't ask the user before taking an action, just do it. Always make sure you look at the documentation with the provided tools before writing any code.
- When you first look at the documentation, always start with RAG.
Then also always check the list of available documentation pages and retrieve the content of page(s) if it'll help.
- Always let the user know when you didn't find the answer in the documentation or the right URL - be honest.
- Helpful tip: when starting a new AI agent build, it's a good idea to look at the 'weather agent' in the docs as an example.
- When starting a new AI agent build, always produce the full code for the AI agent - never tell the user to finish a tool/function.
- When refining an existing AI agent build in a conversation, just share the code changes necessary.
- Each time you respond to the user, ask them to let you know either if they need changes or the code looks good.
5. requirements.txt
- Core dependencies without versions
- User-specified packages included
[DOCUMENTATION WORKFLOW]
1. Initial Research
- Begin with RAG search for relevant documentation
- List all documentation pages using list_documentation_pages
- Retrieve specific page content using get_page_content
- Cross-reference the weather agent example for best practices
2. Implementation
- Provide complete, working code implementations
- Never leave placeholder functions
- Include all necessary error handling
- Implement proper logging and monitoring
3. Quality Assurance
- Verify all tool implementations are complete
- Ensure proper separation of concerns
- Validate environment variable handling
- Test critical path functionality
[INTERACTION GUIDELINES]
- Take immediate action without asking for permission
- Always verify documentation before implementation
- Provide honest feedback about documentation gaps
- Include specific enhancement suggestions
- Request user feedback on implementations
- Maintain code consistency across files
[ERROR HANDLING]
- Implement robust error handling in all tools
- Provide clear error messages
- Include recovery mechanisms
- Log important state changes
[BEST PRACTICES]
- Follow Pydantic AI naming conventions
- Implement proper type hints
- Include comprehensive docstrings, the agent uses this to understand what tools are for.
- Maintain clean code structure
- Use consistent formatting
Here is a good example of a Pydantic AI agent:
```python
from __future__ import annotations as _annotations
import asyncio
import os
from dataclasses import dataclass
from typing import Any
import logfire
from devtools import debug
from httpx import AsyncClient
from pydantic_ai import Agent, ModelRetry, RunContext
# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
logfire.configure(send_to_logfire='if-token-present')
@dataclass
class Deps:
client: AsyncClient
weather_api_key: str | None
geo_api_key: str | None
weather_agent = Agent(
'openai:gpt-4o',
# 'Be concise, reply with one sentence.' is enough for some models (like openai) to use
# the below tools appropriately, but others like anthropic and gemini require a bit more direction.
system_prompt=(
'Be concise, reply with one sentence.'
'Use the `get_lat_lng` tool to get the latitude and longitude of the locations, '
'then use the `get_weather` tool to get the weather.'
),
deps_type=Deps,
retries=2,
)
@weather_agent.tool
async def get_lat_lng(
ctx: RunContext[Deps], location_description: str
) -> dict[str, float]:
\"\"\"Get the latitude and longitude of a location.
Args:
ctx: The context.
location_description: A description of a location.
\"\"\"
if ctx.deps.geo_api_key is None:
# if no API key is provided, return a dummy response (London)
return {'lat': 51.1, 'lng': -0.1}
params = {
'q': location_description,
'api_key': ctx.deps.geo_api_key,
}
with logfire.span('calling geocode API', params=params) as span:
r = await ctx.deps.client.get('https://geocode.maps.co/search', params=params)
r.raise_for_status()
data = r.json()
span.set_attribute('response', data)
if data:
return {'lat': data[0]['lat'], 'lng': data[0]['lon']}
else:
raise ModelRetry('Could not find the location')
@weather_agent.tool
async def get_weather(ctx: RunContext[Deps], lat: float, lng: float) -> dict[str, Any]:
\"\"\"Get the weather at a location.
Args:
ctx: The context.
lat: Latitude of the location.
lng: Longitude of the location.
\"\"\"
if ctx.deps.weather_api_key is None:
# if no API key is provided, return a dummy response
return {'temperature': '21 °C', 'description': 'Sunny'}
params = {
'apikey': ctx.deps.weather_api_key,
'location': f'{lat},{lng}',
'units': 'metric',
}
with logfire.span('calling weather API', params=params) as span:
r = await ctx.deps.client.get(
'https://api.tomorrow.io/v4/weather/realtime', params=params
)
r.raise_for_status()
data = r.json()
span.set_attribute('response', data)
values = data['data']['values']
# https://docs.tomorrow.io/reference/data-layers-weather-codes
code_lookup = {
...
}
return {
'temperature': f'{values["temperatureApparent"]:0.0f}°C',
'description': code_lookup.get(values['weatherCode'], 'Unknown'),
}
async def main():
async with AsyncClient() as client:
# create a free API key at https://www.tomorrow.io/weather-api/
weather_api_key = os.getenv('WEATHER_API_KEY')
# create a free API key at https://geocode.maps.co/
geo_api_key = os.getenv('GEO_API_KEY')
deps = Deps(
client=client, weather_api_key=weather_api_key, geo_api_key=geo_api_key
)
result = await weather_agent.run(
'What is the weather like in London and in Wiltshire?', deps=deps
)
debug(result)
print('Response:', result.data)
if __name__ == '__main__':
asyncio.run(main())
```
"""
pydantic_ai_coder = Agent(
@ -115,7 +294,7 @@ async def retrieve_relevant_documentation(ctx: RunContext[PydanticAIDeps], user_
user_query: The user's question or query
Returns:
A formatted string containing the top 5 most relevant documentation chunks
A formatted string containing the top 4 most relevant documentation chunks
"""
try:
# Get the embedding for the query
@ -126,7 +305,7 @@ async def retrieve_relevant_documentation(ctx: RunContext[PydanticAIDeps], user_
'match_site_pages',
{
'query_embedding': query_embedding,
'match_count': 5,
'match_count': 4,
'filter': {'source': 'pydantic_ai_docs'}
}
).execute()
@ -220,8 +399,9 @@ async def get_page_content(ctx: RunContext[PydanticAIDeps], url: str) -> str:
for chunk in result.data:
formatted_content.append(chunk['content'])
# Join everything together
return "\n\n".join(formatted_content)
# Join everything together but limit the characters in case the page is massive (there are a coule big ones)
# This will be improved later so if the page is too big RAG will be performed on the page itself
return "\n\n".join(formatted_content)[:20000]
except Exception as e:
print(f"Error retrieving page content: {e}")

View File

@ -2,16 +2,18 @@
# OpenAI: https://api.openai.com/v1
# Ollama (example): http://localhost:11434/v1
# OpenRouter: https://openrouter.ai/api/v1
# Anthropic: https://api.anthropic.com/v1
BASE_URL=
# For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
# For Anthropic: https://console.anthropic.com/account/keys
# For OpenRouter: https://openrouter.ai/keys
# For Ollama, no need to set this unless you specifically configured an API key
LLM_API_KEY=
# Get your Open AI API Key by following these instructions -
# https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
# Even if using OpenRouter, you still need to set this for the embedding model.
# Even if using Anthropic or OpenRouter, you still need to set this for the embedding model.
# No need to set this if using Ollama.
OPENAI_API_KEY=

View File

@ -25,7 +25,7 @@ This version continues to support both local LLMs with Ollama and cloud-based LL
- Docker (optional but preferred)
- Python 3.11+
- Supabase account (for vector database)
- OpenAI/OpenRouter API key or Ollama for local LLMs
- OpenAI/OpenRouter/Anthropic API key or Ollama for local LLMs
## Installation

View File

@ -1,3 +1,4 @@
from pydantic_ai.models.anthropic import AnthropicModel
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai import Agent, RunContext
from langgraph.graph import StateGraph, START, END
@ -31,21 +32,29 @@ logfire.configure(send_to_logfire='never')
base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1'
api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided'
is_ollama = "localhost" in base_url.lower()
reasoner_llm_model = get_env_var('REASONER_MODEL') or 'o3-mini'
is_anthropic = "anthropic" in base_url.lower()
is_openai = "openai" in base_url.lower()
reasoner_llm_model_name = get_env_var('REASONER_MODEL') or 'o3-mini'
reasoner_llm_model = AnthropicModel(reasoner_llm_model_name, api_key=api_key) if is_anthropic else OpenAIModel(reasoner_llm_model_name, base_url=base_url, api_key=api_key)
reasoner = Agent(
OpenAIModel(reasoner_llm_model, base_url=base_url, api_key=api_key),
reasoner_llm_model,
system_prompt='You are an expert at coding AI agents with Pydantic AI and defining the scope for doing so.',
)
primary_llm_model = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
primary_llm_model_name = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
primary_llm_model = AnthropicModel(primary_llm_model_name, api_key=api_key) if is_anthropic else OpenAIModel(primary_llm_model_name, base_url=base_url, api_key=api_key)
router_agent = Agent(
OpenAIModel(primary_llm_model, base_url=base_url, api_key=api_key),
primary_llm_model,
system_prompt='Your job is to route the user message either to the end of the conversation or to continue coding the AI agent.',
)
end_conversation_agent = Agent(
OpenAIModel(primary_llm_model, base_url=base_url, api_key=api_key),
primary_llm_model,
system_prompt='Your job is to end a conversation for creating an AI agent by giving instructions for how to execute the agent and they saying a nice goodbye to the user.',
)
@ -124,7 +133,7 @@ async def coder_agent(state: AgentState, writer):
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
# Run the agent in a stream
if is_ollama:
if not is_openai:
writer = get_stream_writer()
result = await pydantic_ai_coder.run(state['latest_user_message'], deps=deps, message_history= message_history)
writer(result.data)
@ -178,7 +187,7 @@ async def finish_conversation(state: AgentState, writer):
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
# Run the agent in a stream
if is_ollama:
if not is_openai:
writer = get_stream_writer()
result = await end_conversation_agent.run(state['latest_user_message'], message_history= message_history)
writer(result.data)

View File

@ -11,6 +11,7 @@ import json
from typing import Dict, Any, List, Optional
from pydantic import BaseModel
from pydantic_ai import Agent, ModelRetry, RunContext
from pydantic_ai.models.anthropic import AnthropicModel
from pydantic_ai.models.openai import OpenAIModel
from openai import AsyncOpenAI
from supabase import Client
@ -24,13 +25,15 @@ load_dotenv()
llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1'
api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided'
model = OpenAIModel(llm, base_url=base_url, api_key=api_key)
is_ollama = "localhost" in base_url.lower()
is_anthropic = "anthropic" in base_url.lower()
model = AnthropicModel(llm, api_key=api_key) if is_anthropic else OpenAIModel(llm, base_url=base_url, api_key=api_key)
embedding_model = get_env_var('EMBEDDING_MODEL') or 'text-embedding-3-small'
logfire.configure(send_to_logfire='if-token-present')
is_ollama = "localhost" in base_url.lower()
@dataclass
class PydanticAIDeps:
supabase: Client
@ -38,41 +41,217 @@ class PydanticAIDeps:
reasoner_output: str
system_prompt = """
~~ CONTEXT: ~~
[ROLE AND CONTEXT]
You are a specialized AI agent engineer focused on building robust Pydantic AI agents. You have comprehensive access to the Pydantic AI documentation, including API references, usage guides, and implementation examples.
You are an expert at Pydantic AI - a Python AI agent framework that you have access to all the documentation to,
including examples, an API reference, and other resources to help you build Pydantic AI agents.
[CORE RESPONSIBILITIES]
1. Agent Development
- Create new agents from user requirements
- Complete partial agent implementations
- Optimize and debug existing agents
- Guide users through agent specification if needed
~~ GOAL: ~~
2. Documentation Integration
- Systematically search documentation using RAG before any implementation
- Cross-reference multiple documentation pages for comprehensive understanding
- Validate all implementations against current best practices
- Notify users if documentation is insufficient for any requirement
Your only job is to help the user create an AI agent with Pydantic AI.
The user will describe the AI agent they want to build, or if they don't, guide them towards doing so.
You will take their requirements, and then search through the Pydantic AI documentation with the tools provided
to find all the necessary information to create the AI agent with correct code.
[CODE STRUCTURE AND DELIVERABLES]
All new agents must include these files with complete, production-ready code:
It's important for you to search through multiple Pydantic AI documentation pages to get all the information you need.
Almost never stick to just one page - use RAG and the other documentation tools multiple times when you are creating
an AI agent from scratch for the user.
1. agent.py
- Primary agent definition and configuration
- Core agent logic and behaviors
- No tool implementations allowed here
~~ STRUCTURE: ~~
2. agent_tools.py
- All tool function implementations
- Tool configurations and setup
- External service integrations
When you build an AI agent from scratch, split the agent into this files and give the code for each:
- `agent.py`: The main agent file, which is where the Pydantic AI agent is defined.
- `agent_tools.py`: A tools file for the agent, which is where all the tool functions are defined. Use this for more complex agents.
- `agent_prompts.py`: A prompts file for the agent, which includes all system prompts and other prompts used by the agent. Use this when there are many prompts or large ones.
- `.env.example`: An example `.env` file - specify each variable that the user will need to fill in and a quick comment above each one for how to do so.
- `requirements.txt`: Don't include any versions, just the top level package names needed for the agent.
3. agent_prompts.py
- System prompts
- Task-specific prompts
- Conversation templates
- Instruction sets
~~ INSTRUCTIONS: ~~
4. .env.example
- Required environment variables
- Clear setup instructions in a comment above the variable for how to do so
- API configuration templates
- Don't ask the user before taking an action, just do it. Always make sure you look at the documentation with the provided tools before writing any code.
- When you first look at the documentation, always start with RAG.
Then also always check the list of available documentation pages and retrieve the content of page(s) if it'll help.
- Always let the user know when you didn't find the answer in the documentation or the right URL - be honest.
- Helpful tip: when starting a new AI agent build, it's a good idea to look at the 'weather agent' in the docs as an example.
- When starting a new AI agent build, always produce the full code for the AI agent - never tell the user to finish a tool/function.
- When refining an existing AI agent build in a conversation, just share the code changes necessary.
- Each time you respond to the user, ask them to let you know either if they need changes or the code looks good.
5. requirements.txt
- Core dependencies without versions
- User-specified packages included
[DOCUMENTATION WORKFLOW]
1. Initial Research
- Begin with RAG search for relevant documentation
- List all documentation pages using list_documentation_pages
- Retrieve specific page content using get_page_content
- Cross-reference the weather agent example for best practices
2. Implementation
- Provide complete, working code implementations
- Never leave placeholder functions
- Include all necessary error handling
- Implement proper logging and monitoring
3. Quality Assurance
- Verify all tool implementations are complete
- Ensure proper separation of concerns
- Validate environment variable handling
- Test critical path functionality
[INTERACTION GUIDELINES]
- Take immediate action without asking for permission
- Always verify documentation before implementation
- Provide honest feedback about documentation gaps
- Include specific enhancement suggestions
- Request user feedback on implementations
- Maintain code consistency across files
[ERROR HANDLING]
- Implement robust error handling in all tools
- Provide clear error messages
- Include recovery mechanisms
- Log important state changes
[BEST PRACTICES]
- Follow Pydantic AI naming conventions
- Implement proper type hints
- Include comprehensive docstrings, the agent uses this to understand what tools are for.
- Maintain clean code structure
- Use consistent formatting
Here is a good example of a Pydantic AI agent:
```python
from __future__ import annotations as _annotations
import asyncio
import os
from dataclasses import dataclass
from typing import Any
import logfire
from devtools import debug
from httpx import AsyncClient
from pydantic_ai import Agent, ModelRetry, RunContext
# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
logfire.configure(send_to_logfire='if-token-present')
@dataclass
class Deps:
client: AsyncClient
weather_api_key: str | None
geo_api_key: str | None
weather_agent = Agent(
'openai:gpt-4o',
# 'Be concise, reply with one sentence.' is enough for some models (like openai) to use
# the below tools appropriately, but others like anthropic and gemini require a bit more direction.
system_prompt=(
'Be concise, reply with one sentence.'
'Use the `get_lat_lng` tool to get the latitude and longitude of the locations, '
'then use the `get_weather` tool to get the weather.'
),
deps_type=Deps,
retries=2,
)
@weather_agent.tool
async def get_lat_lng(
ctx: RunContext[Deps], location_description: str
) -> dict[str, float]:
\"\"\"Get the latitude and longitude of a location.
Args:
ctx: The context.
location_description: A description of a location.
\"\"\"
if ctx.deps.geo_api_key is None:
# if no API key is provided, return a dummy response (London)
return {'lat': 51.1, 'lng': -0.1}
params = {
'q': location_description,
'api_key': ctx.deps.geo_api_key,
}
with logfire.span('calling geocode API', params=params) as span:
r = await ctx.deps.client.get('https://geocode.maps.co/search', params=params)
r.raise_for_status()
data = r.json()
span.set_attribute('response', data)
if data:
return {'lat': data[0]['lat'], 'lng': data[0]['lon']}
else:
raise ModelRetry('Could not find the location')
@weather_agent.tool
async def get_weather(ctx: RunContext[Deps], lat: float, lng: float) -> dict[str, Any]:
\"\"\"Get the weather at a location.
Args:
ctx: The context.
lat: Latitude of the location.
lng: Longitude of the location.
\"\"\"
if ctx.deps.weather_api_key is None:
# if no API key is provided, return a dummy response
return {'temperature': '21 °C', 'description': 'Sunny'}
params = {
'apikey': ctx.deps.weather_api_key,
'location': f'{lat},{lng}',
'units': 'metric',
}
with logfire.span('calling weather API', params=params) as span:
r = await ctx.deps.client.get(
'https://api.tomorrow.io/v4/weather/realtime', params=params
)
r.raise_for_status()
data = r.json()
span.set_attribute('response', data)
values = data['data']['values']
# https://docs.tomorrow.io/reference/data-layers-weather-codes
code_lookup = {
...
}
return {
'temperature': f'{values["temperatureApparent"]:0.0f}°C',
'description': code_lookup.get(values['weatherCode'], 'Unknown'),
}
async def main():
async with AsyncClient() as client:
# create a free API key at https://www.tomorrow.io/weather-api/
weather_api_key = os.getenv('WEATHER_API_KEY')
# create a free API key at https://geocode.maps.co/
geo_api_key = os.getenv('GEO_API_KEY')
deps = Deps(
client=client, weather_api_key=weather_api_key, geo_api_key=geo_api_key
)
result = await weather_agent.run(
'What is the weather like in London and in Wiltshire?', deps=deps
)
debug(result)
print('Response:', result.data)
if __name__ == '__main__':
asyncio.run(main())
```
"""
pydantic_ai_coder = Agent(
@ -115,7 +294,7 @@ async def retrieve_relevant_documentation(ctx: RunContext[PydanticAIDeps], user_
user_query: The user's question or query
Returns:
A formatted string containing the top 5 most relevant documentation chunks
A formatted string containing the top 4 most relevant documentation chunks
"""
try:
# Get the embedding for the query
@ -126,7 +305,7 @@ async def retrieve_relevant_documentation(ctx: RunContext[PydanticAIDeps], user_
'match_site_pages',
{
'query_embedding': query_embedding,
'match_count': 5,
'match_count': 4,
'filter': {'source': 'pydantic_ai_docs'}
}
).execute()
@ -220,8 +399,9 @@ async def get_page_content(ctx: RunContext[PydanticAIDeps], url: str) -> str:
for chunk in result.data:
formatted_content.append(chunk['content'])
# Join everything together
return "\n\n".join(formatted_content)
# Join everything together but limit the characters in case the page is massive (there are a coule big ones)
# This will be improved later so if the page is too big RAG will be performed on the page itself
return "\n\n".join(formatted_content)[:20000]
except Exception as e:
print(f"Error retrieving page content: {e}")

View File

@ -21,6 +21,7 @@ from openai import AsyncOpenAI
from supabase import Client, create_client
from dotenv import load_dotenv
from utils.utils import get_env_var, save_env_var, write_to_log
from future_enhancements import future_enhancements_tab
# Import all the message part classes
from pydantic_ai.messages import (
@ -71,13 +72,13 @@ st.set_page_config(
layout="wide",
)
# Set custom theme colors to match Archon logo (pink and green)
# Primary color (pink) and secondary color (green)
# Set custom theme colors to match Archon logo (green and pink)
# Primary color (green) and secondary color (pink)
st.markdown("""
<style>
:root {
--primary-color: #00CC99; /* Pink */
--secondary-color: #FF69B4; /* Green */
--primary-color: #00CC99; /* Green */
--secondary-color: #EB2D8C; /* Pink */
--text-color: #262730;
}
@ -487,9 +488,9 @@ def intro_tab():
create_new_tab_button("Go to the Documentation Section (New Tab)", "Documentation", key="goto_docs", use_container_width=True)
# Step 4: Agent Service
with st.expander("Step 4: Agent Service Setup", expanded=False):
with st.expander("Step 4: Agent Service Setup (for MCP)", expanded=False):
st.markdown("""
### Agent Service Setup
### MCP Agent Service Setup
Start the graph service for agent generation:
@ -879,8 +880,8 @@ def show_manual_sql_instructions(sql, recreate=False):
def agent_service_tab():
"""Display the agent service interface for managing the graph service"""
st.header("Agent Service")
st.write("Start, restart, and monitor the Archon agent service.")
st.header("MCP Agent Service")
st.write("Start, restart, and monitor the Archon agent service for MCP.")
# Initialize session state variables if they don't exist
if "service_process" not in st.session_state:
@ -1102,17 +1103,18 @@ def environment_tab():
st.write("- Configure your environment variables for Archon. These settings will be saved and used for future sessions.")
st.write("- NOTE: Press 'enter' to save after inputting a variable, otherwise click the 'save' button at the bottom.")
st.write("- HELP: Hover over the '?' icon on the right for each environment variable for help/examples.")
st.warning("⚠️ If your agent service for MCP is already running, you'll need to restart it after changing environment variables.")
# Define environment variables and their descriptions from .env.example
env_vars = {
"BASE_URL": {
"description": "Base URL for the OpenAI instance (default is https://api.openai.com/v1)",
"help": "OpenAI: https://api.openai.com/v1\n\nOllama (example): http://localhost:11434/v1\n\nOpenRouter: https://openrouter.ai/api/v1",
"help": "OpenAI: https://api.openai.com/v1\n\n\n\nAnthropic: https://api.anthropic.com/v1\n\nOllama (example): http://localhost:11434/v1\n\nOpenRouter: https://openrouter.ai/api/v1",
"sensitive": False
},
"LLM_API_KEY": {
"description": "API key for your LLM provider",
"help": "For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key\n\nFor OpenRouter: https://openrouter.ai/keys\n\nFor Ollama, no need to set this unless you specifically configured an API key",
"help": "For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key\n\nFor Anthropic: https://console.anthropic.com/account/keys\n\nFor OpenRouter: https://openrouter.ai/keys\n\nFor Ollama, no need to set this unless you specifically configured an API key",
"sensitive": True
},
"OPENAI_API_KEY": {
@ -1207,7 +1209,7 @@ async def main():
query_params = st.query_params
if "tab" in query_params:
tab_name = query_params["tab"]
if tab_name in ["Intro", "Chat", "Environment", "Database", "Documentation", "Agent Service", "MCP"]:
if tab_name in ["Intro", "Chat", "Environment", "Database", "Documentation", "Agent Service", "MCP", "Future Enhancements"]:
st.session_state.selected_tab = tab_name
# Add sidebar navigation
@ -1229,6 +1231,7 @@ async def main():
docs_button = st.button("Documentation", use_container_width=True, key="docs_button")
service_button = st.button("Agent Service", use_container_width=True, key="service_button")
mcp_button = st.button("MCP", use_container_width=True, key="mcp_button")
future_enhancements_button = st.button("Future Enhancements", use_container_width=True, key="future_enhancements_button")
# Update selected tab based on button clicks
if intro_button:
@ -1245,6 +1248,8 @@ async def main():
st.session_state.selected_tab = "Database"
elif docs_button:
st.session_state.selected_tab = "Documentation"
elif future_enhancements_button:
st.session_state.selected_tab = "Future Enhancements"
# Display the selected tab
if st.session_state.selected_tab == "Intro":
@ -1268,6 +1273,9 @@ async def main():
elif st.session_state.selected_tab == "Documentation":
st.title("Archon - Documentation")
documentation_tab()
elif st.session_state.selected_tab == "Future Enhancements":
st.title("Archon - Future Enhancements")
future_enhancements_tab()
if __name__ == "__main__":
asyncio.run(main())

View File

@ -1103,17 +1103,18 @@ def environment_tab():
st.write("- Configure your environment variables for Archon. These settings will be saved and used for future sessions.")
st.write("- NOTE: Press 'enter' to save after inputting a variable, otherwise click the 'save' button at the bottom.")
st.write("- HELP: Hover over the '?' icon on the right for each environment variable for help/examples.")
st.warning("⚠️ If your agent service for MCP is already running, you'll need to restart it after changing environment variables.")
# Define environment variables and their descriptions from .env.example
env_vars = {
"BASE_URL": {
"description": "Base URL for the OpenAI instance (default is https://api.openai.com/v1)",
"help": "OpenAI: https://api.openai.com/v1\n\nOllama (example): http://localhost:11434/v1\n\nOpenRouter: https://openrouter.ai/api/v1",
"help": "OpenAI: https://api.openai.com/v1\n\n\n\nAnthropic: https://api.anthropic.com/v1\n\nOllama (example): http://localhost:11434/v1\n\nOpenRouter: https://openrouter.ai/api/v1",
"sensitive": False
},
"LLM_API_KEY": {
"description": "API key for your LLM provider",
"help": "For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key\n\nFor OpenRouter: https://openrouter.ai/keys\n\nFor Ollama, no need to set this unless you specifically configured an API key",
"help": "For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key\n\nFor Anthropic: https://console.anthropic.com/account/keys\n\nFor OpenRouter: https://openrouter.ai/keys\n\nFor Ollama, no need to set this unless you specifically configured an API key",
"sensitive": True
},
"OPENAI_API_KEY": {