Fixes issues with OpenRouter/Anthropic (disabling streaming until better Pydantic AI implementation)
This commit is contained in:
parent
53cfd5e6a7
commit
fcca718d3a
@ -2,16 +2,18 @@
|
|||||||
# OpenAI: https://api.openai.com/v1
|
# OpenAI: https://api.openai.com/v1
|
||||||
# Ollama (example): http://localhost:11434/v1
|
# Ollama (example): http://localhost:11434/v1
|
||||||
# OpenRouter: https://openrouter.ai/api/v1
|
# OpenRouter: https://openrouter.ai/api/v1
|
||||||
|
# Anthropic: https://api.anthropic.com/v1
|
||||||
BASE_URL=
|
BASE_URL=
|
||||||
|
|
||||||
# For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
|
# 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 OpenRouter: https://openrouter.ai/keys
|
||||||
# For Ollama, no need to set this unless you specifically configured an API key
|
# For Ollama, no need to set this unless you specifically configured an API key
|
||||||
LLM_API_KEY=
|
LLM_API_KEY=
|
||||||
|
|
||||||
# Get your Open AI API Key by following these instructions -
|
# 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
|
# 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.
|
# No need to set this if using Ollama.
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
- Docker (optional but preferred)
|
||||||
- Python 3.11+
|
- Python 3.11+
|
||||||
- Supabase account (for vector database)
|
- 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
|
### Installation
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from pydantic_ai.models.anthropic import AnthropicModel
|
||||||
from pydantic_ai.models.openai import OpenAIModel
|
from pydantic_ai.models.openai import OpenAIModel
|
||||||
from pydantic_ai import Agent, RunContext
|
from pydantic_ai import Agent, RunContext
|
||||||
from langgraph.graph import StateGraph, START, END
|
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'
|
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'
|
api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided'
|
||||||
|
|
||||||
is_ollama = "localhost" in base_url.lower()
|
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(
|
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.',
|
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(
|
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.',
|
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(
|
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.',
|
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))
|
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
|
||||||
|
|
||||||
# Run the agent in a stream
|
# Run the agent in a stream
|
||||||
if is_ollama:
|
if not is_openai:
|
||||||
writer = get_stream_writer()
|
writer = get_stream_writer()
|
||||||
result = await pydantic_ai_coder.run(state['latest_user_message'], deps=deps, message_history= message_history)
|
result = await pydantic_ai_coder.run(state['latest_user_message'], deps=deps, message_history= message_history)
|
||||||
writer(result.data)
|
writer(result.data)
|
||||||
@ -178,7 +187,7 @@ async def finish_conversation(state: AgentState, writer):
|
|||||||
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
|
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
|
||||||
|
|
||||||
# Run the agent in a stream
|
# Run the agent in a stream
|
||||||
if is_ollama:
|
if not is_openai:
|
||||||
writer = get_stream_writer()
|
writer = get_stream_writer()
|
||||||
result = await end_conversation_agent.run(state['latest_user_message'], message_history= message_history)
|
result = await end_conversation_agent.run(state['latest_user_message'], message_history= message_history)
|
||||||
writer(result.data)
|
writer(result.data)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import json
|
|||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pydantic_ai import Agent, ModelRetry, RunContext
|
from pydantic_ai import Agent, ModelRetry, RunContext
|
||||||
|
from pydantic_ai.models.anthropic import AnthropicModel
|
||||||
from pydantic_ai.models.openai import OpenAIModel
|
from pydantic_ai.models.openai import OpenAIModel
|
||||||
from openai import AsyncOpenAI
|
from openai import AsyncOpenAI
|
||||||
from supabase import Client
|
from supabase import Client
|
||||||
@ -24,13 +25,15 @@ load_dotenv()
|
|||||||
llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
|
llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
|
||||||
base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1'
|
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'
|
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'
|
embedding_model = get_env_var('EMBEDDING_MODEL') or 'text-embedding-3-small'
|
||||||
|
|
||||||
logfire.configure(send_to_logfire='if-token-present')
|
logfire.configure(send_to_logfire='if-token-present')
|
||||||
|
|
||||||
is_ollama = "localhost" in base_url.lower()
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PydanticAIDeps:
|
class PydanticAIDeps:
|
||||||
supabase: Client
|
supabase: Client
|
||||||
@ -38,41 +41,217 @@ class PydanticAIDeps:
|
|||||||
reasoner_output: str
|
reasoner_output: str
|
||||||
|
|
||||||
system_prompt = """
|
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,
|
[CORE RESPONSIBILITIES]
|
||||||
including examples, an API reference, and other resources to help you build Pydantic AI agents.
|
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.
|
[CODE STRUCTURE AND DELIVERABLES]
|
||||||
The user will describe the AI agent they want to build, or if they don't, guide them towards doing so.
|
All new agents must include these files with complete, production-ready code:
|
||||||
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.
|
|
||||||
|
|
||||||
It's important for you to search through multiple Pydantic AI documentation pages to get all the information you need.
|
1. agent.py
|
||||||
Almost never stick to just one page - use RAG and the other documentation tools multiple times when you are creating
|
- Primary agent definition and configuration
|
||||||
an AI agent from scratch for the user.
|
- 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:
|
3. agent_prompts.py
|
||||||
- `agent.py`: The main agent file, which is where the Pydantic AI agent is defined.
|
- System prompts
|
||||||
- `agent_tools.py`: A tools file for the agent, which is where all the tool functions are defined. Use this for more complex agents.
|
- Task-specific prompts
|
||||||
- `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.
|
- Conversation templates
|
||||||
- `.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.
|
- Instruction sets
|
||||||
- `requirements.txt`: Don't include any versions, just the top level package names needed for the agent.
|
|
||||||
|
|
||||||
~~ 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.
|
5. requirements.txt
|
||||||
- When you first look at the documentation, always start with RAG.
|
- Core dependencies without versions
|
||||||
Then also always check the list of available documentation pages and retrieve the content of page(s) if it'll help.
|
- User-specified packages included
|
||||||
- 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.
|
[DOCUMENTATION WORKFLOW]
|
||||||
- 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.
|
1. Initial Research
|
||||||
- When refining an existing AI agent build in a conversation, just share the code changes necessary.
|
- Begin with RAG search for relevant documentation
|
||||||
- Each time you respond to the user, ask them to let you know either if they need changes or the code looks good.
|
- 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(
|
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
|
user_query: The user's question or query
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A formatted string containing the top 5 most relevant documentation chunks
|
A formatted string containing the top 4 most relevant documentation chunks
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get the embedding for the query
|
# Get the embedding for the query
|
||||||
@ -126,7 +305,7 @@ async def retrieve_relevant_documentation(ctx: RunContext[PydanticAIDeps], user_
|
|||||||
'match_site_pages',
|
'match_site_pages',
|
||||||
{
|
{
|
||||||
'query_embedding': query_embedding,
|
'query_embedding': query_embedding,
|
||||||
'match_count': 5,
|
'match_count': 4,
|
||||||
'filter': {'source': 'pydantic_ai_docs'}
|
'filter': {'source': 'pydantic_ai_docs'}
|
||||||
}
|
}
|
||||||
).execute()
|
).execute()
|
||||||
@ -220,8 +399,9 @@ async def get_page_content(ctx: RunContext[PydanticAIDeps], url: str) -> str:
|
|||||||
for chunk in result.data:
|
for chunk in result.data:
|
||||||
formatted_content.append(chunk['content'])
|
formatted_content.append(chunk['content'])
|
||||||
|
|
||||||
# Join everything together
|
# Join everything together but limit the characters in case the page is massive (there are a coule big ones)
|
||||||
return "\n\n".join(formatted_content)
|
# 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:
|
except Exception as e:
|
||||||
print(f"Error retrieving page content: {e}")
|
print(f"Error retrieving page content: {e}")
|
||||||
|
|||||||
@ -2,16 +2,18 @@
|
|||||||
# OpenAI: https://api.openai.com/v1
|
# OpenAI: https://api.openai.com/v1
|
||||||
# Ollama (example): http://localhost:11434/v1
|
# Ollama (example): http://localhost:11434/v1
|
||||||
# OpenRouter: https://openrouter.ai/api/v1
|
# OpenRouter: https://openrouter.ai/api/v1
|
||||||
|
# Anthropic: https://api.anthropic.com/v1
|
||||||
BASE_URL=
|
BASE_URL=
|
||||||
|
|
||||||
# For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
|
# 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 OpenRouter: https://openrouter.ai/keys
|
||||||
# For Ollama, no need to set this unless you specifically configured an API key
|
# For Ollama, no need to set this unless you specifically configured an API key
|
||||||
LLM_API_KEY=
|
LLM_API_KEY=
|
||||||
|
|
||||||
# Get your Open AI API Key by following these instructions -
|
# 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
|
# 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.
|
# No need to set this if using Ollama.
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ This version continues to support both local LLMs with Ollama and cloud-based LL
|
|||||||
- Docker (optional but preferred)
|
- Docker (optional but preferred)
|
||||||
- Python 3.11+
|
- Python 3.11+
|
||||||
- Supabase account (for vector database)
|
- 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
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from pydantic_ai.models.anthropic import AnthropicModel
|
||||||
from pydantic_ai.models.openai import OpenAIModel
|
from pydantic_ai.models.openai import OpenAIModel
|
||||||
from pydantic_ai import Agent, RunContext
|
from pydantic_ai import Agent, RunContext
|
||||||
from langgraph.graph import StateGraph, START, END
|
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'
|
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'
|
api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided'
|
||||||
|
|
||||||
is_ollama = "localhost" in base_url.lower()
|
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(
|
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.',
|
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(
|
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.',
|
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(
|
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.',
|
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))
|
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
|
||||||
|
|
||||||
# Run the agent in a stream
|
# Run the agent in a stream
|
||||||
if is_ollama:
|
if not is_openai:
|
||||||
writer = get_stream_writer()
|
writer = get_stream_writer()
|
||||||
result = await pydantic_ai_coder.run(state['latest_user_message'], deps=deps, message_history= message_history)
|
result = await pydantic_ai_coder.run(state['latest_user_message'], deps=deps, message_history= message_history)
|
||||||
writer(result.data)
|
writer(result.data)
|
||||||
@ -178,7 +187,7 @@ async def finish_conversation(state: AgentState, writer):
|
|||||||
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
|
message_history.extend(ModelMessagesTypeAdapter.validate_json(message_row))
|
||||||
|
|
||||||
# Run the agent in a stream
|
# Run the agent in a stream
|
||||||
if is_ollama:
|
if not is_openai:
|
||||||
writer = get_stream_writer()
|
writer = get_stream_writer()
|
||||||
result = await end_conversation_agent.run(state['latest_user_message'], message_history= message_history)
|
result = await end_conversation_agent.run(state['latest_user_message'], message_history= message_history)
|
||||||
writer(result.data)
|
writer(result.data)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import json
|
|||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pydantic_ai import Agent, ModelRetry, RunContext
|
from pydantic_ai import Agent, ModelRetry, RunContext
|
||||||
|
from pydantic_ai.models.anthropic import AnthropicModel
|
||||||
from pydantic_ai.models.openai import OpenAIModel
|
from pydantic_ai.models.openai import OpenAIModel
|
||||||
from openai import AsyncOpenAI
|
from openai import AsyncOpenAI
|
||||||
from supabase import Client
|
from supabase import Client
|
||||||
@ -24,13 +25,15 @@ load_dotenv()
|
|||||||
llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
|
llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini'
|
||||||
base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1'
|
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'
|
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'
|
embedding_model = get_env_var('EMBEDDING_MODEL') or 'text-embedding-3-small'
|
||||||
|
|
||||||
logfire.configure(send_to_logfire='if-token-present')
|
logfire.configure(send_to_logfire='if-token-present')
|
||||||
|
|
||||||
is_ollama = "localhost" in base_url.lower()
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PydanticAIDeps:
|
class PydanticAIDeps:
|
||||||
supabase: Client
|
supabase: Client
|
||||||
@ -38,41 +41,217 @@ class PydanticAIDeps:
|
|||||||
reasoner_output: str
|
reasoner_output: str
|
||||||
|
|
||||||
system_prompt = """
|
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,
|
[CORE RESPONSIBILITIES]
|
||||||
including examples, an API reference, and other resources to help you build Pydantic AI agents.
|
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.
|
[CODE STRUCTURE AND DELIVERABLES]
|
||||||
The user will describe the AI agent they want to build, or if they don't, guide them towards doing so.
|
All new agents must include these files with complete, production-ready code:
|
||||||
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.
|
|
||||||
|
|
||||||
It's important for you to search through multiple Pydantic AI documentation pages to get all the information you need.
|
1. agent.py
|
||||||
Almost never stick to just one page - use RAG and the other documentation tools multiple times when you are creating
|
- Primary agent definition and configuration
|
||||||
an AI agent from scratch for the user.
|
- 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:
|
3. agent_prompts.py
|
||||||
- `agent.py`: The main agent file, which is where the Pydantic AI agent is defined.
|
- System prompts
|
||||||
- `agent_tools.py`: A tools file for the agent, which is where all the tool functions are defined. Use this for more complex agents.
|
- Task-specific prompts
|
||||||
- `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.
|
- Conversation templates
|
||||||
- `.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.
|
- Instruction sets
|
||||||
- `requirements.txt`: Don't include any versions, just the top level package names needed for the agent.
|
|
||||||
|
|
||||||
~~ 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.
|
5. requirements.txt
|
||||||
- When you first look at the documentation, always start with RAG.
|
- Core dependencies without versions
|
||||||
Then also always check the list of available documentation pages and retrieve the content of page(s) if it'll help.
|
- User-specified packages included
|
||||||
- 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.
|
[DOCUMENTATION WORKFLOW]
|
||||||
- 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.
|
1. Initial Research
|
||||||
- When refining an existing AI agent build in a conversation, just share the code changes necessary.
|
- Begin with RAG search for relevant documentation
|
||||||
- Each time you respond to the user, ask them to let you know either if they need changes or the code looks good.
|
- 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(
|
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
|
user_query: The user's question or query
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A formatted string containing the top 5 most relevant documentation chunks
|
A formatted string containing the top 4 most relevant documentation chunks
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get the embedding for the query
|
# Get the embedding for the query
|
||||||
@ -126,7 +305,7 @@ async def retrieve_relevant_documentation(ctx: RunContext[PydanticAIDeps], user_
|
|||||||
'match_site_pages',
|
'match_site_pages',
|
||||||
{
|
{
|
||||||
'query_embedding': query_embedding,
|
'query_embedding': query_embedding,
|
||||||
'match_count': 5,
|
'match_count': 4,
|
||||||
'filter': {'source': 'pydantic_ai_docs'}
|
'filter': {'source': 'pydantic_ai_docs'}
|
||||||
}
|
}
|
||||||
).execute()
|
).execute()
|
||||||
@ -220,8 +399,9 @@ async def get_page_content(ctx: RunContext[PydanticAIDeps], url: str) -> str:
|
|||||||
for chunk in result.data:
|
for chunk in result.data:
|
||||||
formatted_content.append(chunk['content'])
|
formatted_content.append(chunk['content'])
|
||||||
|
|
||||||
# Join everything together
|
# Join everything together but limit the characters in case the page is massive (there are a coule big ones)
|
||||||
return "\n\n".join(formatted_content)
|
# 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:
|
except Exception as e:
|
||||||
print(f"Error retrieving page content: {e}")
|
print(f"Error retrieving page content: {e}")
|
||||||
|
|||||||
@ -21,6 +21,7 @@ from openai import AsyncOpenAI
|
|||||||
from supabase import Client, create_client
|
from supabase import Client, create_client
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from utils.utils import get_env_var, save_env_var, write_to_log
|
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
|
# Import all the message part classes
|
||||||
from pydantic_ai.messages import (
|
from pydantic_ai.messages import (
|
||||||
@ -71,13 +72,13 @@ st.set_page_config(
|
|||||||
layout="wide",
|
layout="wide",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set custom theme colors to match Archon logo (pink and green)
|
# Set custom theme colors to match Archon logo (green and pink)
|
||||||
# Primary color (pink) and secondary color (green)
|
# Primary color (green) and secondary color (pink)
|
||||||
st.markdown("""
|
st.markdown("""
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #00CC99; /* Pink */
|
--primary-color: #00CC99; /* Green */
|
||||||
--secondary-color: #FF69B4; /* Green */
|
--secondary-color: #EB2D8C; /* Pink */
|
||||||
--text-color: #262730;
|
--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)
|
create_new_tab_button("Go to the Documentation Section (New Tab)", "Documentation", key="goto_docs", use_container_width=True)
|
||||||
|
|
||||||
# Step 4: Agent Service
|
# 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("""
|
st.markdown("""
|
||||||
### Agent Service Setup
|
### MCP Agent Service Setup
|
||||||
|
|
||||||
Start the graph service for agent generation:
|
Start the graph service for agent generation:
|
||||||
|
|
||||||
@ -879,8 +880,8 @@ def show_manual_sql_instructions(sql, recreate=False):
|
|||||||
|
|
||||||
def agent_service_tab():
|
def agent_service_tab():
|
||||||
"""Display the agent service interface for managing the graph service"""
|
"""Display the agent service interface for managing the graph service"""
|
||||||
st.header("Agent Service")
|
st.header("MCP Agent Service")
|
||||||
st.write("Start, restart, and monitor the Archon agent service.")
|
st.write("Start, restart, and monitor the Archon agent service for MCP.")
|
||||||
|
|
||||||
# Initialize session state variables if they don't exist
|
# Initialize session state variables if they don't exist
|
||||||
if "service_process" not in st.session_state:
|
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("- 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("- 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.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
|
# Define environment variables and their descriptions from .env.example
|
||||||
env_vars = {
|
env_vars = {
|
||||||
"BASE_URL": {
|
"BASE_URL": {
|
||||||
"description": "Base URL for the OpenAI instance (default is https://api.openai.com/v1)",
|
"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
|
"sensitive": False
|
||||||
},
|
},
|
||||||
"LLM_API_KEY": {
|
"LLM_API_KEY": {
|
||||||
"description": "API key for your LLM provider",
|
"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
|
"sensitive": True
|
||||||
},
|
},
|
||||||
"OPENAI_API_KEY": {
|
"OPENAI_API_KEY": {
|
||||||
@ -1207,7 +1209,7 @@ async def main():
|
|||||||
query_params = st.query_params
|
query_params = st.query_params
|
||||||
if "tab" in query_params:
|
if "tab" in query_params:
|
||||||
tab_name = query_params["tab"]
|
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
|
st.session_state.selected_tab = tab_name
|
||||||
|
|
||||||
# Add sidebar navigation
|
# Add sidebar navigation
|
||||||
@ -1229,6 +1231,7 @@ async def main():
|
|||||||
docs_button = st.button("Documentation", use_container_width=True, key="docs_button")
|
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")
|
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")
|
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
|
# Update selected tab based on button clicks
|
||||||
if intro_button:
|
if intro_button:
|
||||||
@ -1245,6 +1248,8 @@ async def main():
|
|||||||
st.session_state.selected_tab = "Database"
|
st.session_state.selected_tab = "Database"
|
||||||
elif docs_button:
|
elif docs_button:
|
||||||
st.session_state.selected_tab = "Documentation"
|
st.session_state.selected_tab = "Documentation"
|
||||||
|
elif future_enhancements_button:
|
||||||
|
st.session_state.selected_tab = "Future Enhancements"
|
||||||
|
|
||||||
# Display the selected tab
|
# Display the selected tab
|
||||||
if st.session_state.selected_tab == "Intro":
|
if st.session_state.selected_tab == "Intro":
|
||||||
@ -1268,6 +1273,9 @@ async def main():
|
|||||||
elif st.session_state.selected_tab == "Documentation":
|
elif st.session_state.selected_tab == "Documentation":
|
||||||
st.title("Archon - Documentation")
|
st.title("Archon - Documentation")
|
||||||
documentation_tab()
|
documentation_tab()
|
||||||
|
elif st.session_state.selected_tab == "Future Enhancements":
|
||||||
|
st.title("Archon - Future Enhancements")
|
||||||
|
future_enhancements_tab()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@ -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("- 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("- 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.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
|
# Define environment variables and their descriptions from .env.example
|
||||||
env_vars = {
|
env_vars = {
|
||||||
"BASE_URL": {
|
"BASE_URL": {
|
||||||
"description": "Base URL for the OpenAI instance (default is https://api.openai.com/v1)",
|
"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
|
"sensitive": False
|
||||||
},
|
},
|
||||||
"LLM_API_KEY": {
|
"LLM_API_KEY": {
|
||||||
"description": "API key for your LLM provider",
|
"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
|
"sensitive": True
|
||||||
},
|
},
|
||||||
"OPENAI_API_KEY": {
|
"OPENAI_API_KEY": {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user