├── .dockerignore ├── .env.example ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── .streamlit └── config.toml ├── Dockerfile ├── LICENSE ├── README.md ├── agent-resources ├── examples │ ├── pydantic_github_agent.py │ ├── pydantic_mcp_agent.py │ └── pydantic_web_search_agent.py ├── mcps │ ├── airtable.json │ ├── brave_search.json │ ├── chroma.json │ ├── file_system.json │ ├── firecrawl.json │ ├── git.json │ ├── github.json │ ├── google_drive.json │ ├── qdrant.json │ ├── redis.json │ ├── slack.json │ └── sqlite.json └── tools │ ├── get_github_file.py │ ├── get_github_file_structure.py │ ├── get_github_repo_info.py │ └── web_search.py ├── archon ├── __init__.py ├── advisor_agent.py ├── agent_prompts.py ├── agent_tools.py ├── archon_graph.py ├── crawl_pydantic_ai_docs.py ├── langgraph.json ├── pydantic_ai_coder.py └── refiner_agents │ ├── agent_refiner_agent.py │ ├── prompt_refiner_agent.py │ └── tools_refiner_agent.py ├── graph_service.py ├── iterations ├── v1-single-agent │ ├── .env.example │ ├── README.md │ ├── crawl_pydantic_ai_docs.py │ ├── pydantic_ai_coder.py │ ├── requirements.txt │ ├── site_pages.sql │ └── streamlit_ui.py ├── v2-agentic-workflow │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── archon_graph.py │ ├── crawl_pydantic_ai_docs.py │ ├── langgraph.json │ ├── ollama_site_pages.sql │ ├── pydantic_ai_coder.py │ ├── requirements.txt │ ├── site_pages.sql │ └── streamlit_ui.py ├── v3-mcp-support │ ├── .env.example │ ├── README.md │ ├── archon │ │ ├── __init__.py │ │ ├── archon_graph.py │ │ ├── crawl_pydantic_ai_docs.py │ │ ├── langgraph.json │ │ └── pydantic_ai_coder.py │ ├── graph_service.py │ ├── mcp-config.json │ ├── mcp_server.py │ ├── requirements.txt │ ├── setup_mcp.py │ ├── streamlit_ui.py │ └── utils │ │ ├── ollama_site_pages.sql │ │ ├── site_pages.sql │ │ └── utils.py ├── v4-streamlit-ui-overhaul │ ├── .dockerignore │ ├── .env.example │ ├── .gitignore │ ├── .streamlit │ │ └── config.toml │ ├── Dockerfile │ ├── README.md │ ├── archon │ │ ├── __init__.py │ │ ├── archon_graph.py │ │ ├── crawl_pydantic_ai_docs.py │ │ ├── langgraph.json │ │ └── pydantic_ai_coder.py │ ├── future_enhancements.py │ ├── graph_service.py │ ├── mcp │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── mcp_server.py │ │ └── requirements.txt │ ├── mcp_server.py │ ├── public │ │ ├── Archon.png │ │ └── ArchonLightGrey.png │ ├── requirements.txt │ ├── run_docker.py │ ├── streamlit_ui.py │ └── utils │ │ ├── site_pages.sql │ │ └── utils.py ├── v5-parallel-specialized-agents │ ├── .dockerignore │ ├── .env.example │ ├── Dockerfile │ ├── README.md │ ├── archon │ │ ├── __init__.py │ │ ├── agent_prompts.py │ │ ├── agent_tools.py │ │ ├── archon_graph.py │ │ ├── crawl_pydantic_ai_docs.py │ │ ├── langgraph.json │ │ ├── pydantic_ai_coder.py │ │ └── refiner_agents │ │ │ ├── agent_refiner_agent.py │ │ │ ├── prompt_refiner_agent.py │ │ │ └── tools_refiner_agent.py │ ├── graph_service.py │ ├── mcp │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── mcp_server.py │ │ └── requirements.txt │ ├── public │ │ ├── Archon.png │ │ ├── ArchonGraph.png │ │ └── ArchonLightGrey.png │ ├── requirements.txt │ ├── run_docker.py │ ├── streamlit_pages │ │ ├── __init__.py │ │ ├── agent_service.py │ │ ├── chat.py │ │ ├── database.py │ │ ├── documentation.py │ │ ├── environment.py │ │ ├── future_enhancements.py │ │ ├── intro.py │ │ ├── mcp.py │ │ └── styles.py │ ├── streamlit_ui.py │ └── utils │ │ ├── site_pages.sql │ │ └── utils.py └── v6-tool-library-integration │ ├── .dockerignore │ ├── .env.example │ ├── Dockerfile │ ├── README.md │ ├── agent-resources │ ├── examples │ │ ├── pydantic_github_agent.py │ │ ├── pydantic_mcp_agent.py │ │ └── pydantic_web_search_agent.py │ ├── mcps │ │ ├── airtable.json │ │ ├── brave_search.json │ │ ├── chroma.json │ │ ├── file_system.json │ │ ├── firecrawl.json │ │ ├── git.json │ │ ├── github.json │ │ ├── google_drive.json │ │ ├── qdrant.json │ │ ├── redis.json │ │ ├── slack.json │ │ └── sqlite.json │ └── tools │ │ ├── get_github_file.py │ │ ├── get_github_file_structure.py │ │ ├── get_github_repo_info.py │ │ └── web_search.py │ ├── archon │ ├── __init__.py │ ├── advisor_agent.py │ ├── agent_prompts.py │ ├── agent_tools.py │ ├── archon_graph.py │ ├── crawl_pydantic_ai_docs.py │ ├── langgraph.json │ ├── pydantic_ai_coder.py │ └── refiner_agents │ │ ├── agent_refiner_agent.py │ │ ├── prompt_refiner_agent.py │ │ └── tools_refiner_agent.py │ ├── graph_service.py │ ├── mcp │ ├── .dockerignore │ ├── Dockerfile │ ├── mcp_server.py │ └── requirements.txt │ ├── public │ ├── Archon.png │ ├── ArchonGraph.png │ └── ArchonLightGrey.png │ ├── requirements.txt │ ├── run_docker.py │ ├── streamlit_pages │ ├── __init__.py │ ├── agent_service.py │ ├── chat.py │ ├── database.py │ ├── documentation.py │ ├── environment.py │ ├── future_enhancements.py │ ├── intro.py │ ├── mcp.py │ └── styles.py │ ├── streamlit_ui.py │ └── utils │ ├── site_pages.sql │ └── utils.py ├── mcp ├── .dockerignore ├── Dockerfile ├── mcp_server.py └── requirements.txt ├── public ├── Archon.png ├── ArchonGraph.png └── ArchonLightGrey.png ├── requirements.txt ├── run_docker.py ├── streamlit_pages ├── __init__.py ├── agent_service.py ├── chat.py ├── database.py ├── documentation.py ├── environment.py ├── future_enhancements.py ├── intro.py ├── mcp.py └── styles.py ├── streamlit_ui.py └── utils ├── site_pages.sql └── utils.py /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore specified folders 2 | iterations/ 3 | venv/ 4 | .langgraph_api/ 5 | .github/ 6 | __pycache__/ 7 | .env 8 | 9 | # Git related 10 | .git/ 11 | .gitignore 12 | .gitattributes 13 | 14 | # Python cache 15 | *.pyc 16 | *.pyo 17 | *.pyd 18 | .Python 19 | *.so 20 | .pytest_cache/ 21 | 22 | # Environment files 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # Logs 29 | *.log 30 | 31 | # IDE specific files 32 | .idea/ 33 | .vscode/ 34 | *.swp 35 | *.swo 36 | 37 | # Keep the example env file for reference 38 | !.env.example 39 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Base URL for the OpenAI instance (default is https://api.openai.com/v1) 2 | # OpenAI: https://api.openai.com/v1 3 | # Ollama (example): http://localhost:11434/v1 4 | # OpenRouter: https://openrouter.ai/api/v1 5 | # Anthropic: https://api.anthropic.com/v1 6 | BASE_URL= 7 | 8 | # For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 9 | # For Anthropic: https://console.anthropic.com/account/keys 10 | # For OpenRouter: https://openrouter.ai/keys 11 | # For Ollama, no need to set this unless you specifically configured an API key 12 | LLM_API_KEY= 13 | 14 | # Get your Open AI API Key by following these instructions - 15 | # https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 16 | # Even if using Anthropic or OpenRouter, you still need to set this for the embedding model. 17 | # No need to set this if using Ollama. 18 | OPENAI_API_KEY= 19 | 20 | # For the Supabase version (sample_supabase_agent.py), set your Supabase URL and Service Key. 21 | # Get your SUPABASE_URL from the API section of your Supabase project settings - 22 | # https://supabase.com/dashboard/project//settings/api 23 | SUPABASE_URL= 24 | 25 | # Get your SUPABASE_SERVICE_KEY from the API section of your Supabase project settings - 26 | # https://supabase.com/dashboard/project//settings/api 27 | # On this page it is called the service_role secret. 28 | SUPABASE_SERVICE_KEY= 29 | 30 | # The LLM you want to use for the reasoner (o3-mini, R1, QwQ, etc.). 31 | # Example: o3-mini 32 | # Example: deepseek-r1:7b-8k 33 | REASONER_MODEL= 34 | 35 | # The LLM you want to use for the primary agent/coder. 36 | # Example: gpt-4o-mini 37 | # Example: qwen2.5:14b-instruct-8k 38 | PRIMARY_MODEL= 39 | 40 | # Embedding model you want to use 41 | # Example for Ollama: nomic-embed-text 42 | # Example for OpenAI: text-embedding-3-small 43 | EMBEDDING_MODEL= -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help improve Archon 4 | title: '[BUG] ' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Description 10 | A clear and concise description of the issue. 11 | 12 | ## Steps to Reproduce 13 | 1. Go to '...' 14 | 2. Click on '....' 15 | 3. Scroll down to '....' 16 | 4. See error 17 | 18 | ## Expected Behavior 19 | A clear and concise description of what you expected to happen. 20 | 21 | ## Actual Behavior 22 | A clear and concise description of what actually happened. 23 | 24 | ## Screenshots 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | ## Environment 28 | - OS: [e.g. Windows 10, macOS Monterey, Ubuntu 22.04] 29 | - Python Version: [e.g. Python 3.13, Python 3.12] 30 | - Using MCP or Streamlit (or something else) 31 | 32 | ## Additional Context 33 | Add any other context about the problem here, such as: 34 | - Does this happen consistently or intermittently? 35 | - Were there any recent changes that might be related? 36 | - Any workarounds you've discovered? 37 | 38 | ## Possible Solution 39 | If you have suggestions on how to fix the issue or what might be causing it. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Archon Community 4 | url: https://thinktank.ottomator.ai/c/archon/30 5 | about: Please ask questions and start conversations about Archon here in the oTTomator Think Tank! -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for Archon 4 | title: '[FEATURE] ' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Describe the feature you'd like and why 10 | A clear and concise description of what you want to happen. 11 | 12 | ## User Impact 13 | Who would benefit from this feature and how? 14 | 15 | ## Implementation Details (optional) 16 | Any thoughts on how this might be implemented? 17 | 18 | ## Additional context 19 | Add any other screenshots, mockups, or context about the feature request here. -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | 9 | # Check for updates to GitHub Actions 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | 15 | # Check for updates to Python packages in root (actual iteration) 16 | - package-ecosystem: "pip" 17 | directory: "/" 18 | schedule: 19 | interval: "weekly" 20 | 21 | # Check for updates to Python packages in mcp 22 | - package-ecosystem: "pip" 23 | directory: "/mcp" 24 | schedule: 25 | interval: "weekly" 26 | 27 | # Check for updates in Dockerfile 28 | - package-ecosystem: "docker" 29 | directory: "/" 30 | schedule: 31 | interval: "weekly" 32 | 33 | # Check for updates in MCP Dockerfile 34 | - package-ecosystem: "docker" 35 | directory: "/mcp" 36 | schedule: 37 | interval: "weekly" 38 | 39 | 40 | # Aditional: Structure to maintain previous iterations 41 | 42 | # Update Version 1: Single Agent 43 | # - package-ecosystem: "pip" 44 | # directory: "/iterations/v1-single-agent" 45 | # schedule: 46 | # interval: "monthly" 47 | 48 | # Update Version 2: Agentic Workflow 49 | # - package-ecosystem: "pip" 50 | # directory: "/iterations/v2-agentic-workflow" 51 | # schedule: 52 | # interval: "monthly" 53 | 54 | # Upate Version 3: MCP Support 55 | # - package-ecosystem: "pip" 56 | # directory: "/iterations/v3-mcp-support" 57 | # schedule: 58 | # interval: "monthly" 59 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Archon 2 | 3 | on: 4 | push: 5 | branches: 6 | [ main, master ] 7 | pull_request: 8 | branches: 9 | [ main, master ] 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | build-locally: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | python-version: ['3.10', '3.11', '3.12', '3.13'] 20 | include: 21 | - python-version: '3.10' 22 | experimental: true 23 | - python-version: '3.12' 24 | experimental: true 25 | - python-version: '3.13' 26 | experimental: true 27 | fail-fast: false 28 | 29 | # Test on newer Python versions 30 | continue-on-error: ${{ matrix.experimental || false }} 31 | steps: 32 | - name: Checkout code 33 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 34 | 35 | - name: Set up Python ${{ matrix.python-version }} 36 | uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 37 | with: 38 | python-version: ${{ matrix.python-version }} 39 | 40 | - name: Install dependencies 41 | run: | 42 | python -m venv venv 43 | source venv/bin/activate 44 | pip install -r requirements.txt 45 | 46 | - name: Verify code compilation 47 | run: | 48 | source venv/bin/activate 49 | python -m compileall -f . 50 | 51 | build-docker: 52 | runs-on: ubuntu-latest 53 | strategy: 54 | matrix: 55 | python-version: ['3.10', '3.11', '3.12', '3.13'] 56 | include: 57 | - python-version: '3.10' 58 | experimental: true 59 | - python-version: '3.13' 60 | experimental: true 61 | fail-fast: false 62 | 63 | # Test on newer Python versions 64 | continue-on-error: ${{ matrix.experimental || false }} 65 | steps: 66 | - name: Checkout code 67 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 68 | 69 | - name: Set up Python ${{ matrix.python-version }} 70 | uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 71 | with: 72 | python-version: ${{ matrix.python-version }} 73 | 74 | - name: Modify run_docker.py for CI environment 75 | run: | 76 | cp run_docker.py run_docker_ci.py 77 | # Modify the script to just build and verify containers without running them indefinitely 78 | sed -i 's/"-d",/"-d", "--rm",/g' run_docker_ci.py 79 | 80 | - name: Run Docker setup script 81 | run: | 82 | chmod +x run_docker_ci.py 83 | python run_docker_ci.py 84 | 85 | - name: Verify containers are built 86 | run: | 87 | docker images | grep archon 88 | docker images | grep archon-mcp 89 | 90 | - name: Test container running status 91 | run: | 92 | docker ps -a | grep archon-container 93 | 94 | - name: Stop containers 95 | run: | 96 | docker stop archon-container || true 97 | docker rm archon-container || true 98 | docker stop archon-mcp || true 99 | docker rm archon-mcp || true 100 | docker ps -a | grep archon || echo "All containers successfully removed" 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | workbench 3 | __pycache__ 4 | venv 5 | .langgraph_api 6 | 7 | # Files 8 | .env 9 | .env.temp 10 | .env.test 11 | env_vars.json -------------------------------------------------------------------------------- /.streamlit/config.toml: -------------------------------------------------------------------------------- 1 | [client] 2 | showErrorDetails = "none" 3 | 4 | [theme] 5 | primaryColor = "#EB2D8C" 6 | base="dark" -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Install system dependencies 6 | RUN apt-get update && apt-get install -y --no-install-recommends \ 7 | build-essential \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | # Copy requirements first for better caching 11 | COPY requirements.txt . 12 | RUN pip install --no-cache-dir -r requirements.txt 13 | 14 | # Copy the rest of the application 15 | COPY . . 16 | 17 | # Set environment variables 18 | ENV PYTHONUNBUFFERED=1 19 | ENV PYTHONPATH=/app 20 | 21 | # Expose port for Streamlit 22 | EXPOSE 8501 23 | 24 | # Expose port for the Archon Service (started within Streamlit) 25 | EXPOSE 8100 26 | 27 | # Set the entrypoint to run Streamlit directly 28 | CMD ["streamlit", "run", "streamlit_ui.py", "--server.port=8501", "--server.address=0.0.0.0"] 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 oTTomator and Archon contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /agent-resources/examples/pydantic_mcp_agent.py: -------------------------------------------------------------------------------- 1 | from pydantic_ai.providers.openai import OpenAIProvider 2 | from pydantic_ai.models.openai import OpenAIModel 3 | from pydantic_ai.mcp import MCPServerStdio 4 | from pydantic_ai import Agent 5 | from dotenv import load_dotenv 6 | import asyncio 7 | import os 8 | 9 | load_dotenv() 10 | 11 | def get_model(): 12 | llm = os.getenv('MODEL_CHOICE', 'gpt-4o-mini') 13 | base_url = os.getenv('BASE_URL', 'https://api.openai.com/v1') 14 | api_key = os.getenv('LLM_API_KEY', 'no-api-key-provided') 15 | 16 | return OpenAIModel(llm, provider=OpenAIProvider(base_url=base_url, api_key=api_key)) 17 | 18 | server = MCPServerStdio( 19 | 'npx', 20 | ['-y', '@modelcontextprotocol/server-brave-search', 'stdio'], 21 | env={"BRAVE_API_KEY": os.getenv("BRAVE_API_KEY")} 22 | ) 23 | agent = Agent(get_model(), mcp_servers=[server]) 24 | 25 | 26 | async def main(): 27 | async with agent.run_mcp_servers(): 28 | result = await agent.run('What is new with Gemini 2.5 Pro?') 29 | print(result.data) 30 | user_input = input("Press enter to quit...") 31 | 32 | if __name__ == '__main__': 33 | asyncio.run(main()) -------------------------------------------------------------------------------- /agent-resources/examples/pydantic_web_search_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | import asyncio 4 | import os 5 | from dataclasses import dataclass 6 | from datetime import datetime 7 | from typing import Any 8 | 9 | import logfire 10 | from devtools import debug 11 | from httpx import AsyncClient 12 | from dotenv import load_dotenv 13 | 14 | from openai import AsyncOpenAI 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from pydantic_ai import Agent, ModelRetry, RunContext 17 | 18 | load_dotenv() 19 | llm = os.getenv('LLM_MODEL', 'gpt-4o') 20 | 21 | client = AsyncOpenAI( 22 | base_url = 'http://localhost:11434/v1', 23 | api_key='ollama' 24 | ) 25 | 26 | model = OpenAIModel(llm) if llm.lower().startswith("gpt") else OpenAIModel(llm, openai_client=client) 27 | 28 | # 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured 29 | logfire.configure(send_to_logfire='if-token-present') 30 | 31 | 32 | @dataclass 33 | class Deps: 34 | client: AsyncClient 35 | brave_api_key: str | None 36 | 37 | 38 | web_search_agent = Agent( 39 | model, 40 | system_prompt=f'You are an expert at researching the web to answer user questions. The current date is: {datetime.now().strftime("%Y-%m-%d")}', 41 | deps_type=Deps, 42 | retries=2 43 | ) 44 | 45 | 46 | @web_search_agent.tool 47 | async def search_web( 48 | ctx: RunContext[Deps], web_query: str 49 | ) -> str: 50 | """Search the web given a query defined to answer the user's question. 51 | 52 | Args: 53 | ctx: The context. 54 | web_query: The query for the web search. 55 | 56 | Returns: 57 | str: The search results as a formatted string. 58 | """ 59 | if ctx.deps.brave_api_key is None: 60 | return "This is a test web search result. Please provide a Brave API key to get real search results." 61 | 62 | headers = { 63 | 'X-Subscription-Token': ctx.deps.brave_api_key, 64 | 'Accept': 'application/json', 65 | } 66 | 67 | with logfire.span('calling Brave search API', query=web_query) as span: 68 | r = await ctx.deps.client.get( 69 | 'https://api.search.brave.com/res/v1/web/search', 70 | params={ 71 | 'q': web_query, 72 | 'count': 5, 73 | 'text_decorations': True, 74 | 'search_lang': 'en' 75 | }, 76 | headers=headers 77 | ) 78 | r.raise_for_status() 79 | data = r.json() 80 | span.set_attribute('response', data) 81 | 82 | results = [] 83 | 84 | # Add web results in a nice formatted way 85 | web_results = data.get('web', {}).get('results', []) 86 | for item in web_results[:3]: 87 | title = item.get('title', '') 88 | description = item.get('description', '') 89 | url = item.get('url', '') 90 | if title and description: 91 | results.append(f"Title: {title}\nSummary: {description}\nSource: {url}\n") 92 | 93 | return "\n".join(results) if results else "No results found for the query." 94 | 95 | 96 | async def main(): 97 | async with AsyncClient() as client: 98 | brave_api_key = os.getenv('BRAVE_API_KEY', None) 99 | deps = Deps(client=client, brave_api_key=brave_api_key) 100 | 101 | result = await web_search_agent.run( 102 | 'Give me some articles talking about the new release of React 19.', deps=deps 103 | ) 104 | 105 | debug(result) 106 | print('Response:', result.data) 107 | 108 | 109 | if __name__ == '__main__': 110 | asyncio.run(main()) -------------------------------------------------------------------------------- /agent-resources/mcps/airtable.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "airtable": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "airtable-mcp-server" 8 | ], 9 | "env": { 10 | "AIRTABLE_API_KEY": "pat123.abc123" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /agent-resources/mcps/brave_search.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "brave-search": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-brave-search" 8 | ], 9 | "env": { 10 | "BRAVE_API_KEY": "YOUR_API_KEY_HERE" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /agent-resources/mcps/chroma.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "chroma": { 4 | "command": "uvx", 5 | "args": [ 6 | "chroma-mcp", 7 | "--client-type", 8 | "persistent", 9 | "--data-dir", 10 | "/full/path/to/your/data/directory" 11 | ] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /agent-resources/mcps/file_system.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "filesystem": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-filesystem", 8 | "/Users/username/Desktop", 9 | "/path/to/other/allowed/dir" 10 | ] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /agent-resources/mcps/firecrawl.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "mcp-server-firecrawl": { 4 | "command": "npx", 5 | "args": ["-y", "firecrawl-mcp"], 6 | "env": { 7 | "FIRECRAWL_API_KEY": "YOUR_API_KEY_HERE", 8 | 9 | "FIRECRAWL_RETRY_MAX_ATTEMPTS": "5", 10 | "FIRECRAWL_RETRY_INITIAL_DELAY": "2000", 11 | "FIRECRAWL_RETRY_MAX_DELAY": "30000", 12 | "FIRECRAWL_RETRY_BACKOFF_FACTOR": "3", 13 | 14 | "FIRECRAWL_CREDIT_WARNING_THRESHOLD": "2000", 15 | "FIRECRAWL_CREDIT_CRITICAL_THRESHOLD": "500" 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /agent-resources/mcps/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "git": { 4 | "command": "docker", 5 | "args": [ 6 | "run", 7 | "--rm", 8 | "-i", 9 | "--mount", "type=bind,src=/Users/username/Desktop,dst=/projects/Desktop", 10 | "--mount", "type=bind,src=/path/to/other/allowed/dir,dst=/projects/other/allowed/dir,ro", 11 | "--mount", "type=bind,src=/path/to/file.txt,dst=/projects/path/to/file.txt", 12 | "mcp/git" 13 | ] 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /agent-resources/mcps/github.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "github": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-github" 8 | ], 9 | "env": { 10 | "GITHUB_PERSONAL_ACCESS_TOKEN": "" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /agent-resources/mcps/google_drive.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "gdrive": { 4 | "command": "docker", 5 | "args": ["run", "-i", "--rm", "-v", "mcp-gdrive:/gdrive-server", "-e", "GDRIVE_CREDENTIALS_PATH=/gdrive-server/credentials.json", "mcp/gdrive"] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /agent-resources/mcps/qdrant.json: -------------------------------------------------------------------------------- 1 | { 2 | "qdrant": { 3 | "command": "uvx", 4 | "args": ["mcp-server-qdrant"], 5 | "env": { 6 | "QDRANT_URL": "https://xyz-example.eu-central.aws.cloud.qdrant.io:6333", 7 | "QDRANT_API_KEY": "your_api_key", 8 | "COLLECTION_NAME": "your-collection-name", 9 | "EMBEDDING_MODEL": "sentence-transformers/all-MiniLM-L6-v2" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /agent-resources/mcps/redis.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "redis": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-redis", 8 | "redis://localhost:6379" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /agent-resources/mcps/slack.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "slack": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-slack" 8 | ], 9 | "env": { 10 | "SLACK_BOT_TOKEN": "xoxb-your-bot-token", 11 | "SLACK_TEAM_ID": "T01234567" 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /agent-resources/mcps/sqlite.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "sqlite": { 4 | "command": "docker", 5 | "args": [ 6 | "run", 7 | "--rm", 8 | "-i", 9 | "-v", 10 | "mcp-test:/mcp", 11 | "mcp/sqlite", 12 | "--db-path", 13 | "/mcp/test.db" 14 | ] 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /agent-resources/tools/get_github_file.py: -------------------------------------------------------------------------------- 1 | @github_agent.tool 2 | async def get_file_content(ctx: RunContext[GitHubDeps], github_url: str, file_path: str) -> str: 3 | """Get the content of a specific file from the GitHub repository. 4 | 5 | Args: 6 | ctx: The context. 7 | github_url: The GitHub repository URL. 8 | file_path: Path to the file within the repository. 9 | 10 | Returns: 11 | str: File content as a string. 12 | """ 13 | match = re.search(r'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$', github_url) 14 | if not match: 15 | return "Invalid GitHub URL format" 16 | 17 | owner, repo = match.groups() 18 | headers = {'Authorization': f'token {ctx.deps.github_token}'} if ctx.deps.github_token else {} 19 | 20 | response = await ctx.deps.client.get( 21 | f'https://raw.githubusercontent.com/{owner}/{repo}/main/{file_path}', 22 | headers=headers 23 | ) 24 | 25 | if response.status_code != 200: 26 | # Try with master branch if main fails 27 | response = await ctx.deps.client.get( 28 | f'https://raw.githubusercontent.com/{owner}/{repo}/master/{file_path}', 29 | headers=headers 30 | ) 31 | if response.status_code != 200: 32 | return f"Failed to get file content: {response.text}" 33 | 34 | return response.text -------------------------------------------------------------------------------- /agent-resources/tools/get_github_file_structure.py: -------------------------------------------------------------------------------- 1 | @github_agent.tool 2 | async def get_repo_structure(ctx: RunContext[GitHubDeps], github_url: str) -> str: 3 | """Get the directory structure of a GitHub repository. 4 | 5 | Args: 6 | ctx: The context. 7 | github_url: The GitHub repository URL. 8 | 9 | Returns: 10 | str: Directory structure as a formatted string. 11 | """ 12 | match = re.search(r'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$', github_url) 13 | if not match: 14 | return "Invalid GitHub URL format" 15 | 16 | owner, repo = match.groups() 17 | headers = {'Authorization': f'token {ctx.deps.github_token}'} if ctx.deps.github_token else {} 18 | 19 | response = await ctx.deps.client.get( 20 | f'https://api.github.com/repos/{owner}/{repo}/git/trees/main?recursive=1', 21 | headers=headers 22 | ) 23 | 24 | if response.status_code != 200: 25 | # Try with master branch if main fails 26 | response = await ctx.deps.client.get( 27 | f'https://api.github.com/repos/{owner}/{repo}/git/trees/master?recursive=1', 28 | headers=headers 29 | ) 30 | if response.status_code != 200: 31 | return f"Failed to get repository structure: {response.text}" 32 | 33 | data = response.json() 34 | tree = data['tree'] 35 | 36 | # Build directory structure 37 | structure = [] 38 | for item in tree: 39 | if not any(excluded in item['path'] for excluded in ['.git/', 'node_modules/', '__pycache__/']): 40 | structure.append(f"{'📁 ' if item['type'] == 'tree' else '📄 '}{item['path']}") 41 | 42 | return "\n".join(structure) -------------------------------------------------------------------------------- /agent-resources/tools/get_github_repo_info.py: -------------------------------------------------------------------------------- 1 | @github_agent.tool 2 | async def get_repo_info(ctx: RunContext[GitHubDeps], github_url: str) -> str: 3 | """Get repository information including size and description using GitHub API. 4 | 5 | Args: 6 | ctx: The context. 7 | github_url: The GitHub repository URL. 8 | 9 | Returns: 10 | str: Repository information as a formatted string. 11 | """ 12 | match = re.search(r'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$', github_url) 13 | if not match: 14 | return "Invalid GitHub URL format" 15 | 16 | owner, repo = match.groups() 17 | headers = {'Authorization': f'token {ctx.deps.github_token}'} if ctx.deps.github_token else {} 18 | 19 | response = await ctx.deps.client.get( 20 | f'https://api.github.com/repos/{owner}/{repo}', 21 | headers=headers 22 | ) 23 | 24 | if response.status_code != 200: 25 | return f"Failed to get repository info: {response.text}" 26 | 27 | data = response.json() 28 | size_mb = data['size'] / 1024 29 | 30 | return ( 31 | f"Repository: {data['full_name']}\n" 32 | f"Description: {data['description']}\n" 33 | f"Size: {size_mb:.1f}MB\n" 34 | f"Stars: {data['stargazers_count']}\n" 35 | f"Language: {data['language']}\n" 36 | f"Created: {data['created_at']}\n" 37 | f"Last Updated: {data['updated_at']}" 38 | ) -------------------------------------------------------------------------------- /agent-resources/tools/web_search.py: -------------------------------------------------------------------------------- 1 | @web_search_agent.tool 2 | async def search_web( 3 | ctx: RunContext[Deps], web_query: str 4 | ) -> str: 5 | """Search the web given a query defined to answer the user's question. 6 | 7 | Args: 8 | ctx: The context. 9 | web_query: The query for the web search. 10 | 11 | Returns: 12 | str: The search results as a formatted string. 13 | """ 14 | if ctx.deps.brave_api_key is None: 15 | return "This is a test web search result. Please provide a Brave API key to get real search results." 16 | 17 | headers = { 18 | 'X-Subscription-Token': ctx.deps.brave_api_key, 19 | 'Accept': 'application/json', 20 | } 21 | 22 | with logfire.span('calling Brave search API', query=web_query) as span: 23 | r = await ctx.deps.client.get( 24 | 'https://api.search.brave.com/res/v1/web/search', 25 | params={ 26 | 'q': web_query, 27 | 'count': 5, 28 | 'text_decorations': True, 29 | 'search_lang': 'en' 30 | }, 31 | headers=headers 32 | ) 33 | r.raise_for_status() 34 | data = r.json() 35 | span.set_attribute('response', data) 36 | 37 | results = [] 38 | 39 | # Add web results in a nice formatted way 40 | web_results = data.get('web', {}).get('results', []) 41 | for item in web_results[:3]: 42 | title = item.get('title', '') 43 | description = item.get('description', '') 44 | url = item.get('url', '') 45 | if title and description: 46 | results.append(f"Title: {title}\nSummary: {description}\nSource: {url}\n") 47 | 48 | return "\n".join(results) if results else "No results found for the query." -------------------------------------------------------------------------------- /archon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/archon/__init__.py -------------------------------------------------------------------------------- /archon/advisor_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | from dataclasses import dataclass 4 | from dotenv import load_dotenv 5 | import logfire 6 | import asyncio 7 | import httpx 8 | import os 9 | import sys 10 | import json 11 | from typing import List 12 | from pydantic import BaseModel 13 | from pydantic_ai import Agent, ModelRetry, RunContext 14 | from pydantic_ai.models.anthropic import AnthropicModel 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from openai import AsyncOpenAI 17 | from supabase import Client 18 | 19 | # Add the parent directory to sys.path to allow importing from the parent directory 20 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 21 | from utils.utils import get_env_var 22 | from archon.agent_prompts import advisor_prompt 23 | from archon.agent_tools import get_file_content_tool 24 | 25 | load_dotenv() 26 | 27 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 28 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 29 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 30 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 31 | 32 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 33 | 34 | logfire.configure(send_to_logfire='if-token-present') 35 | 36 | @dataclass 37 | class AdvisorDeps: 38 | file_list: List[str] 39 | 40 | advisor_agent = Agent( 41 | model, 42 | system_prompt=advisor_prompt, 43 | deps_type=AdvisorDeps, 44 | retries=2 45 | ) 46 | 47 | @advisor_agent.system_prompt 48 | def add_file_list(ctx: RunContext[str]) -> str: 49 | joined_files = "\n".join(ctx.deps.file_list) 50 | return f""" 51 | 52 | Here is the list of all the files that you can pull the contents of with the 53 | 'get_file_content' tool if the example/tool/MCP server is relevant to the 54 | agent the user is trying to build: 55 | 56 | {joined_files} 57 | """ 58 | 59 | @advisor_agent.tool_plain 60 | def get_file_content(file_path: str) -> str: 61 | """ 62 | Retrieves the content of a specific file. Use this to get the contents of an example, tool, config for an MCP server 63 | 64 | Args: 65 | file_path: The path to the file 66 | 67 | Returns: 68 | The raw contents of the file 69 | """ 70 | return get_file_content_tool(file_path) -------------------------------------------------------------------------------- /archon/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "agent": "./archon_graph.py:agentic_flow" 5 | }, 6 | "env": "../.env" 7 | } 8 | -------------------------------------------------------------------------------- /archon/pydantic_ai_coder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | from dataclasses import dataclass 4 | from dotenv import load_dotenv 5 | import logfire 6 | import asyncio 7 | import httpx 8 | import os 9 | import sys 10 | import json 11 | from typing import List 12 | from pydantic import BaseModel 13 | from pydantic_ai import Agent, ModelRetry, RunContext 14 | from pydantic_ai.models.anthropic import AnthropicModel 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from openai import AsyncOpenAI 17 | from supabase import Client 18 | 19 | # Add the parent directory to sys.path to allow importing from the parent directory 20 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 21 | from utils.utils import get_env_var 22 | from archon.agent_prompts import primary_coder_prompt 23 | from archon.agent_tools import ( 24 | retrieve_relevant_documentation_tool, 25 | list_documentation_pages_tool, 26 | get_page_content_tool 27 | ) 28 | 29 | load_dotenv() 30 | 31 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 32 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 33 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 34 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 35 | 36 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 37 | 38 | logfire.configure(send_to_logfire='if-token-present') 39 | 40 | @dataclass 41 | class PydanticAIDeps: 42 | supabase: Client 43 | embedding_client: AsyncOpenAI 44 | reasoner_output: str 45 | advisor_output: str 46 | 47 | pydantic_ai_coder = Agent( 48 | model, 49 | system_prompt=primary_coder_prompt, 50 | deps_type=PydanticAIDeps, 51 | retries=2 52 | ) 53 | 54 | @pydantic_ai_coder.system_prompt 55 | def add_reasoner_output(ctx: RunContext[str]) -> str: 56 | return f""" 57 | 58 | Additional thoughts/instructions from the reasoner LLM. 59 | This scope includes documentation pages for you to search as well: 60 | {ctx.deps.reasoner_output} 61 | 62 | Recommended starting point from the advisor agent: 63 | {ctx.deps.advisor_output} 64 | """ 65 | 66 | @pydantic_ai_coder.tool 67 | async def retrieve_relevant_documentation(ctx: RunContext[PydanticAIDeps], user_query: str) -> str: 68 | """ 69 | Retrieve relevant documentation chunks based on the query with RAG. 70 | 71 | Args: 72 | ctx: The context including the Supabase client and OpenAI client 73 | user_query: The user's question or query 74 | 75 | Returns: 76 | A formatted string containing the top 4 most relevant documentation chunks 77 | """ 78 | return await retrieve_relevant_documentation_tool(ctx.deps.supabase, ctx.deps.embedding_client, user_query) 79 | 80 | @pydantic_ai_coder.tool 81 | async def list_documentation_pages(ctx: RunContext[PydanticAIDeps]) -> List[str]: 82 | """ 83 | Retrieve a list of all available Pydantic AI documentation pages. 84 | 85 | Returns: 86 | List[str]: List of unique URLs for all documentation pages 87 | """ 88 | return await list_documentation_pages_tool(ctx.deps.supabase) 89 | 90 | @pydantic_ai_coder.tool 91 | async def get_page_content(ctx: RunContext[PydanticAIDeps], url: str) -> str: 92 | """ 93 | Retrieve the full content of a specific documentation page by combining all its chunks. 94 | 95 | Args: 96 | ctx: The context including the Supabase client 97 | url: The URL of the page to retrieve 98 | 99 | Returns: 100 | str: The complete page content with all chunks combined in order 101 | """ 102 | return await get_page_content_tool(ctx.deps.supabase, url) -------------------------------------------------------------------------------- /archon/refiner_agents/agent_refiner_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | from dataclasses import dataclass 4 | from dotenv import load_dotenv 5 | import logfire 6 | import asyncio 7 | import httpx 8 | import os 9 | import sys 10 | import json 11 | from typing import List 12 | from pydantic import BaseModel 13 | from pydantic_ai import Agent, ModelRetry, RunContext 14 | from pydantic_ai.models.anthropic import AnthropicModel 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from openai import AsyncOpenAI 17 | from supabase import Client 18 | 19 | # Add the parent directory to sys.path to allow importing from the parent directory 20 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 21 | from utils.utils import get_env_var 22 | from archon.agent_prompts import agent_refiner_prompt 23 | from archon.agent_tools import ( 24 | retrieve_relevant_documentation_tool, 25 | list_documentation_pages_tool, 26 | get_page_content_tool 27 | ) 28 | 29 | load_dotenv() 30 | 31 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 32 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 33 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 34 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 35 | 36 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 37 | embedding_model = get_env_var('EMBEDDING_MODEL') or 'text-embedding-3-small' 38 | 39 | logfire.configure(send_to_logfire='if-token-present') 40 | 41 | @dataclass 42 | class AgentRefinerDeps: 43 | supabase: Client 44 | embedding_client: AsyncOpenAI 45 | 46 | agent_refiner_agent = Agent( 47 | model, 48 | system_prompt=agent_refiner_prompt, 49 | deps_type=AgentRefinerDeps, 50 | retries=2 51 | ) 52 | 53 | @agent_refiner_agent.tool 54 | async def retrieve_relevant_documentation(ctx: RunContext[AgentRefinerDeps], query: str) -> str: 55 | """ 56 | Retrieve relevant documentation chunks based on the query with RAG. 57 | Make sure your searches always focus on implementing the agent itself. 58 | 59 | Args: 60 | ctx: The context including the Supabase client and OpenAI client 61 | query: Your query to retrieve relevant documentation for implementing agents 62 | 63 | Returns: 64 | A formatted string containing the top 4 most relevant documentation chunks 65 | """ 66 | return await retrieve_relevant_documentation_tool(ctx.deps.supabase, ctx.deps.embedding_client, query) 67 | 68 | @agent_refiner_agent.tool 69 | async def list_documentation_pages(ctx: RunContext[AgentRefinerDeps]) -> List[str]: 70 | """ 71 | Retrieve a list of all available Pydantic AI documentation pages. 72 | This will give you all pages available, but focus on the ones related to configuring agents and their dependencies. 73 | 74 | Returns: 75 | List[str]: List of unique URLs for all documentation pages 76 | """ 77 | return await list_documentation_pages_tool(ctx.deps.supabase) 78 | 79 | @agent_refiner_agent.tool 80 | async def get_page_content(ctx: RunContext[AgentRefinerDeps], url: str) -> str: 81 | """ 82 | Retrieve the full content of a specific documentation page by combining all its chunks. 83 | Only use this tool to get pages related to setting up agents with Pydantic AI. 84 | 85 | Args: 86 | ctx: The context including the Supabase client 87 | url: The URL of the page to retrieve 88 | 89 | Returns: 90 | str: The complete page content with all chunks combined in order 91 | """ 92 | return await get_page_content_tool(ctx.deps.supabase, url) -------------------------------------------------------------------------------- /archon/refiner_agents/prompt_refiner_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | import logfire 4 | import os 5 | import sys 6 | from pydantic_ai import Agent 7 | from dotenv import load_dotenv 8 | from pydantic_ai.models.anthropic import AnthropicModel 9 | from pydantic_ai.models.openai import OpenAIModel 10 | from supabase import Client 11 | 12 | # Add the parent directory to sys.path to allow importing from the parent directory 13 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 14 | from utils.utils import get_env_var 15 | from archon.agent_prompts import prompt_refiner_prompt 16 | 17 | load_dotenv() 18 | 19 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 20 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 21 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 22 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 23 | 24 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 25 | 26 | logfire.configure(send_to_logfire='if-token-present') 27 | 28 | prompt_refiner_agent = Agent( 29 | model, 30 | system_prompt=prompt_refiner_prompt 31 | ) -------------------------------------------------------------------------------- /graph_service.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException 2 | from pydantic import BaseModel 3 | from typing import Optional, Dict, Any 4 | from archon.archon_graph import agentic_flow 5 | from langgraph.types import Command 6 | from utils.utils import write_to_log 7 | 8 | app = FastAPI() 9 | 10 | class InvokeRequest(BaseModel): 11 | message: str 12 | thread_id: str 13 | is_first_message: bool = False 14 | config: Optional[Dict[str, Any]] = None 15 | 16 | @app.get("/health") 17 | async def health_check(): 18 | """Health check endpoint""" 19 | return {"status": "ok"} 20 | 21 | @app.post("/invoke") 22 | async def invoke_agent(request: InvokeRequest): 23 | """Process a message through the agentic flow and return the complete response. 24 | 25 | The agent streams the response but this API endpoint waits for the full output 26 | before returning so it's a synchronous operation for MCP. 27 | Another endpoint will be made later to fully stream the response from the API. 28 | 29 | Args: 30 | request: The InvokeRequest containing message and thread info 31 | 32 | Returns: 33 | dict: Contains the complete response from the agent 34 | """ 35 | try: 36 | config = request.config or { 37 | "configurable": { 38 | "thread_id": request.thread_id 39 | } 40 | } 41 | 42 | response = "" 43 | if request.is_first_message: 44 | write_to_log(f"Processing first message for thread {request.thread_id}") 45 | async for msg in agentic_flow.astream( 46 | {"latest_user_message": request.message}, 47 | config, 48 | stream_mode="custom" 49 | ): 50 | response += str(msg) 51 | else: 52 | write_to_log(f"Processing continuation for thread {request.thread_id}") 53 | async for msg in agentic_flow.astream( 54 | Command(resume=request.message), 55 | config, 56 | stream_mode="custom" 57 | ): 58 | response += str(msg) 59 | 60 | write_to_log(f"Final response for thread {request.thread_id}: {response}") 61 | return {"response": response} 62 | 63 | except Exception as e: 64 | print(f"Exception invoking Archon for thread {request.thread_id}: {str(e)}") 65 | write_to_log(f"Error processing message for thread {request.thread_id}: {str(e)}") 66 | raise HTTPException(status_code=500, detail=str(e)) 67 | 68 | if __name__ == "__main__": 69 | import uvicorn 70 | uvicorn.run(app, host="0.0.0.0", port=8100) 71 | -------------------------------------------------------------------------------- /iterations/v1-single-agent/.env.example: -------------------------------------------------------------------------------- 1 | # Get your Open AI API Key by following these instructions - 2 | # https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 3 | # You only need this environment variable set if you are using GPT (and not Ollama) 4 | OPENAI_API_KEY= 5 | 6 | # For the Supabase version (sample_supabase_agent.py), set your Supabase URL and Service Key. 7 | # Get your SUPABASE_URL from the API section of your Supabase project settings - 8 | # https://supabase.com/dashboard/project//settings/api 9 | SUPABASE_URL= 10 | 11 | # Get your SUPABASE_SERVICE_KEY from the API section of your Supabase project settings - 12 | # https://supabase.com/dashboard/project//settings/api 13 | # On this page it is called the service_role secret. 14 | SUPABASE_SERVICE_KEY= 15 | 16 | # The LLM you want to use from OpenAI. See the list of models here: 17 | # https://platform.openai.com/docs/models 18 | # Example: gpt-4o-mini 19 | LLM_MODEL= -------------------------------------------------------------------------------- /iterations/v1-single-agent/README.md: -------------------------------------------------------------------------------- 1 | # Archon V1 - Basic Pydantic AI Agent to Build other Pydantic AI Agents 2 | 3 | This is the first iteration of the Archon project - no use of LangGraph and built with a single AI agent to keep things very simple and introductory. 4 | 5 | An intelligent documentation crawler and RAG (Retrieval-Augmented Generation) agent built using Pydantic AI and Supabase that is capable of building other Pydantic AI agents. The agent crawls the Pydantic AI documentation, stores content in a vector database, and provides Pydantic AI agent code by retrieving and analyzing relevant documentation chunks. 6 | 7 | ## Features 8 | 9 | - Pydantic AI documentation crawling and chunking 10 | - Vector database storage with Supabase 11 | - Semantic search using OpenAI embeddings 12 | - RAG-based question answering 13 | - Support for code block preservation 14 | - Streamlit UI for interactive querying 15 | 16 | ## Prerequisites 17 | 18 | - Python 3.11+ 19 | - Supabase account and database 20 | - OpenAI API key 21 | - Streamlit (for web interface) 22 | 23 | ## Installation 24 | 25 | 1. Clone the repository: 26 | ```bash 27 | git clone https://github.com/coleam00/archon.git 28 | cd archon/iterations/v1-single-agent 29 | ``` 30 | 31 | 2. Install dependencies (recommended to use a Python virtual environment): 32 | ```bash 33 | python -m venv venv 34 | source venv/bin/activate # On Windows: venv\Scripts\activate 35 | pip install -r requirements.txt 36 | ``` 37 | 38 | 3. Set up environment variables: 39 | - Rename `.env.example` to `.env` 40 | - Edit `.env` with your API keys and preferences: 41 | ```env 42 | OPENAI_API_KEY=your_openai_api_key 43 | SUPABASE_URL=your_supabase_url 44 | SUPABASE_SERVICE_KEY=your_supabase_service_key 45 | LLM_MODEL=gpt-4o-mini # or your preferred OpenAI model 46 | ``` 47 | 48 | ## Usage 49 | 50 | ### Database Setup 51 | 52 | Execute the SQL commands in `site_pages.sql` to: 53 | 1. Create the necessary tables 54 | 2. Enable vector similarity search 55 | 3. Set up Row Level Security policies 56 | 57 | In Supabase, do this by going to the "SQL Editor" tab and pasting in the SQL into the editor there. Then click "Run". 58 | 59 | ### Crawl Documentation 60 | 61 | To crawl and store documentation in the vector database: 62 | 63 | ```bash 64 | python crawl_pydantic_ai_docs.py 65 | ``` 66 | 67 | This will: 68 | 1. Fetch URLs from the documentation sitemap 69 | 2. Crawl each page and split into chunks 70 | 3. Generate embeddings and store in Supabase 71 | 72 | ### Streamlit Web Interface 73 | 74 | For an interactive web interface to query the documentation: 75 | 76 | ```bash 77 | streamlit run streamlit_ui.py 78 | ``` 79 | 80 | The interface will be available at `http://localhost:8501` 81 | 82 | ## Configuration 83 | 84 | ### Database Schema 85 | 86 | The Supabase database uses the following schema: 87 | ```sql 88 | CREATE TABLE site_pages ( 89 | id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 90 | url TEXT, 91 | chunk_number INTEGER, 92 | title TEXT, 93 | summary TEXT, 94 | content TEXT, 95 | metadata JSONB, 96 | embedding VECTOR(1536) 97 | ); 98 | ``` 99 | 100 | ### Chunking Configuration 101 | 102 | You can configure chunking parameters in `crawl_pydantic_ai_docs.py`: 103 | ```python 104 | chunk_size = 5000 # Characters per chunk 105 | ``` 106 | 107 | The chunker intelligently preserves: 108 | - Code blocks 109 | - Paragraph boundaries 110 | - Sentence boundaries 111 | 112 | ## Project Structure 113 | 114 | - `crawl_pydantic_ai_docs.py`: Documentation crawler and processor 115 | - `pydantic_ai_expert.py`: RAG agent implementation 116 | - `streamlit_ui.py`: Web interface 117 | - `site_pages.sql`: Database setup commands 118 | - `requirements.txt`: Project dependencies 119 | 120 | ## Contributing 121 | 122 | Contributions are welcome! Please feel free to submit a Pull Request. -------------------------------------------------------------------------------- /iterations/v1-single-agent/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v1-single-agent/requirements.txt -------------------------------------------------------------------------------- /iterations/v1-single-agent/site_pages.sql: -------------------------------------------------------------------------------- 1 | -- Enable the pgvector extension 2 | create extension if not exists vector; 3 | 4 | -- Create the documentation chunks table 5 | create table site_pages ( 6 | id bigserial primary key, 7 | url varchar not null, 8 | chunk_number integer not null, 9 | title varchar not null, 10 | summary varchar not null, 11 | content text not null, -- Added content column 12 | metadata jsonb not null default '{}'::jsonb, -- Added metadata column 13 | embedding vector(1536), -- OpenAI embeddings are 1536 dimensions 14 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 15 | 16 | -- Add a unique constraint to prevent duplicate chunks for the same URL 17 | unique(url, chunk_number) 18 | ); 19 | 20 | -- Create an index for better vector similarity search performance 21 | create index on site_pages using ivfflat (embedding vector_cosine_ops); 22 | 23 | -- Create an index on metadata for faster filtering 24 | create index idx_site_pages_metadata on site_pages using gin (metadata); 25 | 26 | -- Create a function to search for documentation chunks 27 | create function match_site_pages ( 28 | query_embedding vector(1536), 29 | match_count int default 10, 30 | filter jsonb DEFAULT '{}'::jsonb 31 | ) returns table ( 32 | id bigint, 33 | url varchar, 34 | chunk_number integer, 35 | title varchar, 36 | summary varchar, 37 | content text, 38 | metadata jsonb, 39 | similarity float 40 | ) 41 | language plpgsql 42 | as $$ 43 | #variable_conflict use_column 44 | begin 45 | return query 46 | select 47 | id, 48 | url, 49 | chunk_number, 50 | title, 51 | summary, 52 | content, 53 | metadata, 54 | 1 - (site_pages.embedding <=> query_embedding) as similarity 55 | from site_pages 56 | where metadata @> filter 57 | order by site_pages.embedding <=> query_embedding 58 | limit match_count; 59 | end; 60 | $$; 61 | 62 | -- Everything above will work for any PostgreSQL database. The below commands are for Supabase security 63 | 64 | -- Enable RLS on the table 65 | alter table site_pages enable row level security; 66 | 67 | -- Create a policy that allows anyone to read 68 | create policy "Allow public read access" 69 | on site_pages 70 | for select 71 | to public 72 | using (true); -------------------------------------------------------------------------------- /iterations/v2-agentic-workflow/.env.example: -------------------------------------------------------------------------------- 1 | # Base URL for the OpenAI instance (default is https://api.openai.com/v1) 2 | # OpenAI: https://api.openai.com/v1 3 | # Ollama (example): http://localhost:11434/v1 4 | # OpenRouter: https://openrouter.ai/api/v1 5 | BASE_URL= 6 | 7 | # Get your Open AI API Key by following these instructions - 8 | # https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 9 | # Even if using OpenRouter/Ollama, you still need to set this for the embedding model. 10 | # Future versions of Archon will be more flexible with this. 11 | OPENAI_API_KEY= 12 | 13 | # For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 14 | # For OpenRouter: https://openrouter.ai/keys 15 | LLM_API_KEY= 16 | 17 | # For the Supabase version (sample_supabase_agent.py), set your Supabase URL and Service Key. 18 | # Get your SUPABASE_URL from the API section of your Supabase project settings - 19 | # https://supabase.com/dashboard/project//settings/api 20 | SUPABASE_URL= 21 | 22 | # Get your SUPABASE_SERVICE_KEY from the API section of your Supabase project settings - 23 | # https://supabase.com/dashboard/project//settings/api 24 | # On this page it is called the service_role secret. 25 | SUPABASE_SERVICE_KEY= 26 | 27 | # The LLM you want to use for the reasoner (o3-mini, R1, QwQ, etc.). 28 | # Example: o3-mini 29 | # Example: deepseek-r1:7b-8k 30 | REASONER_MODEL= 31 | 32 | # The LLM you want to use for the primary agent/coder. 33 | # Example: qwen2.5:14b-instruct-8k 34 | PRIMARY_MODEL= 35 | 36 | # Embedding model you want to use (nomic-embed-text:latest, text-embedding-3-small) 37 | # Example: nomic-embed-text:latest 38 | EMBEDDING_MODEL= 39 | -------------------------------------------------------------------------------- /iterations/v2-agentic-workflow/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /iterations/v2-agentic-workflow/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "agent": "./archon_graph.py:agentic_flow" 5 | }, 6 | "env": ".env" 7 | } 8 | -------------------------------------------------------------------------------- /iterations/v2-agentic-workflow/ollama_site_pages.sql: -------------------------------------------------------------------------------- 1 | -- Enable the pgvector extension 2 | create extension if not exists vector; 3 | 4 | -- Create the documentation chunks table 5 | create table site_pages ( 6 | id bigserial primary key, 7 | url varchar not null, 8 | chunk_number integer not null, 9 | title varchar not null, 10 | summary varchar not null, 11 | content text not null, -- Added content column 12 | metadata jsonb not null default '{}'::jsonb, -- Added metadata column 13 | embedding vector(768), -- Ollama nomic-embed-text embeddings are 768 dimensions 14 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 15 | 16 | -- Add a unique constraint to prevent duplicate chunks for the same URL 17 | unique(url, chunk_number) 18 | ); 19 | 20 | -- Create an index for better vector similarity search performance 21 | create index on site_pages using ivfflat (embedding vector_cosine_ops); 22 | 23 | -- Create an index on metadata for faster filtering 24 | create index idx_site_pages_metadata on site_pages using gin (metadata); 25 | 26 | -- Create a function to search for documentation chunks 27 | create function match_site_pages ( 28 | query_embedding vector(768), 29 | match_count int default 10, 30 | filter jsonb DEFAULT '{}'::jsonb 31 | ) returns table ( 32 | id bigint, 33 | url varchar, 34 | chunk_number integer, 35 | title varchar, 36 | summary varchar, 37 | content text, 38 | metadata jsonb, 39 | similarity float 40 | ) 41 | language plpgsql 42 | as $$ 43 | #variable_conflict use_column 44 | begin 45 | return query 46 | select 47 | id, 48 | url, 49 | chunk_number, 50 | title, 51 | summary, 52 | content, 53 | metadata, 54 | 1 - (site_pages.embedding <=> query_embedding) as similarity 55 | from site_pages 56 | where metadata @> filter 57 | order by site_pages.embedding <=> query_embedding 58 | limit match_count; 59 | end; 60 | $$; 61 | 62 | -- Everything above will work for any PostgreSQL database. The below commands are for Supabase security 63 | 64 | -- Enable RLS on the table 65 | alter table site_pages enable row level security; 66 | 67 | -- Create a policy that allows anyone to read 68 | create policy "Allow public read access" 69 | on site_pages 70 | for select 71 | to public 72 | using (true); -------------------------------------------------------------------------------- /iterations/v2-agentic-workflow/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v2-agentic-workflow/requirements.txt -------------------------------------------------------------------------------- /iterations/v2-agentic-workflow/site_pages.sql: -------------------------------------------------------------------------------- 1 | -- Enable the pgvector extension 2 | create extension if not exists vector; 3 | 4 | -- Create the documentation chunks table 5 | create table site_pages ( 6 | id bigserial primary key, 7 | url varchar not null, 8 | chunk_number integer not null, 9 | title varchar not null, 10 | summary varchar not null, 11 | content text not null, -- Added content column 12 | metadata jsonb not null default '{}'::jsonb, -- Added metadata column 13 | embedding vector(1536), -- OpenAI embeddings are 1536 dimensions 14 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 15 | 16 | -- Add a unique constraint to prevent duplicate chunks for the same URL 17 | unique(url, chunk_number) 18 | ); 19 | 20 | -- Create an index for better vector similarity search performance 21 | create index on site_pages using ivfflat (embedding vector_cosine_ops); 22 | 23 | -- Create an index on metadata for faster filtering 24 | create index idx_site_pages_metadata on site_pages using gin (metadata); 25 | 26 | -- Create a function to search for documentation chunks 27 | create function match_site_pages ( 28 | query_embedding vector(1536), 29 | match_count int default 10, 30 | filter jsonb DEFAULT '{}'::jsonb 31 | ) returns table ( 32 | id bigint, 33 | url varchar, 34 | chunk_number integer, 35 | title varchar, 36 | summary varchar, 37 | content text, 38 | metadata jsonb, 39 | similarity float 40 | ) 41 | language plpgsql 42 | as $$ 43 | #variable_conflict use_column 44 | begin 45 | return query 46 | select 47 | id, 48 | url, 49 | chunk_number, 50 | title, 51 | summary, 52 | content, 53 | metadata, 54 | 1 - (site_pages.embedding <=> query_embedding) as similarity 55 | from site_pages 56 | where metadata @> filter 57 | order by site_pages.embedding <=> query_embedding 58 | limit match_count; 59 | end; 60 | $$; 61 | 62 | -- Everything above will work for any PostgreSQL database. The below commands are for Supabase security 63 | 64 | -- Enable RLS on the table 65 | alter table site_pages enable row level security; 66 | 67 | -- Create a policy that allows anyone to read 68 | create policy "Allow public read access" 69 | on site_pages 70 | for select 71 | to public 72 | using (true); -------------------------------------------------------------------------------- /iterations/v2-agentic-workflow/streamlit_ui.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Literal, TypedDict 3 | from langgraph.types import Command 4 | from openai import AsyncOpenAI 5 | from supabase import Client 6 | import streamlit as st 7 | import logfire 8 | import asyncio 9 | import json 10 | import uuid 11 | import os 12 | 13 | # Import all the message part classes 14 | from pydantic_ai.messages import ( 15 | ModelMessage, 16 | ModelRequest, 17 | ModelResponse, 18 | SystemPromptPart, 19 | UserPromptPart, 20 | TextPart, 21 | ToolCallPart, 22 | ToolReturnPart, 23 | RetryPromptPart, 24 | ModelMessagesTypeAdapter 25 | ) 26 | 27 | from archon_graph import agentic_flow 28 | 29 | # Load environment variables 30 | from dotenv import load_dotenv 31 | load_dotenv() 32 | 33 | openai_client=None 34 | 35 | base_url = os.getenv('BASE_URL', 'https://api.openai.com/v1') 36 | api_key = os.getenv('LLM_API_KEY', 'no-llm-api-key-provided') 37 | is_ollama = "localhost" in base_url.lower() 38 | 39 | if is_ollama: 40 | openai_client = AsyncOpenAI(base_url=base_url,api_key=api_key) 41 | else: 42 | openai_client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY")) 43 | 44 | supabase: Client = Client( 45 | os.getenv("SUPABASE_URL"), 46 | os.getenv("SUPABASE_SERVICE_KEY") 47 | ) 48 | 49 | # Configure logfire to suppress warnings (optional) 50 | logfire.configure(send_to_logfire='never') 51 | 52 | @st.cache_resource 53 | def get_thread_id(): 54 | return str(uuid.uuid4()) 55 | 56 | thread_id = get_thread_id() 57 | 58 | async def run_agent_with_streaming(user_input: str): 59 | """ 60 | Run the agent with streaming text for the user_input prompt, 61 | while maintaining the entire conversation in `st.session_state.messages`. 62 | """ 63 | config = { 64 | "configurable": { 65 | "thread_id": thread_id 66 | } 67 | } 68 | 69 | # First message from user 70 | if len(st.session_state.messages) == 1: 71 | async for msg in agentic_flow.astream( 72 | {"latest_user_message": user_input}, config, stream_mode="custom" 73 | ): 74 | yield msg 75 | # Continue the conversation 76 | else: 77 | async for msg in agentic_flow.astream( 78 | Command(resume=user_input), config, stream_mode="custom" 79 | ): 80 | yield msg 81 | 82 | 83 | async def main(): 84 | st.title("Archon - Agent Builder") 85 | st.write("Describe to me an AI agent you want to build and I'll code it for you with Pydantic AI.") 86 | st.write("Example: Build me an AI agent that can search the web with the Brave API.") 87 | 88 | # Initialize chat history in session state if not present 89 | if "messages" not in st.session_state: 90 | st.session_state.messages = [] 91 | 92 | # Display chat messages from history on app rerun 93 | for message in st.session_state.messages: 94 | message_type = message["type"] 95 | if message_type in ["human", "ai", "system"]: 96 | with st.chat_message(message_type): 97 | st.markdown(message["content"]) 98 | 99 | # Chat input for the user 100 | user_input = st.chat_input("What do you want to build today?") 101 | 102 | if user_input: 103 | # We append a new request to the conversation explicitly 104 | st.session_state.messages.append({"type": "human", "content": user_input}) 105 | 106 | # Display user prompt in the UI 107 | with st.chat_message("user"): 108 | st.markdown(user_input) 109 | 110 | # Display assistant response in chat message container 111 | response_content = "" 112 | with st.chat_message("assistant"): 113 | message_placeholder = st.empty() # Placeholder for updating the message 114 | # Run the async generator to fetch responses 115 | async for chunk in run_agent_with_streaming(user_input): 116 | response_content += chunk 117 | # Update the placeholder with the current response content 118 | message_placeholder.markdown(response_content) 119 | 120 | st.session_state.messages.append({"type": "ai", "content": response_content}) 121 | 122 | 123 | if __name__ == "__main__": 124 | asyncio.run(main()) 125 | -------------------------------------------------------------------------------- /iterations/v3-mcp-support/.env.example: -------------------------------------------------------------------------------- 1 | # Base URL for the OpenAI instance (default is https://api.openai.com/v1) 2 | # OpenAI: https://api.openai.com/v1 3 | # Ollama (example): http://localhost:11434/v1 4 | # OpenRouter: https://openrouter.ai/api/v1 5 | BASE_URL= 6 | 7 | # For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 8 | # For OpenRouter: https://openrouter.ai/keys 9 | # For Ollama, no need to set this unless you specifically configured an API key 10 | LLM_API_KEY= 11 | 12 | # Get your Open AI API Key by following these instructions - 13 | # https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 14 | # Even if using OpenRouter, you still need to set this for the embedding model. 15 | # No need to set this if using Ollama. 16 | OPENAI_API_KEY= 17 | 18 | # For the Supabase version (sample_supabase_agent.py), set your Supabase URL and Service Key. 19 | # Get your SUPABASE_URL from the API section of your Supabase project settings - 20 | # https://supabase.com/dashboard/project//settings/api 21 | SUPABASE_URL= 22 | 23 | # Get your SUPABASE_SERVICE_KEY from the API section of your Supabase project settings - 24 | # https://supabase.com/dashboard/project//settings/api 25 | # On this page it is called the service_role secret. 26 | SUPABASE_SERVICE_KEY= 27 | 28 | # The LLM you want to use for the reasoner (o3-mini, R1, QwQ, etc.). 29 | # Example: o3-mini 30 | # Example: deepseek-r1:7b-8k 31 | REASONER_MODEL= 32 | 33 | # The LLM you want to use for the primary agent/coder. 34 | # Example: gpt-4o-mini 35 | # Example: qwen2.5:14b-instruct-8k 36 | PRIMARY_MODEL= 37 | 38 | # Embedding model you want to use 39 | # Example for Ollama: nomic-embed-text 40 | # Example for OpenAI: text-embedding-3-small 41 | EMBEDDING_MODEL= -------------------------------------------------------------------------------- /iterations/v3-mcp-support/archon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v3-mcp-support/archon/__init__.py -------------------------------------------------------------------------------- /iterations/v3-mcp-support/archon/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "agent": "./archon_graph.py:agentic_flow" 5 | }, 6 | "env": "../.env" 7 | } 8 | -------------------------------------------------------------------------------- /iterations/v3-mcp-support/graph_service.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException 2 | from pydantic import BaseModel 3 | from typing import Optional, Dict, Any 4 | from archon.archon_graph import agentic_flow 5 | from langgraph.types import Command 6 | from utils.utils import write_to_log 7 | 8 | app = FastAPI() 9 | 10 | class InvokeRequest(BaseModel): 11 | message: str 12 | thread_id: str 13 | is_first_message: bool = False 14 | config: Optional[Dict[str, Any]] = None 15 | 16 | @app.get("/health") 17 | async def health_check(): 18 | """Health check endpoint""" 19 | return {"status": "ok"} 20 | 21 | @app.post("/invoke") 22 | async def invoke_agent(request: InvokeRequest): 23 | """Process a message through the agentic flow and return the complete response. 24 | 25 | The agent streams the response but this API endpoint waits for the full output 26 | before returning so it's a synchronous operation for MCP. 27 | Another endpoint will be made later to fully stream the response from the API. 28 | 29 | Args: 30 | request: The InvokeRequest containing message and thread info 31 | 32 | Returns: 33 | dict: Contains the complete response from the agent 34 | """ 35 | try: 36 | config = request.config or { 37 | "configurable": { 38 | "thread_id": request.thread_id 39 | } 40 | } 41 | 42 | response = "" 43 | if request.is_first_message: 44 | write_to_log(f"Processing first message for thread {request.thread_id}") 45 | async for msg in agentic_flow.astream( 46 | {"latest_user_message": request.message}, 47 | config, 48 | stream_mode="custom" 49 | ): 50 | response += str(msg) 51 | else: 52 | write_to_log(f"Processing continuation for thread {request.thread_id}") 53 | async for msg in agentic_flow.astream( 54 | Command(resume=request.message), 55 | config, 56 | stream_mode="custom" 57 | ): 58 | response += str(msg) 59 | 60 | write_to_log(f"Final response for thread {request.thread_id}: {response}") 61 | return {"response": response} 62 | 63 | except Exception as e: 64 | write_to_log(f"Error processing message for thread {request.thread_id}: {str(e)}") 65 | raise HTTPException(status_code=500, detail=str(e)) 66 | 67 | if __name__ == "__main__": 68 | import uvicorn 69 | uvicorn.run(app, host="127.0.0.1", port=8100) 70 | -------------------------------------------------------------------------------- /iterations/v3-mcp-support/mcp-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "archon": { 4 | "command": "[path to Archon]\\archon\\venv\\Scripts\\python.exe", 5 | "args": [ 6 | "[path to Archon]\\archon\\mcp_server.py" 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /iterations/v3-mcp-support/mcp_server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import asyncio 4 | import threading 5 | from mcp.server.fastmcp import FastMCP 6 | import requests 7 | from typing import Dict, List 8 | import uuid 9 | from utils.utils import write_to_log 10 | from graph_service import app 11 | import uvicorn 12 | 13 | 14 | # Initialize FastMCP server 15 | mcp = FastMCP("archon") 16 | 17 | 18 | # Store active threads 19 | active_threads: Dict[str, List[str]] = {} 20 | 21 | 22 | # FastAPI service URL 23 | GRAPH_SERVICE_URL = "http://127.0.0.1:8100" 24 | 25 | 26 | @mcp.tool() 27 | async def create_thread() -> str: 28 | """Create a new conversation thread for Archon. 29 | Always call this tool before invoking Archon for the first time in a conversation. 30 | (if you don't already have a thread ID) 31 | 32 | Returns: 33 | str: A unique thread ID for the conversation 34 | """ 35 | thread_id = str(uuid.uuid4()) 36 | active_threads[thread_id] = [] 37 | write_to_log(f"Created new thread: {thread_id}") 38 | return thread_id 39 | 40 | 41 | def _make_request(thread_id: str, user_input: str, config: dict) -> str: 42 | """Make synchronous request to graph service""" 43 | response = requests.post( 44 | f"{GRAPH_SERVICE_URL}/invoke", 45 | json={ 46 | "message": user_input, 47 | "thread_id": thread_id, 48 | "is_first_message": not active_threads[thread_id], 49 | "config": config 50 | } 51 | ) 52 | response.raise_for_status() 53 | return response.json() 54 | 55 | 56 | @mcp.tool() 57 | async def run_agent(thread_id: str, user_input: str) -> str: 58 | """Run the Archon agent with user input. 59 | Only use this tool after you have called create_thread in this conversation to get a unique thread ID. 60 | If you already created a thread ID in this conversation, do not create another one. Reuse the same ID. 61 | After you receive the code from Archon, always implement it into the codebase unless asked not to. 62 | 63 | Args: 64 | thread_id: The conversation thread ID 65 | user_input: The user's message to process 66 | 67 | Returns: 68 | str: The agent's response which generally includes the code for the agent 69 | """ 70 | if thread_id not in active_threads: 71 | write_to_log(f"Error: Thread not found - {thread_id}") 72 | raise ValueError("Thread not found") 73 | 74 | write_to_log(f"Processing message for thread {thread_id}: {user_input}") 75 | 76 | config = { 77 | "configurable": { 78 | "thread_id": thread_id 79 | } 80 | } 81 | 82 | try: 83 | result = await asyncio.to_thread(_make_request, thread_id, user_input, config) 84 | active_threads[thread_id].append(user_input) 85 | return result['response'] 86 | 87 | except Exception as e: 88 | raise 89 | 90 | 91 | if __name__ == "__main__": 92 | write_to_log("Starting MCP server") 93 | 94 | # Run MCP server 95 | mcp.run(transport='stdio') 96 | -------------------------------------------------------------------------------- /iterations/v3-mcp-support/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==24.1.0 2 | aiohappyeyeballs==2.4.4 3 | aiohttp==3.11.11 4 | aiosignal==1.3.2 5 | aiosqlite==0.20.0 6 | altair==5.5.0 7 | annotated-types==0.7.0 8 | anthropic==0.42.0 9 | anyio==4.8.0 10 | attrs==24.3.0 11 | beautifulsoup4==4.12.3 12 | blinker==1.9.0 13 | cachetools==5.5.0 14 | certifi==2024.12.14 15 | cffi==1.17.1 16 | charset-normalizer==3.4.1 17 | click==8.1.8 18 | cohere==5.13.12 19 | colorama==0.4.6 20 | Crawl4AI==0.4.247 21 | cryptography==43.0.3 22 | Deprecated==1.2.15 23 | deprecation==2.1.0 24 | distro==1.9.0 25 | dnspython==2.7.0 26 | email_validator==2.2.0 27 | eval_type_backport==0.2.2 28 | executing==2.1.0 29 | fake-http-header==0.3.5 30 | fastapi==0.115.8 31 | fastapi-cli==0.0.7 32 | fastavro==1.10.0 33 | filelock==3.16.1 34 | frozenlist==1.5.0 35 | fsspec==2024.12.0 36 | gitdb==4.0.12 37 | GitPython==3.1.44 38 | google-auth==2.37.0 39 | googleapis-common-protos==1.66.0 40 | gotrue==2.11.1 41 | greenlet==3.1.1 42 | griffe==1.5.4 43 | groq==0.15.0 44 | h11==0.14.0 45 | h2==4.1.0 46 | hpack==4.0.0 47 | httpcore==1.0.7 48 | httptools==0.6.4 49 | httpx==0.27.2 50 | httpx-sse==0.4.0 51 | huggingface-hub==0.27.1 52 | hyperframe==6.0.1 53 | idna==3.10 54 | importlib_metadata==8.5.0 55 | iniconfig==2.0.0 56 | itsdangerous==2.2.0 57 | Jinja2==3.1.5 58 | jiter==0.8.2 59 | joblib==1.4.2 60 | jsonpatch==1.33 61 | jsonpath-python==1.0.6 62 | jsonpointer==3.0.0 63 | jsonschema==4.23.0 64 | jsonschema-specifications==2024.10.1 65 | jsonschema_rs==0.25.1 66 | langchain-core==0.3.33 67 | langgraph==0.2.69 68 | langgraph-api==0.0.22 69 | langgraph-checkpoint==2.0.10 70 | langgraph-cli==0.1.71 71 | langgraph-sdk==0.1.51 72 | langsmith==0.3.6 73 | litellm==1.57.8 74 | logfire==3.1.0 75 | logfire-api==3.1.0 76 | lxml==5.3.0 77 | markdown-it-py==3.0.0 78 | MarkupSafe==3.0.2 79 | mcp==1.2.1 80 | mdurl==0.1.2 81 | mistralai==1.2.6 82 | mockito==1.5.3 83 | msgpack==1.1.0 84 | multidict==6.1.0 85 | mypy-extensions==1.0.0 86 | narwhals==1.21.1 87 | nltk==3.9.1 88 | numpy==2.2.1 89 | openai==1.59.6 90 | opentelemetry-api==1.29.0 91 | opentelemetry-exporter-otlp-proto-common==1.29.0 92 | opentelemetry-exporter-otlp-proto-http==1.29.0 93 | opentelemetry-instrumentation==0.50b0 94 | opentelemetry-proto==1.29.0 95 | opentelemetry-sdk==1.29.0 96 | opentelemetry-semantic-conventions==0.50b0 97 | orjson==3.10.15 98 | packaging==24.2 99 | pandas==2.2.3 100 | pillow==10.4.0 101 | playwright==1.49.1 102 | pluggy==1.5.0 103 | postgrest==0.19.1 104 | propcache==0.2.1 105 | protobuf==5.29.3 106 | psutil==6.1.1 107 | pyarrow==18.1.0 108 | pyasn1==0.6.1 109 | pyasn1_modules==0.4.1 110 | pycparser==2.22 111 | pydantic==2.10.5 112 | pydantic-ai==0.0.22 113 | pydantic-ai-slim==0.0.22 114 | pydantic-extra-types==2.10.2 115 | pydantic-graph==0.0.22 116 | pydantic-settings==2.7.1 117 | pydantic_core==2.27.2 118 | pydeck==0.9.1 119 | pyee==12.0.0 120 | Pygments==2.19.1 121 | PyJWT==2.10.1 122 | pyOpenSSL==24.3.0 123 | pytest==8.3.4 124 | pytest-mockito==0.0.4 125 | python-dateutil==2.9.0.post0 126 | python-dotenv==1.0.1 127 | python-multipart==0.0.20 128 | pytz==2024.2 129 | PyYAML==6.0.2 130 | rank-bm25==0.2.2 131 | realtime==2.1.0 132 | referencing==0.35.1 133 | regex==2024.11.6 134 | requests==2.32.3 135 | requests-toolbelt==1.0.0 136 | rich==13.9.4 137 | rich-toolkit==0.13.2 138 | rpds-py==0.22.3 139 | rsa==4.9 140 | shellingham==1.5.4 141 | six==1.17.0 142 | smmap==5.0.2 143 | sniffio==1.3.1 144 | snowballstemmer==2.2.0 145 | soupsieve==2.6 146 | sse-starlette==2.1.3 147 | starlette==0.45.3 148 | storage3==0.11.0 149 | streamlit==1.41.1 150 | StrEnum==0.4.15 151 | structlog==24.4.0 152 | supabase==2.11.0 153 | supafunc==0.9.0 154 | tenacity==9.0.0 155 | tf-playwright-stealth==1.1.0 156 | tiktoken==0.8.0 157 | tokenizers==0.21.0 158 | toml==0.10.2 159 | tornado==6.4.2 160 | tqdm==4.67.1 161 | typer==0.15.1 162 | types-requests==2.32.0.20241016 163 | typing-inspect==0.9.0 164 | typing_extensions==4.12.2 165 | tzdata==2024.2 166 | ujson==5.10.0 167 | urllib3==2.3.0 168 | uvicorn==0.34.0 169 | watchdog==6.0.0 170 | watchfiles==1.0.4 171 | websockets==13.1 172 | wrapt==1.17.1 173 | xxhash==3.5.0 174 | yarl==1.18.3 175 | zipp==3.21.0 176 | zstandard==0.23.0 177 | -------------------------------------------------------------------------------- /iterations/v3-mcp-support/setup_mcp.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import subprocess 4 | import sys 5 | 6 | def setup_venv(): 7 | # Get the absolute path to the current directory 8 | base_path = os.path.abspath(os.path.dirname(__file__)) 9 | venv_path = os.path.join(base_path, 'venv') 10 | venv_created = False 11 | 12 | # Create virtual environment if it doesn't exist 13 | if not os.path.exists(venv_path): 14 | print("Creating virtual environment...") 15 | subprocess.run([sys.executable, '-m', 'venv', venv_path], check=True) 16 | print("Virtual environment created successfully!") 17 | venv_created = True 18 | else: 19 | print("Virtual environment already exists.") 20 | 21 | # Install requirements if we just created the venv 22 | if venv_created: 23 | print("\nInstalling requirements...") 24 | # Use the venv's pip to install requirements 25 | pip_path = os.path.join(venv_path, 'Scripts', 'pip.exe') 26 | requirements_path = os.path.join(base_path, 'requirements.txt') 27 | subprocess.run([pip_path, 'install', '-r', requirements_path], check=True) 28 | print("Requirements installed successfully!") 29 | 30 | def generate_mcp_config(): 31 | # Get the absolute path to the current directory 32 | base_path = os.path.abspath(os.path.dirname(__file__)) 33 | 34 | # Construct the paths 35 | python_path = os.path.join(base_path, 'venv', 'Scripts', 'python.exe') 36 | server_script_path = os.path.join(base_path, 'mcp_server.py') 37 | 38 | # Create the config dictionary 39 | config = { 40 | "mcpServers": { 41 | "archon": { 42 | "command": python_path, 43 | "args": [server_script_path] 44 | } 45 | } 46 | } 47 | 48 | # Write the config to a file 49 | config_path = os.path.join(base_path, 'mcp-config.json') 50 | with open(config_path, 'w') as f: 51 | json.dump(config, f, indent=2) 52 | 53 | print(f"\nMCP configuration has been written to: {config_path}") 54 | print(f"\nMCP configuration for Cursor:\n\n{python_path} {server_script_path}") 55 | print("\nMCP configuration for Windsurf/Claude Desktop:") 56 | print(json.dumps(config, indent=2)) 57 | 58 | if __name__ == '__main__': 59 | setup_venv() 60 | generate_mcp_config() 61 | -------------------------------------------------------------------------------- /iterations/v3-mcp-support/streamlit_ui.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Literal, TypedDict 3 | from langgraph.types import Command 4 | from openai import AsyncOpenAI 5 | from supabase import Client 6 | import streamlit as st 7 | import logfire 8 | import asyncio 9 | import json 10 | import uuid 11 | import os 12 | import sys 13 | 14 | # Import all the message part classes 15 | from pydantic_ai.messages import ( 16 | ModelMessage, 17 | ModelRequest, 18 | ModelResponse, 19 | SystemPromptPart, 20 | UserPromptPart, 21 | TextPart, 22 | ToolCallPart, 23 | ToolReturnPart, 24 | RetryPromptPart, 25 | ModelMessagesTypeAdapter 26 | ) 27 | 28 | # Add the current directory to Python path 29 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 30 | from archon.archon_graph import agentic_flow 31 | 32 | # Load environment variables 33 | from dotenv import load_dotenv 34 | load_dotenv() 35 | 36 | 37 | openai_client=None 38 | base_url = os.getenv('BASE_URL', 'https://api.openai.com/v1') 39 | api_key = os.getenv('LLM_API_KEY', 'no-llm-api-key-provided') 40 | is_ollama = "localhost" in base_url.lower() 41 | 42 | if is_ollama: 43 | openai_client = AsyncOpenAI(base_url=base_url,api_key=api_key) 44 | else: 45 | openai_client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY")) 46 | 47 | supabase: Client = Client( 48 | os.getenv("SUPABASE_URL"), 49 | os.getenv("SUPABASE_SERVICE_KEY") 50 | ) 51 | 52 | # Configure logfire to suppress warnings (optional) 53 | logfire.configure(send_to_logfire='never') 54 | 55 | @st.cache_resource 56 | def get_thread_id(): 57 | return str(uuid.uuid4()) 58 | 59 | thread_id = get_thread_id() 60 | 61 | async def run_agent_with_streaming(user_input: str): 62 | """ 63 | Run the agent with streaming text for the user_input prompt, 64 | while maintaining the entire conversation in `st.session_state.messages`. 65 | """ 66 | config = { 67 | "configurable": { 68 | "thread_id": thread_id 69 | } 70 | } 71 | 72 | # First message from user 73 | if len(st.session_state.messages) == 1: 74 | async for msg in agentic_flow.astream( 75 | {"latest_user_message": user_input}, config, stream_mode="custom" 76 | ): 77 | yield msg 78 | # Continue the conversation 79 | else: 80 | async for msg in agentic_flow.astream( 81 | Command(resume=user_input), config, stream_mode="custom" 82 | ): 83 | yield msg 84 | 85 | 86 | async def main(): 87 | st.title("Archon - Agent Builder") 88 | st.write("Describe to me an AI agent you want to build and I'll code it for you with Pydantic AI.") 89 | st.write("Example: Build me an AI agent that can search the web with the Brave API.") 90 | 91 | # Initialize chat history in session state if not present 92 | if "messages" not in st.session_state: 93 | st.session_state.messages = [] 94 | 95 | # Display chat messages from history on app rerun 96 | for message in st.session_state.messages: 97 | message_type = message["type"] 98 | if message_type in ["human", "ai", "system"]: 99 | with st.chat_message(message_type): 100 | st.markdown(message["content"]) 101 | 102 | # Chat input for the user 103 | user_input = st.chat_input("What do you want to build today?") 104 | 105 | if user_input: 106 | # We append a new request to the conversation explicitly 107 | st.session_state.messages.append({"type": "human", "content": user_input}) 108 | 109 | # Display user prompt in the UI 110 | with st.chat_message("user"): 111 | st.markdown(user_input) 112 | 113 | # Display assistant response in chat message container 114 | response_content = "" 115 | with st.chat_message("assistant"): 116 | message_placeholder = st.empty() # Placeholder for updating the message 117 | # Run the async generator to fetch responses 118 | async for chunk in run_agent_with_streaming(user_input): 119 | response_content += chunk 120 | # Update the placeholder with the current response content 121 | message_placeholder.markdown(response_content) 122 | 123 | st.session_state.messages.append({"type": "ai", "content": response_content}) 124 | 125 | 126 | if __name__ == "__main__": 127 | asyncio.run(main()) 128 | -------------------------------------------------------------------------------- /iterations/v3-mcp-support/utils/ollama_site_pages.sql: -------------------------------------------------------------------------------- 1 | -- Enable the pgvector extension 2 | create extension if not exists vector; 3 | 4 | -- Create the documentation chunks table 5 | create table site_pages ( 6 | id bigserial primary key, 7 | url varchar not null, 8 | chunk_number integer not null, 9 | title varchar not null, 10 | summary varchar not null, 11 | content text not null, -- Added content column 12 | metadata jsonb not null default '{}'::jsonb, -- Added metadata column 13 | embedding vector(768), -- Ollama nomic-embed-text embeddings are 768 dimensions 14 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 15 | 16 | -- Add a unique constraint to prevent duplicate chunks for the same URL 17 | unique(url, chunk_number) 18 | ); 19 | 20 | -- Create an index for better vector similarity search performance 21 | create index on site_pages using ivfflat (embedding vector_cosine_ops); 22 | 23 | -- Create an index on metadata for faster filtering 24 | create index idx_site_pages_metadata on site_pages using gin (metadata); 25 | 26 | -- Create a function to search for documentation chunks 27 | create function match_site_pages ( 28 | query_embedding vector(768), 29 | match_count int default 10, 30 | filter jsonb DEFAULT '{}'::jsonb 31 | ) returns table ( 32 | id bigint, 33 | url varchar, 34 | chunk_number integer, 35 | title varchar, 36 | summary varchar, 37 | content text, 38 | metadata jsonb, 39 | similarity float 40 | ) 41 | language plpgsql 42 | as $$ 43 | #variable_conflict use_column 44 | begin 45 | return query 46 | select 47 | id, 48 | url, 49 | chunk_number, 50 | title, 51 | summary, 52 | content, 53 | metadata, 54 | 1 - (site_pages.embedding <=> query_embedding) as similarity 55 | from site_pages 56 | where metadata @> filter 57 | order by site_pages.embedding <=> query_embedding 58 | limit match_count; 59 | end; 60 | $$; 61 | 62 | -- Everything above will work for any PostgreSQL database. The below commands are for Supabase security 63 | 64 | -- Enable RLS on the table 65 | alter table site_pages enable row level security; 66 | 67 | -- Create a policy that allows anyone to read 68 | create policy "Allow public read access" 69 | on site_pages 70 | for select 71 | to public 72 | using (true); -------------------------------------------------------------------------------- /iterations/v3-mcp-support/utils/site_pages.sql: -------------------------------------------------------------------------------- 1 | -- Enable the pgvector extension 2 | create extension if not exists vector; 3 | 4 | -- Create the documentation chunks table 5 | create table site_pages ( 6 | id bigserial primary key, 7 | url varchar not null, 8 | chunk_number integer not null, 9 | title varchar not null, 10 | summary varchar not null, 11 | content text not null, -- Added content column 12 | metadata jsonb not null default '{}'::jsonb, -- Added metadata column 13 | embedding vector(1536), -- OpenAI embeddings are 1536 dimensions 14 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 15 | 16 | -- Add a unique constraint to prevent duplicate chunks for the same URL 17 | unique(url, chunk_number) 18 | ); 19 | 20 | -- Create an index for better vector similarity search performance 21 | create index on site_pages using ivfflat (embedding vector_cosine_ops); 22 | 23 | -- Create an index on metadata for faster filtering 24 | create index idx_site_pages_metadata on site_pages using gin (metadata); 25 | 26 | -- Create a function to search for documentation chunks 27 | create function match_site_pages ( 28 | query_embedding vector(1536), 29 | match_count int default 10, 30 | filter jsonb DEFAULT '{}'::jsonb 31 | ) returns table ( 32 | id bigint, 33 | url varchar, 34 | chunk_number integer, 35 | title varchar, 36 | summary varchar, 37 | content text, 38 | metadata jsonb, 39 | similarity float 40 | ) 41 | language plpgsql 42 | as $$ 43 | #variable_conflict use_column 44 | begin 45 | return query 46 | select 47 | id, 48 | url, 49 | chunk_number, 50 | title, 51 | summary, 52 | content, 53 | metadata, 54 | 1 - (site_pages.embedding <=> query_embedding) as similarity 55 | from site_pages 56 | where metadata @> filter 57 | order by site_pages.embedding <=> query_embedding 58 | limit match_count; 59 | end; 60 | $$; 61 | 62 | -- Everything above will work for any PostgreSQL database. The below commands are for Supabase security 63 | 64 | -- Enable RLS on the table 65 | alter table site_pages enable row level security; 66 | 67 | -- Create a policy that allows anyone to read 68 | create policy "Allow public read access" 69 | on site_pages 70 | for select 71 | to public 72 | using (true); -------------------------------------------------------------------------------- /iterations/v3-mcp-support/utils/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | from functools import wraps 4 | import inspect 5 | 6 | def write_to_log(message: str): 7 | """Write a message to the logs.txt file in the workbench directory. 8 | 9 | Args: 10 | message: The message to log 11 | """ 12 | # Get the directory one level up from the current file 13 | current_dir = os.path.dirname(os.path.abspath(__file__)) 14 | parent_dir = os.path.dirname(current_dir) 15 | workbench_dir = os.path.join(parent_dir, "workbench") 16 | log_path = os.path.join(workbench_dir, "logs.txt") 17 | os.makedirs(workbench_dir, exist_ok=True) 18 | 19 | timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 20 | log_entry = f"[{timestamp}] {message}\n" 21 | 22 | with open(log_path, "a", encoding="utf-8") as f: 23 | f.write(log_entry) 24 | 25 | def log_node_execution(func): 26 | """Decorator to log the start and end of graph node execution. 27 | 28 | Args: 29 | func: The async function to wrap 30 | """ 31 | @wraps(func) 32 | async def wrapper(*args, **kwargs): 33 | func_name = func.__name__ 34 | write_to_log(f"Starting node: {func_name}") 35 | try: 36 | result = await func(*args, **kwargs) 37 | write_to_log(f"Completed node: {func_name}") 38 | return result 39 | except Exception as e: 40 | write_to_log(f"Error in node {func_name}: {str(e)}") 41 | raise 42 | return wrapper 43 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore specified folders 2 | iterations/ 3 | venv/ 4 | .langgraph_api/ 5 | .github/ 6 | __pycache__/ 7 | .env 8 | 9 | # Git related 10 | .git/ 11 | .gitignore 12 | .gitattributes 13 | 14 | # Python cache 15 | *.pyc 16 | *.pyo 17 | *.pyd 18 | .Python 19 | *.so 20 | .pytest_cache/ 21 | 22 | # Environment files 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # Logs 29 | *.log 30 | 31 | # IDE specific files 32 | .idea/ 33 | .vscode/ 34 | *.swp 35 | *.swo 36 | 37 | # Keep the example env file for reference 38 | !.env.example 39 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/.env.example: -------------------------------------------------------------------------------- 1 | # Base URL for the OpenAI instance (default is https://api.openai.com/v1) 2 | # OpenAI: https://api.openai.com/v1 3 | # Ollama (example): http://localhost:11434/v1 4 | # OpenRouter: https://openrouter.ai/api/v1 5 | # Anthropic: https://api.anthropic.com/v1 6 | BASE_URL= 7 | 8 | # For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 9 | # For Anthropic: https://console.anthropic.com/account/keys 10 | # For OpenRouter: https://openrouter.ai/keys 11 | # For Ollama, no need to set this unless you specifically configured an API key 12 | LLM_API_KEY= 13 | 14 | # Get your Open AI API Key by following these instructions - 15 | # https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 16 | # Even if using Anthropic or OpenRouter, you still need to set this for the embedding model. 17 | # No need to set this if using Ollama. 18 | OPENAI_API_KEY= 19 | 20 | # For the Supabase version (sample_supabase_agent.py), set your Supabase URL and Service Key. 21 | # Get your SUPABASE_URL from the API section of your Supabase project settings - 22 | # https://supabase.com/dashboard/project//settings/api 23 | SUPABASE_URL= 24 | 25 | # Get your SUPABASE_SERVICE_KEY from the API section of your Supabase project settings - 26 | # https://supabase.com/dashboard/project//settings/api 27 | # On this page it is called the service_role secret. 28 | SUPABASE_SERVICE_KEY= 29 | 30 | # The LLM you want to use for the reasoner (o3-mini, R1, QwQ, etc.). 31 | # Example: o3-mini 32 | # Example: deepseek-r1:7b-8k 33 | REASONER_MODEL= 34 | 35 | # The LLM you want to use for the primary agent/coder. 36 | # Example: gpt-4o-mini 37 | # Example: qwen2.5:14b-instruct-8k 38 | PRIMARY_MODEL= 39 | 40 | # Embedding model you want to use 41 | # Example for Ollama: nomic-embed-text 42 | # Example for OpenAI: text-embedding-3-small 43 | EMBEDDING_MODEL= -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | workbench 3 | __pycache__ 4 | venv 5 | .langgraph_api 6 | 7 | # Files 8 | .env 9 | .env.temp 10 | .env.test 11 | env_vars.json -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/.streamlit/config.toml: -------------------------------------------------------------------------------- 1 | [client] 2 | showErrorDetails = "none" 3 | 4 | [theme] 5 | primaryColor = "#FF69B4" 6 | base="dark" -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Install system dependencies 6 | RUN apt-get update && apt-get install -y --no-install-recommends \ 7 | build-essential \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | # Copy requirements first for better caching 11 | COPY requirements.txt . 12 | RUN pip install --no-cache-dir -r requirements.txt 13 | 14 | # Copy the rest of the application 15 | COPY . . 16 | 17 | # Set environment variables 18 | ENV PYTHONUNBUFFERED=1 19 | ENV PYTHONPATH=/app 20 | 21 | # Expose port for Streamlit 22 | EXPOSE 8501 23 | 24 | # Expose port for the Archon Service (started within Streamlit) 25 | EXPOSE 8100 26 | 27 | # Set the entrypoint to run Streamlit directly 28 | CMD ["streamlit", "run", "streamlit_ui.py", "--server.port=8501", "--server.address=0.0.0.0"] 29 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/archon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v4-streamlit-ui-overhaul/archon/__init__.py -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/archon/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "agent": "./archon_graph.py:agentic_flow" 5 | }, 6 | "env": "../.env" 7 | } 8 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/graph_service.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException 2 | from pydantic import BaseModel 3 | from typing import Optional, Dict, Any 4 | from archon.archon_graph import agentic_flow 5 | from langgraph.types import Command 6 | from utils.utils import write_to_log 7 | 8 | app = FastAPI() 9 | 10 | class InvokeRequest(BaseModel): 11 | message: str 12 | thread_id: str 13 | is_first_message: bool = False 14 | config: Optional[Dict[str, Any]] = None 15 | 16 | @app.get("/health") 17 | async def health_check(): 18 | """Health check endpoint""" 19 | return {"status": "ok"} 20 | 21 | @app.post("/invoke") 22 | async def invoke_agent(request: InvokeRequest): 23 | """Process a message through the agentic flow and return the complete response. 24 | 25 | The agent streams the response but this API endpoint waits for the full output 26 | before returning so it's a synchronous operation for MCP. 27 | Another endpoint will be made later to fully stream the response from the API. 28 | 29 | Args: 30 | request: The InvokeRequest containing message and thread info 31 | 32 | Returns: 33 | dict: Contains the complete response from the agent 34 | """ 35 | try: 36 | config = request.config or { 37 | "configurable": { 38 | "thread_id": request.thread_id 39 | } 40 | } 41 | 42 | response = "" 43 | if request.is_first_message: 44 | write_to_log(f"Processing first message for thread {request.thread_id}") 45 | async for msg in agentic_flow.astream( 46 | {"latest_user_message": request.message}, 47 | config, 48 | stream_mode="custom" 49 | ): 50 | response += str(msg) 51 | else: 52 | write_to_log(f"Processing continuation for thread {request.thread_id}") 53 | async for msg in agentic_flow.astream( 54 | Command(resume=request.message), 55 | config, 56 | stream_mode="custom" 57 | ): 58 | response += str(msg) 59 | 60 | write_to_log(f"Final response for thread {request.thread_id}: {response}") 61 | return {"response": response} 62 | 63 | except Exception as e: 64 | write_to_log(f"Error processing message for thread {request.thread_id}: {str(e)}") 65 | raise HTTPException(status_code=500, detail=str(e)) 66 | 67 | if __name__ == "__main__": 68 | import uvicorn 69 | uvicorn.run(app, host="0.0.0.0", port=8100) 70 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/mcp/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore specified folders 2 | iterations/ 3 | venv/ 4 | .langgraph_api/ 5 | .github/ 6 | __pycache__/ 7 | .env 8 | 9 | # Git related 10 | .git/ 11 | .gitignore 12 | .gitattributes 13 | 14 | # Python cache 15 | *.pyc 16 | *.pyo 17 | *.pyd 18 | .Python 19 | *.so 20 | .pytest_cache/ 21 | 22 | # Environment files 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # Logs 29 | *.log 30 | 31 | # IDE specific files 32 | .idea/ 33 | .vscode/ 34 | *.swp 35 | *.swo 36 | 37 | # Keep the example env file for reference 38 | !.env.example 39 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/mcp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Copy requirements file and install dependencies 6 | COPY requirements.txt . 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | # Copy the MCP server files 10 | COPY . . 11 | 12 | # Expose port for MCP server 13 | EXPOSE 8100 14 | 15 | # Set environment variables 16 | ENV PYTHONUNBUFFERED=1 17 | 18 | # Command to run the MCP server 19 | CMD ["python", "mcp_server.py"] 20 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/mcp/mcp_server.py: -------------------------------------------------------------------------------- 1 | from mcp.server.fastmcp import FastMCP 2 | from datetime import datetime 3 | from dotenv import load_dotenv 4 | from typing import Dict, List 5 | import threading 6 | import requests 7 | import asyncio 8 | import uuid 9 | import sys 10 | import os 11 | 12 | # Load environment variables from .env file 13 | load_dotenv() 14 | 15 | # Initialize FastMCP server 16 | mcp = FastMCP("archon") 17 | 18 | # Store active threads 19 | active_threads: Dict[str, List[str]] = {} 20 | 21 | # FastAPI service URL 22 | GRAPH_SERVICE_URL = os.getenv("GRAPH_SERVICE_URL", "http://localhost:8100") 23 | 24 | def write_to_log(message: str): 25 | """Write a message to the logs.txt file in the workbench directory. 26 | 27 | Args: 28 | message: The message to log 29 | """ 30 | # Get the directory one level up from the current file 31 | current_dir = os.path.dirname(os.path.abspath(__file__)) 32 | parent_dir = os.path.dirname(current_dir) 33 | workbench_dir = os.path.join(parent_dir, "workbench") 34 | log_path = os.path.join(workbench_dir, "logs.txt") 35 | os.makedirs(workbench_dir, exist_ok=True) 36 | 37 | timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 38 | log_entry = f"[{timestamp}] {message}\n" 39 | 40 | with open(log_path, "a", encoding="utf-8") as f: 41 | f.write(log_entry) 42 | 43 | @mcp.tool() 44 | async def create_thread() -> str: 45 | """Create a new conversation thread for Archon. 46 | Always call this tool before invoking Archon for the first time in a conversation. 47 | (if you don't already have a thread ID) 48 | 49 | Returns: 50 | str: A unique thread ID for the conversation 51 | """ 52 | thread_id = str(uuid.uuid4()) 53 | active_threads[thread_id] = [] 54 | write_to_log(f"Created new thread: {thread_id}") 55 | return thread_id 56 | 57 | 58 | def _make_request(thread_id: str, user_input: str, config: dict) -> str: 59 | """Make synchronous request to graph service""" 60 | response = requests.post( 61 | f"{GRAPH_SERVICE_URL}/invoke", 62 | json={ 63 | "message": user_input, 64 | "thread_id": thread_id, 65 | "is_first_message": not active_threads[thread_id], 66 | "config": config 67 | } 68 | ) 69 | response.raise_for_status() 70 | return response.json() 71 | 72 | 73 | @mcp.tool() 74 | async def run_agent(thread_id: str, user_input: str) -> str: 75 | """Run the Archon agent with user input. 76 | Only use this tool after you have called create_thread in this conversation to get a unique thread ID. 77 | If you already created a thread ID in this conversation, do not create another one. Reuse the same ID. 78 | After you receive the code from Archon, always implement it into the codebase unless asked not to. 79 | 80 | Args: 81 | thread_id: The conversation thread ID 82 | user_input: The user's message to process 83 | 84 | Returns: 85 | str: The agent's response which generally includes the code for the agent 86 | """ 87 | if thread_id not in active_threads: 88 | write_to_log(f"Error: Thread not found - {thread_id}") 89 | raise ValueError("Thread not found") 90 | 91 | write_to_log(f"Processing message for thread {thread_id}: {user_input}") 92 | 93 | config = { 94 | "configurable": { 95 | "thread_id": thread_id 96 | } 97 | } 98 | 99 | try: 100 | result = await asyncio.to_thread(_make_request, thread_id, user_input, config) 101 | active_threads[thread_id].append(user_input) 102 | return result['response'] 103 | 104 | except Exception as e: 105 | raise 106 | 107 | 108 | if __name__ == "__main__": 109 | write_to_log("Starting MCP server") 110 | 111 | # Run MCP server 112 | mcp.run(transport='stdio') 113 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/mcp/requirements.txt: -------------------------------------------------------------------------------- 1 | mcp==1.2.1 2 | python-dotenv==1.0.1 3 | requests==2.32.3 -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/mcp_server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import asyncio 4 | import threading 5 | from mcp.server.fastmcp import FastMCP 6 | import requests 7 | from typing import Dict, List 8 | import uuid 9 | from utils.utils import write_to_log 10 | from graph_service import app 11 | import uvicorn 12 | 13 | 14 | # Initialize FastMCP server 15 | mcp = FastMCP("archon") 16 | 17 | 18 | # Store active threads 19 | active_threads: Dict[str, List[str]] = {} 20 | 21 | 22 | # FastAPI service URL 23 | GRAPH_SERVICE_URL = "http://127.0.0.1:8100" 24 | 25 | 26 | @mcp.tool() 27 | async def create_thread() -> str: 28 | """Create a new conversation thread for Archon. 29 | Always call this tool before invoking Archon for the first time in a conversation. 30 | (if you don't already have a thread ID) 31 | 32 | Returns: 33 | str: A unique thread ID for the conversation 34 | """ 35 | thread_id = str(uuid.uuid4()) 36 | active_threads[thread_id] = [] 37 | write_to_log(f"Created new thread: {thread_id}") 38 | return thread_id 39 | 40 | 41 | def _make_request(thread_id: str, user_input: str, config: dict) -> str: 42 | """Make synchronous request to graph service""" 43 | response = requests.post( 44 | f"{GRAPH_SERVICE_URL}/invoke", 45 | json={ 46 | "message": user_input, 47 | "thread_id": thread_id, 48 | "is_first_message": not active_threads[thread_id], 49 | "config": config 50 | } 51 | ) 52 | response.raise_for_status() 53 | return response.json() 54 | 55 | 56 | @mcp.tool() 57 | async def run_agent(thread_id: str, user_input: str) -> str: 58 | """Run the Archon agent with user input. 59 | Only use this tool after you have called create_thread in this conversation to get a unique thread ID. 60 | If you already created a thread ID in this conversation, do not create another one. Reuse the same ID. 61 | After you receive the code from Archon, always implement it into the codebase unless asked not to. 62 | 63 | Args: 64 | thread_id: The conversation thread ID 65 | user_input: The user's message to process 66 | 67 | Returns: 68 | str: The agent's response which generally includes the code for the agent 69 | """ 70 | if thread_id not in active_threads: 71 | write_to_log(f"Error: Thread not found - {thread_id}") 72 | raise ValueError("Thread not found") 73 | 74 | write_to_log(f"Processing message for thread {thread_id}: {user_input}") 75 | 76 | config = { 77 | "configurable": { 78 | "thread_id": thread_id 79 | } 80 | } 81 | 82 | try: 83 | result = await asyncio.to_thread(_make_request, thread_id, user_input, config) 84 | active_threads[thread_id].append(user_input) 85 | return result['response'] 86 | 87 | except Exception as e: 88 | raise 89 | 90 | 91 | if __name__ == "__main__": 92 | write_to_log("Starting MCP server") 93 | 94 | # Run MCP server 95 | mcp.run(transport='stdio') 96 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/public/Archon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v4-streamlit-ui-overhaul/public/Archon.png -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/public/ArchonLightGrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v4-streamlit-ui-overhaul/public/ArchonLightGrey.png -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==24.1.0 2 | aiohappyeyeballs==2.4.4 3 | aiohttp==3.11.11 4 | aiosignal==1.3.2 5 | aiosqlite==0.20.0 6 | altair==5.5.0 7 | annotated-types==0.7.0 8 | anthropic==0.42.0 9 | anyio==4.8.0 10 | attrs==24.3.0 11 | beautifulsoup4==4.12.3 12 | blinker==1.9.0 13 | cachetools==5.5.0 14 | certifi==2024.12.14 15 | cffi==1.17.1 16 | charset-normalizer==3.4.1 17 | click==8.1.8 18 | cohere==5.13.12 19 | colorama==0.4.6 20 | Crawl4AI==0.4.247 21 | cryptography==43.0.3 22 | Deprecated==1.2.15 23 | deprecation==2.1.0 24 | distro==1.9.0 25 | dnspython==2.7.0 26 | email_validator==2.2.0 27 | eval_type_backport==0.2.2 28 | executing==2.1.0 29 | fake-http-header==0.3.5 30 | fastapi==0.115.8 31 | fastapi-cli==0.0.7 32 | fastavro==1.10.0 33 | filelock==3.16.1 34 | frozenlist==1.5.0 35 | fsspec==2024.12.0 36 | gitdb==4.0.12 37 | GitPython==3.1.44 38 | google-auth==2.37.0 39 | googleapis-common-protos==1.66.0 40 | gotrue==2.11.1 41 | greenlet==3.1.1 42 | griffe==1.5.4 43 | groq==0.15.0 44 | h11==0.14.0 45 | h2==4.1.0 46 | hpack==4.0.0 47 | html2text==2024.2.26 48 | httpcore==1.0.7 49 | httptools==0.6.4 50 | httpx==0.27.2 51 | httpx-sse==0.4.0 52 | huggingface-hub==0.27.1 53 | hyperframe==6.0.1 54 | idna==3.10 55 | importlib_metadata==8.5.0 56 | iniconfig==2.0.0 57 | itsdangerous==2.2.0 58 | Jinja2==3.1.5 59 | jiter==0.8.2 60 | joblib==1.4.2 61 | jsonpatch==1.33 62 | jsonpath-python==1.0.6 63 | jsonpointer==3.0.0 64 | jsonschema==4.23.0 65 | jsonschema-specifications==2024.10.1 66 | jsonschema_rs==0.25.1 67 | langchain-core==0.3.33 68 | langgraph==0.2.69 69 | langgraph-checkpoint==2.0.10 70 | langgraph-cli==0.1.71 71 | langgraph-sdk==0.1.51 72 | langsmith==0.3.6 73 | litellm==1.57.8 74 | logfire==3.1.0 75 | logfire-api==3.1.0 76 | lxml==5.3.0 77 | markdown-it-py==3.0.0 78 | MarkupSafe==3.0.2 79 | mcp==1.2.1 80 | mdurl==0.1.2 81 | mistralai==1.2.6 82 | mockito==1.5.3 83 | msgpack==1.1.0 84 | multidict==6.1.0 85 | mypy-extensions==1.0.0 86 | narwhals==1.21.1 87 | nltk==3.9.1 88 | numpy==2.2.1 89 | openai==1.59.6 90 | opentelemetry-api==1.29.0 91 | opentelemetry-exporter-otlp-proto-common==1.29.0 92 | opentelemetry-exporter-otlp-proto-http==1.29.0 93 | opentelemetry-instrumentation==0.50b0 94 | opentelemetry-proto==1.29.0 95 | opentelemetry-sdk==1.29.0 96 | opentelemetry-semantic-conventions==0.50b0 97 | orjson==3.10.15 98 | packaging==24.2 99 | pandas==2.2.3 100 | pillow==10.4.0 101 | playwright==1.49.1 102 | pluggy==1.5.0 103 | postgrest==0.19.1 104 | propcache==0.2.1 105 | protobuf==5.29.3 106 | psutil==6.1.1 107 | pyarrow==18.1.0 108 | pyasn1==0.6.1 109 | pyasn1_modules==0.4.1 110 | pycparser==2.22 111 | pydantic==2.10.5 112 | pydantic-ai==0.0.22 113 | pydantic-ai-slim==0.0.22 114 | pydantic-extra-types==2.10.2 115 | pydantic-graph==0.0.22 116 | pydantic-settings==2.7.1 117 | pydantic_core==2.27.2 118 | pydeck==0.9.1 119 | pyee==12.0.0 120 | Pygments==2.19.1 121 | PyJWT==2.10.1 122 | pyOpenSSL==24.3.0 123 | pytest==8.3.4 124 | pytest-mockito==0.0.4 125 | python-dateutil==2.9.0.post0 126 | python-dotenv==1.0.1 127 | python-multipart==0.0.20 128 | pytz==2024.2 129 | PyYAML==6.0.2 130 | rank-bm25==0.2.2 131 | realtime==2.1.0 132 | referencing==0.35.1 133 | regex==2024.11.6 134 | requests==2.32.3 135 | requests-toolbelt==1.0.0 136 | rich==13.9.4 137 | rich-toolkit==0.13.2 138 | rpds-py==0.22.3 139 | rsa==4.9 140 | shellingham==1.5.4 141 | six==1.17.0 142 | smmap==5.0.2 143 | sniffio==1.3.1 144 | snowballstemmer==2.2.0 145 | soupsieve==2.6 146 | sse-starlette==2.1.3 147 | starlette==0.45.3 148 | storage3==0.11.0 149 | streamlit==1.41.1 150 | StrEnum==0.4.15 151 | structlog==24.4.0 152 | supabase==2.11.0 153 | supafunc==0.9.0 154 | tenacity==9.0.0 155 | tf-playwright-stealth==1.1.0 156 | tiktoken==0.8.0 157 | tokenizers==0.21.0 158 | toml==0.10.2 159 | tornado==6.4.2 160 | tqdm==4.67.1 161 | typer==0.15.1 162 | types-requests==2.32.0.20241016 163 | typing-inspect==0.9.0 164 | typing_extensions==4.12.2 165 | tzdata==2024.2 166 | ujson==5.10.0 167 | urllib3==2.3.0 168 | uvicorn==0.34.0 169 | watchdog==6.0.0 170 | watchfiles==1.0.4 171 | websockets==13.1 172 | wrapt==1.17.1 173 | xxhash==3.5.0 174 | yarl==1.18.3 175 | zipp==3.21.0 176 | zstandard==0.23.0 177 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/run_docker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple script to build and run Archon Docker containers. 4 | """ 5 | 6 | import os 7 | import subprocess 8 | import platform 9 | import time 10 | from pathlib import Path 11 | 12 | def run_command(command, cwd=None): 13 | """Run a command and print output in real-time.""" 14 | print(f"Running: {' '.join(command)}") 15 | process = subprocess.Popen( 16 | command, 17 | stdout=subprocess.PIPE, 18 | stderr=subprocess.STDOUT, 19 | text=False, 20 | cwd=cwd 21 | ) 22 | 23 | for line in process.stdout: 24 | try: 25 | decoded_line = line.decode('utf-8', errors='replace') 26 | print(decoded_line.strip()) 27 | except Exception as e: 28 | print(f"Error processing output: {e}") 29 | 30 | process.wait() 31 | return process.returncode 32 | 33 | def check_docker(): 34 | """Check if Docker is installed and running.""" 35 | try: 36 | subprocess.run( 37 | ["docker", "--version"], 38 | check=True, 39 | stdout=subprocess.PIPE, 40 | stderr=subprocess.PIPE 41 | ) 42 | return True 43 | except (subprocess.SubprocessError, FileNotFoundError): 44 | print("Error: Docker is not installed or not in PATH") 45 | return False 46 | 47 | def main(): 48 | """Main function to build and run Archon containers.""" 49 | # Check if Docker is available 50 | if not check_docker(): 51 | return 1 52 | 53 | # Get the base directory 54 | base_dir = Path(__file__).parent.absolute() 55 | 56 | # Check for .env file 57 | env_file = base_dir / ".env" 58 | env_args = [] 59 | if env_file.exists(): 60 | print(f"Using environment file: {env_file}") 61 | env_args = ["--env-file", str(env_file)] 62 | else: 63 | print("No .env file found. Continuing without environment variables.") 64 | 65 | # Build the MCP container 66 | print("\n=== Building Archon MCP container ===") 67 | mcp_dir = base_dir / "mcp" 68 | if run_command(["docker", "build", "-t", "archon-mcp:latest", "."], cwd=mcp_dir) != 0: 69 | print("Error building MCP container") 70 | return 1 71 | 72 | # Build the main Archon container 73 | print("\n=== Building main Archon container ===") 74 | if run_command(["docker", "build", "-t", "archon:latest", "."], cwd=base_dir) != 0: 75 | print("Error building main Archon container") 76 | return 1 77 | 78 | # Check if the container is already running 79 | try: 80 | result = subprocess.run( 81 | ["docker", "ps", "-q", "--filter", "name=archon-container"], 82 | check=True, 83 | capture_output=True, 84 | text=True 85 | ) 86 | if result.stdout.strip(): 87 | print("\n=== Stopping existing Archon container ===") 88 | run_command(["docker", "stop", "archon-container"]) 89 | run_command(["docker", "rm", "archon-container"]) 90 | except subprocess.SubprocessError: 91 | pass 92 | 93 | # Run the Archon container 94 | print("\n=== Starting Archon container ===") 95 | cmd = [ 96 | "docker", "run", "-d", 97 | "--name", "archon-container", 98 | "-p", "8501:8501", 99 | "-p", "8100:8100", 100 | "--add-host", "host.docker.internal:host-gateway" 101 | ] 102 | 103 | # Add environment variables if .env exists 104 | if env_args: 105 | cmd.extend(env_args) 106 | 107 | # Add image name 108 | cmd.append("archon:latest") 109 | 110 | if run_command(cmd) != 0: 111 | print("Error starting Archon container") 112 | return 1 113 | 114 | # Wait a moment for the container to start 115 | time.sleep(2) 116 | 117 | # Print success message 118 | print("\n=== Archon is now running! ===") 119 | print("-> Access the Streamlit UI at: http://localhost:8501") 120 | print("-> MCP container is ready to use - see the MCP tab in the UI.") 121 | print("\nTo stop Archon, run: docker stop archon-container && docker rm archon-container") 122 | 123 | return 0 124 | 125 | if __name__ == "__main__": 126 | exit(main()) 127 | -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/utils/site_pages.sql: -------------------------------------------------------------------------------- 1 | -- Enable the pgvector extension 2 | create extension if not exists vector; 3 | 4 | -- Create the documentation chunks table 5 | create table site_pages ( 6 | id bigserial primary key, 7 | url varchar not null, 8 | chunk_number integer not null, 9 | title varchar not null, 10 | summary varchar not null, 11 | content text not null, -- Added content column 12 | metadata jsonb not null default '{}'::jsonb, -- Added metadata column 13 | embedding vector(1536), -- OpenAI embeddings are 1536 dimensions 14 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 15 | 16 | -- Add a unique constraint to prevent duplicate chunks for the same URL 17 | unique(url, chunk_number) 18 | ); 19 | 20 | -- Create an index for better vector similarity search performance 21 | create index on site_pages using ivfflat (embedding vector_cosine_ops); 22 | 23 | -- Create an index on metadata for faster filtering 24 | create index idx_site_pages_metadata on site_pages using gin (metadata); 25 | 26 | -- Create a function to search for documentation chunks 27 | create function match_site_pages ( 28 | query_embedding vector(1536), 29 | match_count int default 10, 30 | filter jsonb DEFAULT '{}'::jsonb 31 | ) returns table ( 32 | id bigint, 33 | url varchar, 34 | chunk_number integer, 35 | title varchar, 36 | summary varchar, 37 | content text, 38 | metadata jsonb, 39 | similarity float 40 | ) 41 | language plpgsql 42 | as $$ 43 | #variable_conflict use_column 44 | begin 45 | return query 46 | select 47 | id, 48 | url, 49 | chunk_number, 50 | title, 51 | summary, 52 | content, 53 | metadata, 54 | 1 - (site_pages.embedding <=> query_embedding) as similarity 55 | from site_pages 56 | where metadata @> filter 57 | order by site_pages.embedding <=> query_embedding 58 | limit match_count; 59 | end; 60 | $$; 61 | 62 | -- Everything above will work for any PostgreSQL database. The below commands are for Supabase security 63 | 64 | -- Enable RLS on the table 65 | alter table site_pages enable row level security; 66 | 67 | -- Create a policy that allows anyone to read 68 | create policy "Allow public read access" 69 | on site_pages 70 | for select 71 | to public 72 | using (true); -------------------------------------------------------------------------------- /iterations/v4-streamlit-ui-overhaul/utils/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | from functools import wraps 4 | import inspect 5 | import json 6 | from typing import Optional 7 | from dotenv import load_dotenv 8 | 9 | # Load environment variables from .env file 10 | load_dotenv() 11 | 12 | def write_to_log(message: str): 13 | """Write a message to the logs.txt file in the workbench directory. 14 | 15 | Args: 16 | message: The message to log 17 | """ 18 | # Get the directory one level up from the current file 19 | current_dir = os.path.dirname(os.path.abspath(__file__)) 20 | parent_dir = os.path.dirname(current_dir) 21 | workbench_dir = os.path.join(parent_dir, "workbench") 22 | log_path = os.path.join(workbench_dir, "logs.txt") 23 | os.makedirs(workbench_dir, exist_ok=True) 24 | 25 | timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 26 | log_entry = f"[{timestamp}] {message}\n" 27 | 28 | with open(log_path, "a", encoding="utf-8") as f: 29 | f.write(log_entry) 30 | 31 | def get_env_var(var_name: str) -> Optional[str]: 32 | """Get an environment variable from the saved JSON file or from environment variables. 33 | 34 | Args: 35 | var_name: The name of the environment variable to retrieve 36 | 37 | Returns: 38 | The value of the environment variable or None if not found 39 | """ 40 | # Path to the JSON file storing environment variables 41 | current_dir = os.path.dirname(os.path.abspath(__file__)) 42 | parent_dir = os.path.dirname(current_dir) 43 | env_file_path = os.path.join(current_dir, "env_vars.json") 44 | 45 | # First try to get from JSON file 46 | if os.path.exists(env_file_path): 47 | try: 48 | with open(env_file_path, "r") as f: 49 | env_vars = json.load(f) 50 | if var_name in env_vars and env_vars[var_name]: 51 | return env_vars[var_name] 52 | except (json.JSONDecodeError, IOError) as e: 53 | write_to_log(f"Error reading env_vars.json: {str(e)}") 54 | 55 | # If not found in JSON, try to get from environment variables 56 | return os.environ.get(var_name) 57 | 58 | def save_env_var(var_name: str, value: str) -> bool: 59 | """Save an environment variable to the JSON file. 60 | 61 | Args: 62 | var_name: The name of the environment variable 63 | value: The value to save 64 | 65 | Returns: 66 | True if successful, False otherwise 67 | """ 68 | # Path to the JSON file storing environment variables 69 | current_dir = os.path.dirname(os.path.abspath(__file__)) 70 | env_file_path = os.path.join(current_dir, "env_vars.json") 71 | 72 | # Load existing env vars or create empty dict 73 | env_vars = {} 74 | if os.path.exists(env_file_path): 75 | try: 76 | with open(env_file_path, "r") as f: 77 | env_vars = json.load(f) 78 | except (json.JSONDecodeError, IOError) as e: 79 | write_to_log(f"Error reading env_vars.json: {str(e)}") 80 | # Continue with empty dict if file is corrupted 81 | 82 | # Update the variable 83 | env_vars[var_name] = value 84 | 85 | # Save back to file 86 | try: 87 | with open(env_file_path, "w") as f: 88 | json.dump(env_vars, f, indent=2) 89 | return True 90 | except IOError as e: 91 | write_to_log(f"Error writing to env_vars.json: {str(e)}") 92 | return False 93 | 94 | def log_node_execution(func): 95 | """Decorator to log the start and end of graph node execution. 96 | 97 | Args: 98 | func: The async function to wrap 99 | """ 100 | @wraps(func) 101 | async def wrapper(*args, **kwargs): 102 | func_name = func.__name__ 103 | write_to_log(f"Starting node: {func_name}") 104 | try: 105 | result = await func(*args, **kwargs) 106 | write_to_log(f"Completed node: {func_name}") 107 | return result 108 | except Exception as e: 109 | write_to_log(f"Error in node {func_name}: {str(e)}") 110 | raise 111 | return wrapper 112 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore specified folders 2 | iterations/ 3 | venv/ 4 | .langgraph_api/ 5 | .github/ 6 | __pycache__/ 7 | .env 8 | 9 | # Git related 10 | .git/ 11 | .gitignore 12 | .gitattributes 13 | 14 | # Python cache 15 | *.pyc 16 | *.pyo 17 | *.pyd 18 | .Python 19 | *.so 20 | .pytest_cache/ 21 | 22 | # Environment files 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # Logs 29 | *.log 30 | 31 | # IDE specific files 32 | .idea/ 33 | .vscode/ 34 | *.swp 35 | *.swo 36 | 37 | # Keep the example env file for reference 38 | !.env.example 39 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/.env.example: -------------------------------------------------------------------------------- 1 | # Base URL for the OpenAI instance (default is https://api.openai.com/v1) 2 | # OpenAI: https://api.openai.com/v1 3 | # Ollama (example): http://localhost:11434/v1 4 | # OpenRouter: https://openrouter.ai/api/v1 5 | # Anthropic: https://api.anthropic.com/v1 6 | BASE_URL= 7 | 8 | # For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 9 | # For Anthropic: https://console.anthropic.com/account/keys 10 | # For OpenRouter: https://openrouter.ai/keys 11 | # For Ollama, no need to set this unless you specifically configured an API key 12 | LLM_API_KEY= 13 | 14 | # Get your Open AI API Key by following these instructions - 15 | # https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 16 | # Even if using Anthropic or OpenRouter, you still need to set this for the embedding model. 17 | # No need to set this if using Ollama. 18 | OPENAI_API_KEY= 19 | 20 | # For the Supabase version (sample_supabase_agent.py), set your Supabase URL and Service Key. 21 | # Get your SUPABASE_URL from the API section of your Supabase project settings - 22 | # https://supabase.com/dashboard/project//settings/api 23 | SUPABASE_URL= 24 | 25 | # Get your SUPABASE_SERVICE_KEY from the API section of your Supabase project settings - 26 | # https://supabase.com/dashboard/project//settings/api 27 | # On this page it is called the service_role secret. 28 | SUPABASE_SERVICE_KEY= 29 | 30 | # The LLM you want to use for the reasoner (o3-mini, R1, QwQ, etc.). 31 | # Example: o3-mini 32 | # Example: deepseek-r1:7b-8k 33 | REASONER_MODEL= 34 | 35 | # The LLM you want to use for the primary agent/coder. 36 | # Example: gpt-4o-mini 37 | # Example: qwen2.5:14b-instruct-8k 38 | PRIMARY_MODEL= 39 | 40 | # Embedding model you want to use 41 | # Example for Ollama: nomic-embed-text 42 | # Example for OpenAI: text-embedding-3-small 43 | EMBEDDING_MODEL= -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Install system dependencies 6 | RUN apt-get update && apt-get install -y --no-install-recommends \ 7 | build-essential \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | # Copy requirements first for better caching 11 | COPY requirements.txt . 12 | RUN pip install --no-cache-dir -r requirements.txt 13 | 14 | # Copy the rest of the application 15 | COPY . . 16 | 17 | # Set environment variables 18 | ENV PYTHONUNBUFFERED=1 19 | ENV PYTHONPATH=/app 20 | 21 | # Expose port for Streamlit 22 | EXPOSE 8501 23 | 24 | # Expose port for the Archon Service (started within Streamlit) 25 | EXPOSE 8100 26 | 27 | # Set the entrypoint to run Streamlit directly 28 | CMD ["streamlit", "run", "streamlit_ui.py", "--server.port=8501", "--server.address=0.0.0.0"] 29 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/archon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v5-parallel-specialized-agents/archon/__init__.py -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/archon/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "agent": "./archon_graph.py:agentic_flow" 5 | }, 6 | "env": "../.env" 7 | } 8 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/archon/pydantic_ai_coder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | from dataclasses import dataclass 4 | from dotenv import load_dotenv 5 | import logfire 6 | import asyncio 7 | import httpx 8 | import os 9 | import sys 10 | import json 11 | from typing import List 12 | from pydantic import BaseModel 13 | from pydantic_ai import Agent, ModelRetry, RunContext 14 | from pydantic_ai.models.anthropic import AnthropicModel 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from openai import AsyncOpenAI 17 | from supabase import Client 18 | 19 | # Add the parent directory to sys.path to allow importing from the parent directory 20 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 21 | from utils.utils import get_env_var 22 | from archon.agent_prompts import primary_coder_prompt 23 | from archon.agent_tools import ( 24 | retrieve_relevant_documentation_tool, 25 | list_documentation_pages_tool, 26 | get_page_content_tool 27 | ) 28 | 29 | load_dotenv() 30 | 31 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 32 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 33 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 34 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 35 | 36 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 37 | 38 | logfire.configure(send_to_logfire='if-token-present') 39 | 40 | @dataclass 41 | class PydanticAIDeps: 42 | supabase: Client 43 | embedding_client: AsyncOpenAI 44 | reasoner_output: str 45 | 46 | pydantic_ai_coder = Agent( 47 | model, 48 | system_prompt=primary_coder_prompt, 49 | deps_type=PydanticAIDeps, 50 | retries=2 51 | ) 52 | 53 | @pydantic_ai_coder.system_prompt 54 | def add_reasoner_output(ctx: RunContext[str]) -> str: 55 | return f""" 56 | \n\nAdditional thoughts/instructions from the reasoner LLM. 57 | This scope includes documentation pages for you to search as well: 58 | {ctx.deps.reasoner_output} 59 | """ 60 | 61 | @pydantic_ai_coder.tool 62 | async def retrieve_relevant_documentation(ctx: RunContext[PydanticAIDeps], user_query: str) -> str: 63 | """ 64 | Retrieve relevant documentation chunks based on the query with RAG. 65 | 66 | Args: 67 | ctx: The context including the Supabase client and OpenAI client 68 | user_query: The user's question or query 69 | 70 | Returns: 71 | A formatted string containing the top 4 most relevant documentation chunks 72 | """ 73 | return await retrieve_relevant_documentation_tool(ctx.deps.supabase, ctx.deps.embedding_client, user_query) 74 | 75 | @pydantic_ai_coder.tool 76 | async def list_documentation_pages(ctx: RunContext[PydanticAIDeps]) -> List[str]: 77 | """ 78 | Retrieve a list of all available Pydantic AI documentation pages. 79 | 80 | Returns: 81 | List[str]: List of unique URLs for all documentation pages 82 | """ 83 | return await list_documentation_pages_tool(ctx.deps.supabase) 84 | 85 | @pydantic_ai_coder.tool 86 | async def get_page_content(ctx: RunContext[PydanticAIDeps], url: str) -> str: 87 | """ 88 | Retrieve the full content of a specific documentation page by combining all its chunks. 89 | 90 | Args: 91 | ctx: The context including the Supabase client 92 | url: The URL of the page to retrieve 93 | 94 | Returns: 95 | str: The complete page content with all chunks combined in order 96 | """ 97 | return await get_page_content_tool(ctx.deps.supabase, url) -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/archon/refiner_agents/agent_refiner_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | from dataclasses import dataclass 4 | from dotenv import load_dotenv 5 | import logfire 6 | import asyncio 7 | import httpx 8 | import os 9 | import sys 10 | import json 11 | from typing import List 12 | from pydantic import BaseModel 13 | from pydantic_ai import Agent, ModelRetry, RunContext 14 | from pydantic_ai.models.anthropic import AnthropicModel 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from openai import AsyncOpenAI 17 | from supabase import Client 18 | 19 | # Add the parent directory to sys.path to allow importing from the parent directory 20 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 21 | from utils.utils import get_env_var 22 | from archon.agent_prompts import agent_refiner_prompt 23 | from archon.agent_tools import ( 24 | retrieve_relevant_documentation_tool, 25 | list_documentation_pages_tool, 26 | get_page_content_tool 27 | ) 28 | 29 | load_dotenv() 30 | 31 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 32 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 33 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 34 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 35 | 36 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 37 | embedding_model = get_env_var('EMBEDDING_MODEL') or 'text-embedding-3-small' 38 | 39 | logfire.configure(send_to_logfire='if-token-present') 40 | 41 | @dataclass 42 | class AgentRefinerDeps: 43 | supabase: Client 44 | embedding_client: AsyncOpenAI 45 | 46 | agent_refiner_agent = Agent( 47 | model, 48 | system_prompt=agent_refiner_prompt, 49 | deps_type=AgentRefinerDeps, 50 | retries=2 51 | ) 52 | 53 | @agent_refiner_agent.tool 54 | async def retrieve_relevant_documentation(ctx: RunContext[AgentRefinerDeps], query: str) -> str: 55 | """ 56 | Retrieve relevant documentation chunks based on the query with RAG. 57 | Make sure your searches always focus on implementing the agent itself. 58 | 59 | Args: 60 | ctx: The context including the Supabase client and OpenAI client 61 | query: Your query to retrieve relevant documentation for implementing agents 62 | 63 | Returns: 64 | A formatted string containing the top 4 most relevant documentation chunks 65 | """ 66 | return await retrieve_relevant_documentation_tool(ctx.deps.supabase, ctx.deps.embedding_client, query) 67 | 68 | @agent_refiner_agent.tool 69 | async def list_documentation_pages(ctx: RunContext[AgentRefinerDeps]) -> List[str]: 70 | """ 71 | Retrieve a list of all available Pydantic AI documentation pages. 72 | This will give you all pages available, but focus on the ones related to configuring agents and their dependencies. 73 | 74 | Returns: 75 | List[str]: List of unique URLs for all documentation pages 76 | """ 77 | return await list_documentation_pages_tool(ctx.deps.supabase) 78 | 79 | @agent_refiner_agent.tool 80 | async def get_page_content(ctx: RunContext[AgentRefinerDeps], url: str) -> str: 81 | """ 82 | Retrieve the full content of a specific documentation page by combining all its chunks. 83 | Only use this tool to get pages related to setting up agents with Pydantic AI. 84 | 85 | Args: 86 | ctx: The context including the Supabase client 87 | url: The URL of the page to retrieve 88 | 89 | Returns: 90 | str: The complete page content with all chunks combined in order 91 | """ 92 | return await get_page_content_tool(ctx.deps.supabase, url) -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/archon/refiner_agents/prompt_refiner_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | import logfire 4 | import os 5 | import sys 6 | from pydantic_ai import Agent 7 | from dotenv import load_dotenv 8 | from pydantic_ai.models.anthropic import AnthropicModel 9 | from pydantic_ai.models.openai import OpenAIModel 10 | from supabase import Client 11 | 12 | # Add the parent directory to sys.path to allow importing from the parent directory 13 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 14 | from utils.utils import get_env_var 15 | from archon.agent_prompts import prompt_refiner_prompt 16 | 17 | load_dotenv() 18 | 19 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 20 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 21 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 22 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 23 | 24 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 25 | 26 | logfire.configure(send_to_logfire='if-token-present') 27 | 28 | prompt_refiner_agent = Agent( 29 | model, 30 | system_prompt=prompt_refiner_prompt 31 | ) -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/archon/refiner_agents/tools_refiner_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | from dataclasses import dataclass 4 | from dotenv import load_dotenv 5 | import logfire 6 | import asyncio 7 | import httpx 8 | import os 9 | import sys 10 | import json 11 | from typing import List 12 | from pydantic import BaseModel 13 | from pydantic_ai import Agent, ModelRetry, RunContext 14 | from pydantic_ai.models.anthropic import AnthropicModel 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from openai import AsyncOpenAI 17 | from supabase import Client 18 | 19 | # Add the parent directory to sys.path to allow importing from the parent directory 20 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 21 | from utils.utils import get_env_var 22 | from archon.agent_prompts import tools_refiner_prompt 23 | from archon.agent_tools import ( 24 | retrieve_relevant_documentation_tool, 25 | list_documentation_pages_tool, 26 | get_page_content_tool 27 | ) 28 | 29 | load_dotenv() 30 | 31 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 32 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 33 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 34 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 35 | 36 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 37 | embedding_model = get_env_var('EMBEDDING_MODEL') or 'text-embedding-3-small' 38 | 39 | logfire.configure(send_to_logfire='if-token-present') 40 | 41 | @dataclass 42 | class ToolsRefinerDeps: 43 | supabase: Client 44 | embedding_client: AsyncOpenAI 45 | 46 | tools_refiner_agent = Agent( 47 | model, 48 | system_prompt=tools_refiner_prompt, 49 | deps_type=ToolsRefinerDeps, 50 | retries=2 51 | ) 52 | 53 | @tools_refiner_agent.tool 54 | async def retrieve_relevant_documentation(ctx: RunContext[ToolsRefinerDeps], query: str) -> str: 55 | """ 56 | Retrieve relevant documentation chunks based on the query with RAG. 57 | Make sure your searches always focus on implementing tools. 58 | 59 | Args: 60 | ctx: The context including the Supabase client and OpenAI client 61 | query: Your query to retrieve relevant documentation for implementing tools 62 | 63 | Returns: 64 | A formatted string containing the top 4 most relevant documentation chunks 65 | """ 66 | return await retrieve_relevant_documentation_tool(ctx.deps.supabase, ctx.deps.embedding_client, query) 67 | 68 | @tools_refiner_agent.tool 69 | async def list_documentation_pages(ctx: RunContext[ToolsRefinerDeps]) -> List[str]: 70 | """ 71 | Retrieve a list of all available Pydantic AI documentation pages. 72 | This will give you all pages available, but focus on the ones related to tools. 73 | 74 | Returns: 75 | List[str]: List of unique URLs for all documentation pages 76 | """ 77 | return await list_documentation_pages_tool(ctx.deps.supabase) 78 | 79 | @tools_refiner_agent.tool 80 | async def get_page_content(ctx: RunContext[ToolsRefinerDeps], url: str) -> str: 81 | """ 82 | Retrieve the full content of a specific documentation page by combining all its chunks. 83 | Only use this tool to get pages related to using tools with Pydantic AI. 84 | 85 | Args: 86 | ctx: The context including the Supabase client 87 | url: The URL of the page to retrieve 88 | 89 | Returns: 90 | str: The complete page content with all chunks combined in order 91 | """ 92 | return await get_page_content_tool(ctx.deps.supabase, url) -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/graph_service.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException 2 | from pydantic import BaseModel 3 | from typing import Optional, Dict, Any 4 | from archon.archon_graph import agentic_flow 5 | from langgraph.types import Command 6 | from utils.utils import write_to_log 7 | 8 | app = FastAPI() 9 | 10 | class InvokeRequest(BaseModel): 11 | message: str 12 | thread_id: str 13 | is_first_message: bool = False 14 | config: Optional[Dict[str, Any]] = None 15 | 16 | @app.get("/health") 17 | async def health_check(): 18 | """Health check endpoint""" 19 | return {"status": "ok"} 20 | 21 | @app.post("/invoke") 22 | async def invoke_agent(request: InvokeRequest): 23 | """Process a message through the agentic flow and return the complete response. 24 | 25 | The agent streams the response but this API endpoint waits for the full output 26 | before returning so it's a synchronous operation for MCP. 27 | Another endpoint will be made later to fully stream the response from the API. 28 | 29 | Args: 30 | request: The InvokeRequest containing message and thread info 31 | 32 | Returns: 33 | dict: Contains the complete response from the agent 34 | """ 35 | try: 36 | config = request.config or { 37 | "configurable": { 38 | "thread_id": request.thread_id 39 | } 40 | } 41 | 42 | response = "" 43 | if request.is_first_message: 44 | write_to_log(f"Processing first message for thread {request.thread_id}") 45 | async for msg in agentic_flow.astream( 46 | {"latest_user_message": request.message}, 47 | config, 48 | stream_mode="custom" 49 | ): 50 | response += str(msg) 51 | else: 52 | write_to_log(f"Processing continuation for thread {request.thread_id}") 53 | async for msg in agentic_flow.astream( 54 | Command(resume=request.message), 55 | config, 56 | stream_mode="custom" 57 | ): 58 | response += str(msg) 59 | 60 | write_to_log(f"Final response for thread {request.thread_id}: {response}") 61 | return {"response": response} 62 | 63 | except Exception as e: 64 | print(f"Exception invoking Archon for thread {request.thread_id}: {str(e)}") 65 | write_to_log(f"Error processing message for thread {request.thread_id}: {str(e)}") 66 | raise HTTPException(status_code=500, detail=str(e)) 67 | 68 | if __name__ == "__main__": 69 | import uvicorn 70 | uvicorn.run(app, host="0.0.0.0", port=8100) 71 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/mcp/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore specified folders 2 | iterations/ 3 | venv/ 4 | .langgraph_api/ 5 | .github/ 6 | __pycache__/ 7 | .env 8 | 9 | # Git related 10 | .git/ 11 | .gitignore 12 | .gitattributes 13 | 14 | # Python cache 15 | *.pyc 16 | *.pyo 17 | *.pyd 18 | .Python 19 | *.so 20 | .pytest_cache/ 21 | 22 | # Environment files 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # Logs 29 | *.log 30 | 31 | # IDE specific files 32 | .idea/ 33 | .vscode/ 34 | *.swp 35 | *.swo 36 | 37 | # Keep the example env file for reference 38 | !.env.example 39 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/mcp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Copy requirements file and install dependencies 6 | COPY requirements.txt . 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | # Copy the MCP server files 10 | COPY . . 11 | 12 | # Expose port for MCP server 13 | EXPOSE 8100 14 | 15 | # Command to run the MCP server 16 | CMD ["python", "mcp_server.py"] 17 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/mcp/requirements.txt: -------------------------------------------------------------------------------- 1 | mcp==1.2.1 2 | python-dotenv==1.0.1 3 | requests==2.32.3 -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/public/Archon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v5-parallel-specialized-agents/public/Archon.png -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/public/ArchonGraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v5-parallel-specialized-agents/public/ArchonGraph.png -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/public/ArchonLightGrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v5-parallel-specialized-agents/public/ArchonLightGrey.png -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==24.1.0 2 | aiohappyeyeballs==2.4.4 3 | aiohttp==3.11.11 4 | aiosignal==1.3.2 5 | aiosqlite==0.20.0 6 | altair==5.5.0 7 | annotated-types==0.7.0 8 | anthropic==0.42.0 9 | anyio==4.8.0 10 | attrs==24.3.0 11 | beautifulsoup4==4.12.3 12 | blinker==1.9.0 13 | cachetools==5.5.0 14 | certifi==2024.12.14 15 | cffi==1.17.1 16 | charset-normalizer==3.4.1 17 | click==8.1.8 18 | cohere==5.13.12 19 | colorama==0.4.6 20 | Crawl4AI==0.4.247 21 | cryptography==43.0.3 22 | Deprecated==1.2.15 23 | deprecation==2.1.0 24 | distro==1.9.0 25 | dnspython==2.7.0 26 | email_validator==2.2.0 27 | eval_type_backport==0.2.2 28 | executing==2.1.0 29 | fake-http-header==0.3.5 30 | fastapi==0.115.8 31 | fastapi-cli==0.0.7 32 | fastavro==1.10.0 33 | filelock==3.16.1 34 | frozenlist==1.5.0 35 | fsspec==2024.12.0 36 | gitdb==4.0.12 37 | GitPython==3.1.44 38 | google-auth==2.37.0 39 | googleapis-common-protos==1.66.0 40 | gotrue==2.11.1 41 | greenlet==3.1.1 42 | griffe==1.5.4 43 | groq==0.15.0 44 | h11==0.14.0 45 | h2==4.1.0 46 | hpack==4.0.0 47 | html2text==2024.2.26 48 | httpcore==1.0.7 49 | httptools==0.6.4 50 | httpx==0.27.2 51 | httpx-sse==0.4.0 52 | huggingface-hub==0.27.1 53 | hyperframe==6.0.1 54 | idna==3.10 55 | importlib_metadata==8.5.0 56 | iniconfig==2.0.0 57 | itsdangerous==2.2.0 58 | Jinja2==3.1.5 59 | jiter==0.8.2 60 | joblib==1.4.2 61 | jsonpatch==1.33 62 | jsonpath-python==1.0.6 63 | jsonpointer==3.0.0 64 | jsonschema==4.23.0 65 | jsonschema-specifications==2024.10.1 66 | jsonschema_rs==0.25.1 67 | langchain-core==0.3.33 68 | langgraph==0.2.69 69 | langgraph-checkpoint==2.0.10 70 | langgraph-cli==0.1.71 71 | langgraph-sdk==0.1.51 72 | langsmith==0.3.6 73 | litellm==1.57.8 74 | logfire==3.1.0 75 | logfire-api==3.1.0 76 | lxml==5.3.0 77 | markdown-it-py==3.0.0 78 | MarkupSafe==3.0.2 79 | mcp==1.2.1 80 | mdurl==0.1.2 81 | mistralai==1.2.6 82 | mockito==1.5.3 83 | msgpack==1.1.0 84 | multidict==6.1.0 85 | mypy-extensions==1.0.0 86 | narwhals==1.21.1 87 | nltk==3.9.1 88 | numpy==2.2.1 89 | openai==1.59.6 90 | opentelemetry-api==1.29.0 91 | opentelemetry-exporter-otlp-proto-common==1.29.0 92 | opentelemetry-exporter-otlp-proto-http==1.29.0 93 | opentelemetry-instrumentation==0.50b0 94 | opentelemetry-proto==1.29.0 95 | opentelemetry-sdk==1.29.0 96 | opentelemetry-semantic-conventions==0.50b0 97 | orjson==3.10.15 98 | packaging==24.2 99 | pandas==2.2.3 100 | pillow==10.4.0 101 | playwright==1.49.1 102 | pluggy==1.5.0 103 | postgrest==0.19.1 104 | propcache==0.2.1 105 | protobuf==5.29.3 106 | psutil==6.1.1 107 | pyarrow==18.1.0 108 | pyasn1==0.6.1 109 | pyasn1_modules==0.4.1 110 | pycparser==2.22 111 | pydantic==2.10.5 112 | pydantic-ai==0.0.22 113 | pydantic-ai-slim==0.0.22 114 | pydantic-extra-types==2.10.2 115 | pydantic-graph==0.0.22 116 | pydantic-settings==2.7.1 117 | pydantic_core==2.27.2 118 | pydeck==0.9.1 119 | pyee==12.0.0 120 | Pygments==2.19.1 121 | PyJWT==2.10.1 122 | pyOpenSSL==24.3.0 123 | pytest==8.3.4 124 | pytest-mockito==0.0.4 125 | python-dateutil==2.9.0.post0 126 | python-dotenv==1.0.1 127 | python-multipart==0.0.20 128 | pytz==2024.2 129 | PyYAML==6.0.2 130 | rank-bm25==0.2.2 131 | realtime==2.1.0 132 | referencing==0.35.1 133 | regex==2024.11.6 134 | requests==2.32.3 135 | requests-toolbelt==1.0.0 136 | rich==13.9.4 137 | rich-toolkit==0.13.2 138 | rpds-py==0.22.3 139 | rsa==4.9 140 | shellingham==1.5.4 141 | six==1.17.0 142 | smmap==5.0.2 143 | sniffio==1.3.1 144 | snowballstemmer==2.2.0 145 | soupsieve==2.6 146 | sse-starlette==2.1.3 147 | starlette==0.45.3 148 | storage3==0.11.0 149 | streamlit==1.41.1 150 | StrEnum==0.4.15 151 | structlog==24.4.0 152 | supabase==2.11.0 153 | supafunc==0.9.0 154 | tenacity==9.0.0 155 | tf-playwright-stealth==1.1.0 156 | tiktoken==0.8.0 157 | tokenizers==0.21.0 158 | toml==0.10.2 159 | tornado==6.4.2 160 | tqdm==4.67.1 161 | typer==0.15.1 162 | types-requests==2.32.0.20241016 163 | typing-inspect==0.9.0 164 | typing_extensions==4.12.2 165 | tzdata==2024.2 166 | ujson==5.10.0 167 | urllib3==2.3.0 168 | uvicorn==0.34.0 169 | watchdog==6.0.0 170 | watchfiles==1.0.4 171 | websockets==13.1 172 | wrapt==1.17.1 173 | xxhash==3.5.0 174 | yarl==1.18.3 175 | zipp==3.21.0 176 | zstandard==0.23.0 177 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/streamlit_pages/__init__.py: -------------------------------------------------------------------------------- 1 | # This file makes the streamlit_ui directory a Python package 2 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/streamlit_pages/chat.py: -------------------------------------------------------------------------------- 1 | from langgraph.types import Command 2 | import streamlit as st 3 | import uuid 4 | import sys 5 | import os 6 | 7 | # Add the current directory to Python path 8 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 9 | from archon.archon_graph import agentic_flow 10 | 11 | @st.cache_resource 12 | def get_thread_id(): 13 | return str(uuid.uuid4()) 14 | 15 | thread_id = get_thread_id() 16 | 17 | async def run_agent_with_streaming(user_input: str): 18 | """ 19 | Run the agent with streaming text for the user_input prompt, 20 | while maintaining the entire conversation in `st.session_state.messages`. 21 | """ 22 | config = { 23 | "configurable": { 24 | "thread_id": thread_id 25 | } 26 | } 27 | 28 | # First message from user 29 | if len(st.session_state.messages) == 1: 30 | async for msg in agentic_flow.astream( 31 | {"latest_user_message": user_input}, config, stream_mode="custom" 32 | ): 33 | yield msg 34 | # Continue the conversation 35 | else: 36 | async for msg in agentic_flow.astream( 37 | Command(resume=user_input), config, stream_mode="custom" 38 | ): 39 | yield msg 40 | 41 | async def chat_tab(): 42 | """Display the chat interface for talking to Archon""" 43 | st.write("Describe to me an AI agent you want to build and I'll code it for you with Pydantic AI.") 44 | st.write("Example: Build me an AI agent that can search the web with the Brave API.") 45 | 46 | # Initialize chat history in session state if not present 47 | if "messages" not in st.session_state: 48 | st.session_state.messages = [] 49 | 50 | # Add a clear conversation button 51 | if st.button("Clear Conversation"): 52 | st.session_state.messages = [] 53 | st.rerun() 54 | 55 | # Display chat messages from history on app rerun 56 | for message in st.session_state.messages: 57 | message_type = message["type"] 58 | if message_type in ["human", "ai", "system"]: 59 | with st.chat_message(message_type): 60 | st.markdown(message["content"]) 61 | 62 | # Chat input for the user 63 | user_input = st.chat_input("What do you want to build today?") 64 | 65 | if user_input: 66 | # We append a new request to the conversation explicitly 67 | st.session_state.messages.append({"type": "human", "content": user_input}) 68 | 69 | # Display user prompt in the UI 70 | with st.chat_message("user"): 71 | st.markdown(user_input) 72 | 73 | # Display assistant response in chat message container 74 | response_content = "" 75 | with st.chat_message("assistant"): 76 | message_placeholder = st.empty() # Placeholder for updating the message 77 | 78 | # Add a spinner while loading 79 | with st.spinner("Archon is thinking..."): 80 | # Run the async generator to fetch responses 81 | async for chunk in run_agent_with_streaming(user_input): 82 | response_content += chunk 83 | # Update the placeholder with the current response content 84 | message_placeholder.markdown(response_content) 85 | 86 | st.session_state.messages.append({"type": "ai", "content": response_content}) -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/streamlit_pages/styles.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the CSS styles for the Streamlit UI. 3 | """ 4 | 5 | import streamlit as st 6 | 7 | def load_css(): 8 | """ 9 | Load the custom CSS styles for the Archon UI. 10 | """ 11 | st.markdown(""" 12 | 94 | """, unsafe_allow_html=True) 95 | -------------------------------------------------------------------------------- /iterations/v5-parallel-specialized-agents/utils/site_pages.sql: -------------------------------------------------------------------------------- 1 | -- Enable the pgvector extension 2 | create extension if not exists vector; 3 | 4 | -- Create the documentation chunks table 5 | create table site_pages ( 6 | id bigserial primary key, 7 | url varchar not null, 8 | chunk_number integer not null, 9 | title varchar not null, 10 | summary varchar not null, 11 | content text not null, -- Added content column 12 | metadata jsonb not null default '{}'::jsonb, -- Added metadata column 13 | embedding vector(1536), -- OpenAI embeddings are 1536 dimensions 14 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 15 | 16 | -- Add a unique constraint to prevent duplicate chunks for the same URL 17 | unique(url, chunk_number) 18 | ); 19 | 20 | -- Create an index for better vector similarity search performance 21 | create index on site_pages using ivfflat (embedding vector_cosine_ops); 22 | 23 | -- Create an index on metadata for faster filtering 24 | create index idx_site_pages_metadata on site_pages using gin (metadata); 25 | 26 | -- Create a function to search for documentation chunks 27 | create function match_site_pages ( 28 | query_embedding vector(1536), 29 | match_count int default 10, 30 | filter jsonb DEFAULT '{}'::jsonb 31 | ) returns table ( 32 | id bigint, 33 | url varchar, 34 | chunk_number integer, 35 | title varchar, 36 | summary varchar, 37 | content text, 38 | metadata jsonb, 39 | similarity float 40 | ) 41 | language plpgsql 42 | as $$ 43 | #variable_conflict use_column 44 | begin 45 | return query 46 | select 47 | id, 48 | url, 49 | chunk_number, 50 | title, 51 | summary, 52 | content, 53 | metadata, 54 | 1 - (site_pages.embedding <=> query_embedding) as similarity 55 | from site_pages 56 | where metadata @> filter 57 | order by site_pages.embedding <=> query_embedding 58 | limit match_count; 59 | end; 60 | $$; 61 | 62 | -- Everything above will work for any PostgreSQL database. The below commands are for Supabase security 63 | 64 | -- Enable RLS on the table 65 | alter table site_pages enable row level security; 66 | 67 | -- Create a policy that allows anyone to read 68 | create policy "Allow public read access" 69 | on site_pages 70 | for select 71 | to public 72 | using (true); -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore specified folders 2 | iterations/ 3 | venv/ 4 | .langgraph_api/ 5 | .github/ 6 | __pycache__/ 7 | .env 8 | 9 | # Git related 10 | .git/ 11 | .gitignore 12 | .gitattributes 13 | 14 | # Python cache 15 | *.pyc 16 | *.pyo 17 | *.pyd 18 | .Python 19 | *.so 20 | .pytest_cache/ 21 | 22 | # Environment files 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # Logs 29 | *.log 30 | 31 | # IDE specific files 32 | .idea/ 33 | .vscode/ 34 | *.swp 35 | *.swo 36 | 37 | # Keep the example env file for reference 38 | !.env.example 39 | -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/.env.example: -------------------------------------------------------------------------------- 1 | # Base URL for the OpenAI instance (default is https://api.openai.com/v1) 2 | # OpenAI: https://api.openai.com/v1 3 | # Ollama (example): http://localhost:11434/v1 4 | # OpenRouter: https://openrouter.ai/api/v1 5 | # Anthropic: https://api.anthropic.com/v1 6 | BASE_URL= 7 | 8 | # For OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 9 | # For Anthropic: https://console.anthropic.com/account/keys 10 | # For OpenRouter: https://openrouter.ai/keys 11 | # For Ollama, no need to set this unless you specifically configured an API key 12 | LLM_API_KEY= 13 | 14 | # Get your Open AI API Key by following these instructions - 15 | # https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key 16 | # Even if using Anthropic or OpenRouter, you still need to set this for the embedding model. 17 | # No need to set this if using Ollama. 18 | OPENAI_API_KEY= 19 | 20 | # For the Supabase version (sample_supabase_agent.py), set your Supabase URL and Service Key. 21 | # Get your SUPABASE_URL from the API section of your Supabase project settings - 22 | # https://supabase.com/dashboard/project//settings/api 23 | SUPABASE_URL= 24 | 25 | # Get your SUPABASE_SERVICE_KEY from the API section of your Supabase project settings - 26 | # https://supabase.com/dashboard/project//settings/api 27 | # On this page it is called the service_role secret. 28 | SUPABASE_SERVICE_KEY= 29 | 30 | # The LLM you want to use for the reasoner (o3-mini, R1, QwQ, etc.). 31 | # Example: o3-mini 32 | # Example: deepseek-r1:7b-8k 33 | REASONER_MODEL= 34 | 35 | # The LLM you want to use for the primary agent/coder. 36 | # Example: gpt-4o-mini 37 | # Example: qwen2.5:14b-instruct-8k 38 | PRIMARY_MODEL= 39 | 40 | # Embedding model you want to use 41 | # Example for Ollama: nomic-embed-text 42 | # Example for OpenAI: text-embedding-3-small 43 | EMBEDDING_MODEL= -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Install system dependencies 6 | RUN apt-get update && apt-get install -y --no-install-recommends \ 7 | build-essential \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | # Copy requirements first for better caching 11 | COPY requirements.txt . 12 | RUN pip install --no-cache-dir -r requirements.txt 13 | 14 | # Copy the rest of the application 15 | COPY . . 16 | 17 | # Set environment variables 18 | ENV PYTHONUNBUFFERED=1 19 | ENV PYTHONPATH=/app 20 | 21 | # Expose port for Streamlit 22 | EXPOSE 8501 23 | 24 | # Expose port for the Archon Service (started within Streamlit) 25 | EXPOSE 8100 26 | 27 | # Set the entrypoint to run Streamlit directly 28 | CMD ["streamlit", "run", "streamlit_ui.py", "--server.port=8501", "--server.address=0.0.0.0"] 29 | -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/examples/pydantic_mcp_agent.py: -------------------------------------------------------------------------------- 1 | from pydantic_ai.providers.openai import OpenAIProvider 2 | from pydantic_ai.models.openai import OpenAIModel 3 | from pydantic_ai.mcp import MCPServerStdio 4 | from pydantic_ai import Agent 5 | from dotenv import load_dotenv 6 | import asyncio 7 | import os 8 | 9 | load_dotenv() 10 | 11 | def get_model(): 12 | llm = os.getenv('MODEL_CHOICE', 'gpt-4o-mini') 13 | base_url = os.getenv('BASE_URL', 'https://api.openai.com/v1') 14 | api_key = os.getenv('LLM_API_KEY', 'no-api-key-provided') 15 | 16 | return OpenAIModel(llm, provider=OpenAIProvider(base_url=base_url, api_key=api_key)) 17 | 18 | server = MCPServerStdio( 19 | 'npx', 20 | ['-y', '@modelcontextprotocol/server-brave-search', 'stdio'], 21 | env={"BRAVE_API_KEY": os.getenv("BRAVE_API_KEY")} 22 | ) 23 | agent = Agent(get_model(), mcp_servers=[server]) 24 | 25 | 26 | async def main(): 27 | async with agent.run_mcp_servers(): 28 | result = await agent.run('What is new with Gemini 2.5 Pro?') 29 | print(result.data) 30 | user_input = input("Press enter to quit...") 31 | 32 | if __name__ == '__main__': 33 | asyncio.run(main()) -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/examples/pydantic_web_search_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | import asyncio 4 | import os 5 | from dataclasses import dataclass 6 | from datetime import datetime 7 | from typing import Any 8 | 9 | import logfire 10 | from devtools import debug 11 | from httpx import AsyncClient 12 | from dotenv import load_dotenv 13 | 14 | from openai import AsyncOpenAI 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from pydantic_ai import Agent, ModelRetry, RunContext 17 | 18 | load_dotenv() 19 | llm = os.getenv('LLM_MODEL', 'gpt-4o') 20 | 21 | client = AsyncOpenAI( 22 | base_url = 'http://localhost:11434/v1', 23 | api_key='ollama' 24 | ) 25 | 26 | model = OpenAIModel(llm) if llm.lower().startswith("gpt") else OpenAIModel(llm, openai_client=client) 27 | 28 | # 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured 29 | logfire.configure(send_to_logfire='if-token-present') 30 | 31 | 32 | @dataclass 33 | class Deps: 34 | client: AsyncClient 35 | brave_api_key: str | None 36 | 37 | 38 | web_search_agent = Agent( 39 | model, 40 | system_prompt=f'You are an expert at researching the web to answer user questions. The current date is: {datetime.now().strftime("%Y-%m-%d")}', 41 | deps_type=Deps, 42 | retries=2 43 | ) 44 | 45 | 46 | @web_search_agent.tool 47 | async def search_web( 48 | ctx: RunContext[Deps], web_query: str 49 | ) -> str: 50 | """Search the web given a query defined to answer the user's question. 51 | 52 | Args: 53 | ctx: The context. 54 | web_query: The query for the web search. 55 | 56 | Returns: 57 | str: The search results as a formatted string. 58 | """ 59 | if ctx.deps.brave_api_key is None: 60 | return "This is a test web search result. Please provide a Brave API key to get real search results." 61 | 62 | headers = { 63 | 'X-Subscription-Token': ctx.deps.brave_api_key, 64 | 'Accept': 'application/json', 65 | } 66 | 67 | with logfire.span('calling Brave search API', query=web_query) as span: 68 | r = await ctx.deps.client.get( 69 | 'https://api.search.brave.com/res/v1/web/search', 70 | params={ 71 | 'q': web_query, 72 | 'count': 5, 73 | 'text_decorations': True, 74 | 'search_lang': 'en' 75 | }, 76 | headers=headers 77 | ) 78 | r.raise_for_status() 79 | data = r.json() 80 | span.set_attribute('response', data) 81 | 82 | results = [] 83 | 84 | # Add web results in a nice formatted way 85 | web_results = data.get('web', {}).get('results', []) 86 | for item in web_results[:3]: 87 | title = item.get('title', '') 88 | description = item.get('description', '') 89 | url = item.get('url', '') 90 | if title and description: 91 | results.append(f"Title: {title}\nSummary: {description}\nSource: {url}\n") 92 | 93 | return "\n".join(results) if results else "No results found for the query." 94 | 95 | 96 | async def main(): 97 | async with AsyncClient() as client: 98 | brave_api_key = os.getenv('BRAVE_API_KEY', None) 99 | deps = Deps(client=client, brave_api_key=brave_api_key) 100 | 101 | result = await web_search_agent.run( 102 | 'Give me some articles talking about the new release of React 19.', deps=deps 103 | ) 104 | 105 | debug(result) 106 | print('Response:', result.data) 107 | 108 | 109 | if __name__ == '__main__': 110 | asyncio.run(main()) -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/airtable.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "airtable": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "airtable-mcp-server" 8 | ], 9 | "env": { 10 | "AIRTABLE_API_KEY": "pat123.abc123" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/brave_search.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "brave-search": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-brave-search" 8 | ], 9 | "env": { 10 | "BRAVE_API_KEY": "YOUR_API_KEY_HERE" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/chroma.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "chroma": { 4 | "command": "uvx", 5 | "args": [ 6 | "chroma-mcp", 7 | "--client-type", 8 | "persistent", 9 | "--data-dir", 10 | "/full/path/to/your/data/directory" 11 | ] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/file_system.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "filesystem": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-filesystem", 8 | "/Users/username/Desktop", 9 | "/path/to/other/allowed/dir" 10 | ] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/firecrawl.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "mcp-server-firecrawl": { 4 | "command": "npx", 5 | "args": ["-y", "firecrawl-mcp"], 6 | "env": { 7 | "FIRECRAWL_API_KEY": "YOUR_API_KEY_HERE", 8 | 9 | "FIRECRAWL_RETRY_MAX_ATTEMPTS": "5", 10 | "FIRECRAWL_RETRY_INITIAL_DELAY": "2000", 11 | "FIRECRAWL_RETRY_MAX_DELAY": "30000", 12 | "FIRECRAWL_RETRY_BACKOFF_FACTOR": "3", 13 | 14 | "FIRECRAWL_CREDIT_WARNING_THRESHOLD": "2000", 15 | "FIRECRAWL_CREDIT_CRITICAL_THRESHOLD": "500" 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "git": { 4 | "command": "docker", 5 | "args": [ 6 | "run", 7 | "--rm", 8 | "-i", 9 | "--mount", "type=bind,src=/Users/username/Desktop,dst=/projects/Desktop", 10 | "--mount", "type=bind,src=/path/to/other/allowed/dir,dst=/projects/other/allowed/dir,ro", 11 | "--mount", "type=bind,src=/path/to/file.txt,dst=/projects/path/to/file.txt", 12 | "mcp/git" 13 | ] 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/github.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "github": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-github" 8 | ], 9 | "env": { 10 | "GITHUB_PERSONAL_ACCESS_TOKEN": "" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/google_drive.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "gdrive": { 4 | "command": "docker", 5 | "args": ["run", "-i", "--rm", "-v", "mcp-gdrive:/gdrive-server", "-e", "GDRIVE_CREDENTIALS_PATH=/gdrive-server/credentials.json", "mcp/gdrive"] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/qdrant.json: -------------------------------------------------------------------------------- 1 | { 2 | "qdrant": { 3 | "command": "uvx", 4 | "args": ["mcp-server-qdrant"], 5 | "env": { 6 | "QDRANT_URL": "https://xyz-example.eu-central.aws.cloud.qdrant.io:6333", 7 | "QDRANT_API_KEY": "your_api_key", 8 | "COLLECTION_NAME": "your-collection-name", 9 | "EMBEDDING_MODEL": "sentence-transformers/all-MiniLM-L6-v2" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/redis.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "redis": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-redis", 8 | "redis://localhost:6379" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/slack.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "slack": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-slack" 8 | ], 9 | "env": { 10 | "SLACK_BOT_TOKEN": "xoxb-your-bot-token", 11 | "SLACK_TEAM_ID": "T01234567" 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/mcps/sqlite.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "sqlite": { 4 | "command": "docker", 5 | "args": [ 6 | "run", 7 | "--rm", 8 | "-i", 9 | "-v", 10 | "mcp-test:/mcp", 11 | "mcp/sqlite", 12 | "--db-path", 13 | "/mcp/test.db" 14 | ] 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/tools/get_github_file.py: -------------------------------------------------------------------------------- 1 | @github_agent.tool 2 | async def get_file_content(ctx: RunContext[GitHubDeps], github_url: str, file_path: str) -> str: 3 | """Get the content of a specific file from the GitHub repository. 4 | 5 | Args: 6 | ctx: The context. 7 | github_url: The GitHub repository URL. 8 | file_path: Path to the file within the repository. 9 | 10 | Returns: 11 | str: File content as a string. 12 | """ 13 | match = re.search(r'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$', github_url) 14 | if not match: 15 | return "Invalid GitHub URL format" 16 | 17 | owner, repo = match.groups() 18 | headers = {'Authorization': f'token {ctx.deps.github_token}'} if ctx.deps.github_token else {} 19 | 20 | response = await ctx.deps.client.get( 21 | f'https://raw.githubusercontent.com/{owner}/{repo}/main/{file_path}', 22 | headers=headers 23 | ) 24 | 25 | if response.status_code != 200: 26 | # Try with master branch if main fails 27 | response = await ctx.deps.client.get( 28 | f'https://raw.githubusercontent.com/{owner}/{repo}/master/{file_path}', 29 | headers=headers 30 | ) 31 | if response.status_code != 200: 32 | return f"Failed to get file content: {response.text}" 33 | 34 | return response.text -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/tools/get_github_file_structure.py: -------------------------------------------------------------------------------- 1 | @github_agent.tool 2 | async def get_repo_structure(ctx: RunContext[GitHubDeps], github_url: str) -> str: 3 | """Get the directory structure of a GitHub repository. 4 | 5 | Args: 6 | ctx: The context. 7 | github_url: The GitHub repository URL. 8 | 9 | Returns: 10 | str: Directory structure as a formatted string. 11 | """ 12 | match = re.search(r'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$', github_url) 13 | if not match: 14 | return "Invalid GitHub URL format" 15 | 16 | owner, repo = match.groups() 17 | headers = {'Authorization': f'token {ctx.deps.github_token}'} if ctx.deps.github_token else {} 18 | 19 | response = await ctx.deps.client.get( 20 | f'https://api.github.com/repos/{owner}/{repo}/git/trees/main?recursive=1', 21 | headers=headers 22 | ) 23 | 24 | if response.status_code != 200: 25 | # Try with master branch if main fails 26 | response = await ctx.deps.client.get( 27 | f'https://api.github.com/repos/{owner}/{repo}/git/trees/master?recursive=1', 28 | headers=headers 29 | ) 30 | if response.status_code != 200: 31 | return f"Failed to get repository structure: {response.text}" 32 | 33 | data = response.json() 34 | tree = data['tree'] 35 | 36 | # Build directory structure 37 | structure = [] 38 | for item in tree: 39 | if not any(excluded in item['path'] for excluded in ['.git/', 'node_modules/', '__pycache__/']): 40 | structure.append(f"{'📁 ' if item['type'] == 'tree' else '📄 '}{item['path']}") 41 | 42 | return "\n".join(structure) -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/tools/get_github_repo_info.py: -------------------------------------------------------------------------------- 1 | @github_agent.tool 2 | async def get_repo_info(ctx: RunContext[GitHubDeps], github_url: str) -> str: 3 | """Get repository information including size and description using GitHub API. 4 | 5 | Args: 6 | ctx: The context. 7 | github_url: The GitHub repository URL. 8 | 9 | Returns: 10 | str: Repository information as a formatted string. 11 | """ 12 | match = re.search(r'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$', github_url) 13 | if not match: 14 | return "Invalid GitHub URL format" 15 | 16 | owner, repo = match.groups() 17 | headers = {'Authorization': f'token {ctx.deps.github_token}'} if ctx.deps.github_token else {} 18 | 19 | response = await ctx.deps.client.get( 20 | f'https://api.github.com/repos/{owner}/{repo}', 21 | headers=headers 22 | ) 23 | 24 | if response.status_code != 200: 25 | return f"Failed to get repository info: {response.text}" 26 | 27 | data = response.json() 28 | size_mb = data['size'] / 1024 29 | 30 | return ( 31 | f"Repository: {data['full_name']}\n" 32 | f"Description: {data['description']}\n" 33 | f"Size: {size_mb:.1f}MB\n" 34 | f"Stars: {data['stargazers_count']}\n" 35 | f"Language: {data['language']}\n" 36 | f"Created: {data['created_at']}\n" 37 | f"Last Updated: {data['updated_at']}" 38 | ) -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/agent-resources/tools/web_search.py: -------------------------------------------------------------------------------- 1 | @web_search_agent.tool 2 | async def search_web( 3 | ctx: RunContext[Deps], web_query: str 4 | ) -> str: 5 | """Search the web given a query defined to answer the user's question. 6 | 7 | Args: 8 | ctx: The context. 9 | web_query: The query for the web search. 10 | 11 | Returns: 12 | str: The search results as a formatted string. 13 | """ 14 | if ctx.deps.brave_api_key is None: 15 | return "This is a test web search result. Please provide a Brave API key to get real search results." 16 | 17 | headers = { 18 | 'X-Subscription-Token': ctx.deps.brave_api_key, 19 | 'Accept': 'application/json', 20 | } 21 | 22 | with logfire.span('calling Brave search API', query=web_query) as span: 23 | r = await ctx.deps.client.get( 24 | 'https://api.search.brave.com/res/v1/web/search', 25 | params={ 26 | 'q': web_query, 27 | 'count': 5, 28 | 'text_decorations': True, 29 | 'search_lang': 'en' 30 | }, 31 | headers=headers 32 | ) 33 | r.raise_for_status() 34 | data = r.json() 35 | span.set_attribute('response', data) 36 | 37 | results = [] 38 | 39 | # Add web results in a nice formatted way 40 | web_results = data.get('web', {}).get('results', []) 41 | for item in web_results[:3]: 42 | title = item.get('title', '') 43 | description = item.get('description', '') 44 | url = item.get('url', '') 45 | if title and description: 46 | results.append(f"Title: {title}\nSummary: {description}\nSource: {url}\n") 47 | 48 | return "\n".join(results) if results else "No results found for the query." -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/archon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v6-tool-library-integration/archon/__init__.py -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/archon/advisor_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | from dataclasses import dataclass 4 | from dotenv import load_dotenv 5 | import logfire 6 | import asyncio 7 | import httpx 8 | import os 9 | import sys 10 | import json 11 | from typing import List 12 | from pydantic import BaseModel 13 | from pydantic_ai import Agent, ModelRetry, RunContext 14 | from pydantic_ai.models.anthropic import AnthropicModel 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from openai import AsyncOpenAI 17 | from supabase import Client 18 | 19 | # Add the parent directory to sys.path to allow importing from the parent directory 20 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 21 | from utils.utils import get_env_var 22 | from archon.agent_prompts import advisor_prompt 23 | from archon.agent_tools import get_file_content_tool 24 | 25 | load_dotenv() 26 | 27 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 28 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 29 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 30 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 31 | 32 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 33 | 34 | logfire.configure(send_to_logfire='if-token-present') 35 | 36 | @dataclass 37 | class AdvisorDeps: 38 | file_list: List[str] 39 | 40 | advisor_agent = Agent( 41 | model, 42 | system_prompt=advisor_prompt, 43 | deps_type=AdvisorDeps, 44 | retries=2 45 | ) 46 | 47 | @advisor_agent.system_prompt 48 | def add_file_list(ctx: RunContext[str]) -> str: 49 | joined_files = "\n".join(ctx.deps.file_list) 50 | return f""" 51 | 52 | Here is the list of all the files that you can pull the contents of with the 53 | 'get_file_content' tool if the example/tool/MCP server is relevant to the 54 | agent the user is trying to build: 55 | 56 | {joined_files} 57 | """ 58 | 59 | @advisor_agent.tool_plain 60 | def get_file_content(file_path: str) -> str: 61 | """ 62 | Retrieves the content of a specific file. Use this to get the contents of an example, tool, config for an MCP server 63 | 64 | Args: 65 | file_path: The path to the file 66 | 67 | Returns: 68 | The raw contents of the file 69 | """ 70 | return get_file_content_tool(file_path) -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/archon/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "agent": "./archon_graph.py:agentic_flow" 5 | }, 6 | "env": "../.env" 7 | } 8 | -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/archon/pydantic_ai_coder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | from dataclasses import dataclass 4 | from dotenv import load_dotenv 5 | import logfire 6 | import asyncio 7 | import httpx 8 | import os 9 | import sys 10 | import json 11 | from typing import List 12 | from pydantic import BaseModel 13 | from pydantic_ai import Agent, ModelRetry, RunContext 14 | from pydantic_ai.models.anthropic import AnthropicModel 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from openai import AsyncOpenAI 17 | from supabase import Client 18 | 19 | # Add the parent directory to sys.path to allow importing from the parent directory 20 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 21 | from utils.utils import get_env_var 22 | from archon.agent_prompts import primary_coder_prompt 23 | from archon.agent_tools import ( 24 | retrieve_relevant_documentation_tool, 25 | list_documentation_pages_tool, 26 | get_page_content_tool 27 | ) 28 | 29 | load_dotenv() 30 | 31 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 32 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 33 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 34 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 35 | 36 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 37 | 38 | logfire.configure(send_to_logfire='if-token-present') 39 | 40 | @dataclass 41 | class PydanticAIDeps: 42 | supabase: Client 43 | embedding_client: AsyncOpenAI 44 | reasoner_output: str 45 | advisor_output: str 46 | 47 | pydantic_ai_coder = Agent( 48 | model, 49 | system_prompt=primary_coder_prompt, 50 | deps_type=PydanticAIDeps, 51 | retries=2 52 | ) 53 | 54 | @pydantic_ai_coder.system_prompt 55 | def add_reasoner_output(ctx: RunContext[str]) -> str: 56 | return f""" 57 | 58 | Additional thoughts/instructions from the reasoner LLM. 59 | This scope includes documentation pages for you to search as well: 60 | {ctx.deps.reasoner_output} 61 | 62 | Recommended starting point from the advisor agent: 63 | {ctx.deps.advisor_output} 64 | """ 65 | 66 | @pydantic_ai_coder.tool 67 | async def retrieve_relevant_documentation(ctx: RunContext[PydanticAIDeps], user_query: str) -> str: 68 | """ 69 | Retrieve relevant documentation chunks based on the query with RAG. 70 | 71 | Args: 72 | ctx: The context including the Supabase client and OpenAI client 73 | user_query: The user's question or query 74 | 75 | Returns: 76 | A formatted string containing the top 4 most relevant documentation chunks 77 | """ 78 | return await retrieve_relevant_documentation_tool(ctx.deps.supabase, ctx.deps.embedding_client, user_query) 79 | 80 | @pydantic_ai_coder.tool 81 | async def list_documentation_pages(ctx: RunContext[PydanticAIDeps]) -> List[str]: 82 | """ 83 | Retrieve a list of all available Pydantic AI documentation pages. 84 | 85 | Returns: 86 | List[str]: List of unique URLs for all documentation pages 87 | """ 88 | return await list_documentation_pages_tool(ctx.deps.supabase) 89 | 90 | @pydantic_ai_coder.tool 91 | async def get_page_content(ctx: RunContext[PydanticAIDeps], url: str) -> str: 92 | """ 93 | Retrieve the full content of a specific documentation page by combining all its chunks. 94 | 95 | Args: 96 | ctx: The context including the Supabase client 97 | url: The URL of the page to retrieve 98 | 99 | Returns: 100 | str: The complete page content with all chunks combined in order 101 | """ 102 | return await get_page_content_tool(ctx.deps.supabase, url) -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/archon/refiner_agents/agent_refiner_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | from dataclasses import dataclass 4 | from dotenv import load_dotenv 5 | import logfire 6 | import asyncio 7 | import httpx 8 | import os 9 | import sys 10 | import json 11 | from typing import List 12 | from pydantic import BaseModel 13 | from pydantic_ai import Agent, ModelRetry, RunContext 14 | from pydantic_ai.models.anthropic import AnthropicModel 15 | from pydantic_ai.models.openai import OpenAIModel 16 | from openai import AsyncOpenAI 17 | from supabase import Client 18 | 19 | # Add the parent directory to sys.path to allow importing from the parent directory 20 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 21 | from utils.utils import get_env_var 22 | from archon.agent_prompts import agent_refiner_prompt 23 | from archon.agent_tools import ( 24 | retrieve_relevant_documentation_tool, 25 | list_documentation_pages_tool, 26 | get_page_content_tool 27 | ) 28 | 29 | load_dotenv() 30 | 31 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 32 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 33 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 34 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 35 | 36 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 37 | embedding_model = get_env_var('EMBEDDING_MODEL') or 'text-embedding-3-small' 38 | 39 | logfire.configure(send_to_logfire='if-token-present') 40 | 41 | @dataclass 42 | class AgentRefinerDeps: 43 | supabase: Client 44 | embedding_client: AsyncOpenAI 45 | 46 | agent_refiner_agent = Agent( 47 | model, 48 | system_prompt=agent_refiner_prompt, 49 | deps_type=AgentRefinerDeps, 50 | retries=2 51 | ) 52 | 53 | @agent_refiner_agent.tool 54 | async def retrieve_relevant_documentation(ctx: RunContext[AgentRefinerDeps], query: str) -> str: 55 | """ 56 | Retrieve relevant documentation chunks based on the query with RAG. 57 | Make sure your searches always focus on implementing the agent itself. 58 | 59 | Args: 60 | ctx: The context including the Supabase client and OpenAI client 61 | query: Your query to retrieve relevant documentation for implementing agents 62 | 63 | Returns: 64 | A formatted string containing the top 4 most relevant documentation chunks 65 | """ 66 | return await retrieve_relevant_documentation_tool(ctx.deps.supabase, ctx.deps.embedding_client, query) 67 | 68 | @agent_refiner_agent.tool 69 | async def list_documentation_pages(ctx: RunContext[AgentRefinerDeps]) -> List[str]: 70 | """ 71 | Retrieve a list of all available Pydantic AI documentation pages. 72 | This will give you all pages available, but focus on the ones related to configuring agents and their dependencies. 73 | 74 | Returns: 75 | List[str]: List of unique URLs for all documentation pages 76 | """ 77 | return await list_documentation_pages_tool(ctx.deps.supabase) 78 | 79 | @agent_refiner_agent.tool 80 | async def get_page_content(ctx: RunContext[AgentRefinerDeps], url: str) -> str: 81 | """ 82 | Retrieve the full content of a specific documentation page by combining all its chunks. 83 | Only use this tool to get pages related to setting up agents with Pydantic AI. 84 | 85 | Args: 86 | ctx: The context including the Supabase client 87 | url: The URL of the page to retrieve 88 | 89 | Returns: 90 | str: The complete page content with all chunks combined in order 91 | """ 92 | return await get_page_content_tool(ctx.deps.supabase, url) -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/archon/refiner_agents/prompt_refiner_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _annotations 2 | 3 | import logfire 4 | import os 5 | import sys 6 | from pydantic_ai import Agent 7 | from dotenv import load_dotenv 8 | from pydantic_ai.models.anthropic import AnthropicModel 9 | from pydantic_ai.models.openai import OpenAIModel 10 | from supabase import Client 11 | 12 | # Add the parent directory to sys.path to allow importing from the parent directory 13 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 14 | from utils.utils import get_env_var 15 | from archon.agent_prompts import prompt_refiner_prompt 16 | 17 | load_dotenv() 18 | 19 | provider = get_env_var('LLM_PROVIDER') or 'OpenAI' 20 | llm = get_env_var('PRIMARY_MODEL') or 'gpt-4o-mini' 21 | base_url = get_env_var('BASE_URL') or 'https://api.openai.com/v1' 22 | api_key = get_env_var('LLM_API_KEY') or 'no-llm-api-key-provided' 23 | 24 | model = AnthropicModel(llm, api_key=api_key) if provider == "Anthropic" else OpenAIModel(llm, base_url=base_url, api_key=api_key) 25 | 26 | logfire.configure(send_to_logfire='if-token-present') 27 | 28 | prompt_refiner_agent = Agent( 29 | model, 30 | system_prompt=prompt_refiner_prompt 31 | ) -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/graph_service.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException 2 | from pydantic import BaseModel 3 | from typing import Optional, Dict, Any 4 | from archon.archon_graph import agentic_flow 5 | from langgraph.types import Command 6 | from utils.utils import write_to_log 7 | 8 | app = FastAPI() 9 | 10 | class InvokeRequest(BaseModel): 11 | message: str 12 | thread_id: str 13 | is_first_message: bool = False 14 | config: Optional[Dict[str, Any]] = None 15 | 16 | @app.get("/health") 17 | async def health_check(): 18 | """Health check endpoint""" 19 | return {"status": "ok"} 20 | 21 | @app.post("/invoke") 22 | async def invoke_agent(request: InvokeRequest): 23 | """Process a message through the agentic flow and return the complete response. 24 | 25 | The agent streams the response but this API endpoint waits for the full output 26 | before returning so it's a synchronous operation for MCP. 27 | Another endpoint will be made later to fully stream the response from the API. 28 | 29 | Args: 30 | request: The InvokeRequest containing message and thread info 31 | 32 | Returns: 33 | dict: Contains the complete response from the agent 34 | """ 35 | try: 36 | config = request.config or { 37 | "configurable": { 38 | "thread_id": request.thread_id 39 | } 40 | } 41 | 42 | response = "" 43 | if request.is_first_message: 44 | write_to_log(f"Processing first message for thread {request.thread_id}") 45 | async for msg in agentic_flow.astream( 46 | {"latest_user_message": request.message}, 47 | config, 48 | stream_mode="custom" 49 | ): 50 | response += str(msg) 51 | else: 52 | write_to_log(f"Processing continuation for thread {request.thread_id}") 53 | async for msg in agentic_flow.astream( 54 | Command(resume=request.message), 55 | config, 56 | stream_mode="custom" 57 | ): 58 | response += str(msg) 59 | 60 | write_to_log(f"Final response for thread {request.thread_id}: {response}") 61 | return {"response": response} 62 | 63 | except Exception as e: 64 | print(f"Exception invoking Archon for thread {request.thread_id}: {str(e)}") 65 | write_to_log(f"Error processing message for thread {request.thread_id}: {str(e)}") 66 | raise HTTPException(status_code=500, detail=str(e)) 67 | 68 | if __name__ == "__main__": 69 | import uvicorn 70 | uvicorn.run(app, host="0.0.0.0", port=8100) 71 | -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/mcp/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore specified folders 2 | iterations/ 3 | venv/ 4 | .langgraph_api/ 5 | .github/ 6 | __pycache__/ 7 | .env 8 | 9 | # Git related 10 | .git/ 11 | .gitignore 12 | .gitattributes 13 | 14 | # Python cache 15 | *.pyc 16 | *.pyo 17 | *.pyd 18 | .Python 19 | *.so 20 | .pytest_cache/ 21 | 22 | # Environment files 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # Logs 29 | *.log 30 | 31 | # IDE specific files 32 | .idea/ 33 | .vscode/ 34 | *.swp 35 | *.swo 36 | 37 | # Keep the example env file for reference 38 | !.env.example 39 | -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/mcp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Copy requirements file and install dependencies 6 | COPY requirements.txt . 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | # Copy the MCP server files 10 | COPY . . 11 | 12 | # Expose port for MCP server 13 | EXPOSE 8100 14 | 15 | # Command to run the MCP server 16 | CMD ["python", "mcp_server.py"] 17 | -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/mcp/requirements.txt: -------------------------------------------------------------------------------- 1 | mcp==1.2.1 2 | python-dotenv==1.0.1 3 | requests==2.32.3 -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/public/Archon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v6-tool-library-integration/public/Archon.png -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/public/ArchonGraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v6-tool-library-integration/public/ArchonGraph.png -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/public/ArchonLightGrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/iterations/v6-tool-library-integration/public/ArchonLightGrey.png -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==24.1.0 2 | aiohappyeyeballs==2.4.4 3 | aiohttp==3.11.11 4 | aiosignal==1.3.2 5 | aiosqlite==0.20.0 6 | altair==5.5.0 7 | annotated-types==0.7.0 8 | anthropic==0.42.0 9 | anyio==4.8.0 10 | attrs==24.3.0 11 | beautifulsoup4==4.12.3 12 | blinker==1.9.0 13 | cachetools==5.5.0 14 | certifi==2024.12.14 15 | cffi==1.17.1 16 | charset-normalizer==3.4.1 17 | click==8.1.8 18 | cohere==5.13.12 19 | colorama==0.4.6 20 | Crawl4AI==0.4.247 21 | cryptography==43.0.3 22 | Deprecated==1.2.15 23 | deprecation==2.1.0 24 | distro==1.9.0 25 | dnspython==2.7.0 26 | email_validator==2.2.0 27 | eval_type_backport==0.2.2 28 | executing==2.1.0 29 | fake-http-header==0.3.5 30 | fastapi==0.115.8 31 | fastapi-cli==0.0.7 32 | fastavro==1.10.0 33 | filelock==3.16.1 34 | frozenlist==1.5.0 35 | fsspec==2024.12.0 36 | gitdb==4.0.12 37 | GitPython==3.1.44 38 | google-auth==2.37.0 39 | googleapis-common-protos==1.66.0 40 | gotrue==2.11.1 41 | greenlet==3.1.1 42 | griffe==1.5.4 43 | groq==0.15.0 44 | h11==0.14.0 45 | h2==4.1.0 46 | hpack==4.0.0 47 | html2text==2024.2.26 48 | httpcore==1.0.7 49 | httptools==0.6.4 50 | httpx==0.27.2 51 | httpx-sse==0.4.0 52 | huggingface-hub==0.27.1 53 | hyperframe==6.0.1 54 | idna==3.10 55 | importlib_metadata==8.5.0 56 | iniconfig==2.0.0 57 | itsdangerous==2.2.0 58 | Jinja2==3.1.5 59 | jiter==0.8.2 60 | joblib==1.4.2 61 | jsonpatch==1.33 62 | jsonpath-python==1.0.6 63 | jsonpointer==3.0.0 64 | jsonschema==4.23.0 65 | jsonschema-specifications==2024.10.1 66 | jsonschema_rs==0.25.1 67 | langchain-core==0.3.33 68 | langgraph==0.2.69 69 | langgraph-checkpoint==2.0.10 70 | langgraph-cli==0.1.71 71 | langgraph-sdk==0.1.51 72 | langsmith==0.3.6 73 | litellm==1.57.8 74 | logfire==3.1.0 75 | logfire-api==3.1.0 76 | lxml==5.3.0 77 | markdown-it-py==3.0.0 78 | MarkupSafe==3.0.2 79 | mcp==1.2.1 80 | mdurl==0.1.2 81 | mistralai==1.2.6 82 | mockito==1.5.3 83 | msgpack==1.1.0 84 | multidict==6.1.0 85 | mypy-extensions==1.0.0 86 | narwhals==1.21.1 87 | nltk==3.9.1 88 | numpy==2.2.1 89 | openai==1.59.6 90 | opentelemetry-api==1.29.0 91 | opentelemetry-exporter-otlp-proto-common==1.29.0 92 | opentelemetry-exporter-otlp-proto-http==1.29.0 93 | opentelemetry-instrumentation==0.50b0 94 | opentelemetry-proto==1.29.0 95 | opentelemetry-sdk==1.29.0 96 | opentelemetry-semantic-conventions==0.50b0 97 | orjson==3.10.15 98 | packaging==24.2 99 | pandas==2.2.3 100 | pillow==10.4.0 101 | playwright==1.49.1 102 | pluggy==1.5.0 103 | postgrest==0.19.1 104 | propcache==0.2.1 105 | protobuf==5.29.3 106 | psutil==6.1.1 107 | pyarrow==18.1.0 108 | pyasn1==0.6.1 109 | pyasn1_modules==0.4.1 110 | pycparser==2.22 111 | pydantic==2.10.5 112 | pydantic-ai==0.0.22 113 | pydantic-ai-slim==0.0.22 114 | pydantic-extra-types==2.10.2 115 | pydantic-graph==0.0.22 116 | pydantic-settings==2.7.1 117 | pydantic_core==2.27.2 118 | pydeck==0.9.1 119 | pyee==12.0.0 120 | Pygments==2.19.1 121 | PyJWT==2.10.1 122 | pyOpenSSL==24.3.0 123 | pytest==8.3.4 124 | pytest-mockito==0.0.4 125 | python-dateutil==2.9.0.post0 126 | python-dotenv==1.0.1 127 | python-multipart==0.0.20 128 | pytz==2024.2 129 | PyYAML==6.0.2 130 | rank-bm25==0.2.2 131 | realtime==2.1.0 132 | referencing==0.35.1 133 | regex==2024.11.6 134 | requests==2.32.3 135 | requests-toolbelt==1.0.0 136 | rich==13.9.4 137 | rich-toolkit==0.13.2 138 | rpds-py==0.22.3 139 | rsa==4.9 140 | shellingham==1.5.4 141 | six==1.17.0 142 | smmap==5.0.2 143 | sniffio==1.3.1 144 | snowballstemmer==2.2.0 145 | soupsieve==2.6 146 | sse-starlette==2.1.3 147 | starlette==0.45.3 148 | storage3==0.11.0 149 | streamlit==1.41.1 150 | StrEnum==0.4.15 151 | structlog==24.4.0 152 | supabase==2.11.0 153 | supafunc==0.9.0 154 | tenacity==9.0.0 155 | tf-playwright-stealth==1.1.0 156 | tiktoken==0.8.0 157 | tokenizers==0.21.0 158 | toml==0.10.2 159 | tornado==6.4.2 160 | tqdm==4.67.1 161 | typer==0.15.1 162 | types-requests==2.32.0.20241016 163 | typing-inspect==0.9.0 164 | typing_extensions==4.12.2 165 | tzdata==2024.2 166 | ujson==5.10.0 167 | urllib3==2.3.0 168 | uvicorn==0.34.0 169 | watchdog==6.0.0 170 | watchfiles==1.0.4 171 | websockets==13.1 172 | wrapt==1.17.1 173 | xxhash==3.5.0 174 | yarl==1.18.3 175 | zipp==3.21.0 176 | zstandard==0.23.0 177 | -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/streamlit_pages/__init__.py: -------------------------------------------------------------------------------- 1 | # This file makes the streamlit_ui directory a Python package 2 | -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/streamlit_pages/chat.py: -------------------------------------------------------------------------------- 1 | from langgraph.types import Command 2 | import streamlit as st 3 | import uuid 4 | import sys 5 | import os 6 | 7 | # Add the current directory to Python path 8 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 9 | from archon.archon_graph import agentic_flow 10 | 11 | @st.cache_resource 12 | def get_thread_id(): 13 | return str(uuid.uuid4()) 14 | 15 | thread_id = get_thread_id() 16 | 17 | async def run_agent_with_streaming(user_input: str): 18 | """ 19 | Run the agent with streaming text for the user_input prompt, 20 | while maintaining the entire conversation in `st.session_state.messages`. 21 | """ 22 | config = { 23 | "configurable": { 24 | "thread_id": thread_id 25 | } 26 | } 27 | 28 | # First message from user 29 | if len(st.session_state.messages) == 1: 30 | async for msg in agentic_flow.astream( 31 | {"latest_user_message": user_input}, config, stream_mode="custom" 32 | ): 33 | yield msg 34 | # Continue the conversation 35 | else: 36 | async for msg in agentic_flow.astream( 37 | Command(resume=user_input), config, stream_mode="custom" 38 | ): 39 | yield msg 40 | 41 | async def chat_tab(): 42 | """Display the chat interface for talking to Archon""" 43 | st.write("Describe to me an AI agent you want to build and I'll code it for you with Pydantic AI.") 44 | st.write("Example: Build me an AI agent that can search the web with the Brave API.") 45 | 46 | # Initialize chat history in session state if not present 47 | if "messages" not in st.session_state: 48 | st.session_state.messages = [] 49 | 50 | # Add a clear conversation button 51 | if st.button("Clear Conversation"): 52 | st.session_state.messages = [] 53 | st.rerun() 54 | 55 | # Display chat messages from history on app rerun 56 | for message in st.session_state.messages: 57 | message_type = message["type"] 58 | if message_type in ["human", "ai", "system"]: 59 | with st.chat_message(message_type): 60 | st.markdown(message["content"]) 61 | 62 | # Chat input for the user 63 | user_input = st.chat_input("What do you want to build today?") 64 | 65 | if user_input: 66 | # We append a new request to the conversation explicitly 67 | st.session_state.messages.append({"type": "human", "content": user_input}) 68 | 69 | # Display user prompt in the UI 70 | with st.chat_message("user"): 71 | st.markdown(user_input) 72 | 73 | # Display assistant response in chat message container 74 | response_content = "" 75 | with st.chat_message("assistant"): 76 | message_placeholder = st.empty() # Placeholder for updating the message 77 | 78 | # Add a spinner while loading 79 | with st.spinner("Archon is thinking..."): 80 | # Run the async generator to fetch responses 81 | async for chunk in run_agent_with_streaming(user_input): 82 | response_content += chunk 83 | # Update the placeholder with the current response content 84 | message_placeholder.markdown(response_content) 85 | 86 | st.session_state.messages.append({"type": "ai", "content": response_content}) -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/streamlit_pages/styles.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the CSS styles for the Streamlit UI. 3 | """ 4 | 5 | import streamlit as st 6 | 7 | def load_css(): 8 | """ 9 | Load the custom CSS styles for the Archon UI. 10 | """ 11 | st.markdown(""" 12 | 94 | """, unsafe_allow_html=True) 95 | -------------------------------------------------------------------------------- /iterations/v6-tool-library-integration/utils/site_pages.sql: -------------------------------------------------------------------------------- 1 | -- Enable the pgvector extension 2 | create extension if not exists vector; 3 | 4 | -- Create the documentation chunks table 5 | create table site_pages ( 6 | id bigserial primary key, 7 | url varchar not null, 8 | chunk_number integer not null, 9 | title varchar not null, 10 | summary varchar not null, 11 | content text not null, -- Added content column 12 | metadata jsonb not null default '{}'::jsonb, -- Added metadata column 13 | embedding vector(1536), -- OpenAI embeddings are 1536 dimensions 14 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 15 | 16 | -- Add a unique constraint to prevent duplicate chunks for the same URL 17 | unique(url, chunk_number) 18 | ); 19 | 20 | -- Create an index for better vector similarity search performance 21 | create index on site_pages using ivfflat (embedding vector_cosine_ops); 22 | 23 | -- Create an index on metadata for faster filtering 24 | create index idx_site_pages_metadata on site_pages using gin (metadata); 25 | 26 | -- Create a function to search for documentation chunks 27 | create function match_site_pages ( 28 | query_embedding vector(1536), 29 | match_count int default 10, 30 | filter jsonb DEFAULT '{}'::jsonb 31 | ) returns table ( 32 | id bigint, 33 | url varchar, 34 | chunk_number integer, 35 | title varchar, 36 | summary varchar, 37 | content text, 38 | metadata jsonb, 39 | similarity float 40 | ) 41 | language plpgsql 42 | as $$ 43 | #variable_conflict use_column 44 | begin 45 | return query 46 | select 47 | id, 48 | url, 49 | chunk_number, 50 | title, 51 | summary, 52 | content, 53 | metadata, 54 | 1 - (site_pages.embedding <=> query_embedding) as similarity 55 | from site_pages 56 | where metadata @> filter 57 | order by site_pages.embedding <=> query_embedding 58 | limit match_count; 59 | end; 60 | $$; 61 | 62 | -- Everything above will work for any PostgreSQL database. The below commands are for Supabase security 63 | 64 | -- Enable RLS on the table 65 | alter table site_pages enable row level security; 66 | 67 | -- Create a policy that allows anyone to read 68 | create policy "Allow public read access" 69 | on site_pages 70 | for select 71 | to public 72 | using (true); -------------------------------------------------------------------------------- /mcp/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore specified folders 2 | iterations/ 3 | venv/ 4 | .langgraph_api/ 5 | .github/ 6 | __pycache__/ 7 | .env 8 | 9 | # Git related 10 | .git/ 11 | .gitignore 12 | .gitattributes 13 | 14 | # Python cache 15 | *.pyc 16 | *.pyo 17 | *.pyd 18 | .Python 19 | *.so 20 | .pytest_cache/ 21 | 22 | # Environment files 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # Logs 29 | *.log 30 | 31 | # IDE specific files 32 | .idea/ 33 | .vscode/ 34 | *.swp 35 | *.swo 36 | 37 | # Keep the example env file for reference 38 | !.env.example 39 | -------------------------------------------------------------------------------- /mcp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Copy requirements file and install dependencies 6 | COPY requirements.txt . 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | # Copy the MCP server files 10 | COPY . . 11 | 12 | # Expose port for MCP server 13 | EXPOSE 8100 14 | 15 | # Command to run the MCP server 16 | CMD ["python", "mcp_server.py"] 17 | -------------------------------------------------------------------------------- /mcp/requirements.txt: -------------------------------------------------------------------------------- 1 | mcp==1.2.1 2 | python-dotenv==1.0.1 3 | requests==2.32.3 -------------------------------------------------------------------------------- /public/Archon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/public/Archon.png -------------------------------------------------------------------------------- /public/ArchonGraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/public/ArchonGraph.png -------------------------------------------------------------------------------- /public/ArchonLightGrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/Archon/13e1fc6a0e80966a0910f711e306fc208cc117f1/public/ArchonLightGrey.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==24.1.0 2 | aiohappyeyeballs==2.4.4 3 | aiohttp==3.11.11 4 | aiosignal==1.3.2 5 | aiosqlite==0.20.0 6 | altair==5.5.0 7 | annotated-types==0.7.0 8 | anthropic==0.42.0 9 | anyio==4.8.0 10 | attrs==24.3.0 11 | beautifulsoup4==4.12.3 12 | blinker==1.9.0 13 | cachetools==5.5.0 14 | certifi==2024.12.14 15 | cffi==1.17.1 16 | charset-normalizer==3.4.1 17 | click==8.1.8 18 | cohere==5.13.12 19 | colorama==0.4.6 20 | Crawl4AI==0.4.247 21 | cryptography==43.0.3 22 | Deprecated==1.2.15 23 | deprecation==2.1.0 24 | distro==1.9.0 25 | dnspython==2.7.0 26 | email_validator==2.2.0 27 | eval_type_backport==0.2.2 28 | executing==2.1.0 29 | fake-http-header==0.3.5 30 | fastapi==0.115.8 31 | fastapi-cli==0.0.7 32 | fastavro==1.10.0 33 | filelock==3.16.1 34 | frozenlist==1.5.0 35 | fsspec==2024.12.0 36 | gitdb==4.0.12 37 | GitPython==3.1.44 38 | google-auth==2.37.0 39 | googleapis-common-protos==1.66.0 40 | gotrue==2.11.1 41 | greenlet==3.1.1 42 | griffe==1.5.4 43 | groq==0.15.0 44 | h11==0.14.0 45 | h2==4.1.0 46 | hpack==4.0.0 47 | html2text==2024.2.26 48 | httpcore==1.0.7 49 | httptools==0.6.4 50 | httpx==0.27.2 51 | httpx-sse==0.4.0 52 | huggingface-hub==0.27.1 53 | hyperframe==6.0.1 54 | idna==3.10 55 | importlib_metadata==8.5.0 56 | iniconfig==2.0.0 57 | itsdangerous==2.2.0 58 | Jinja2==3.1.5 59 | jiter==0.8.2 60 | joblib==1.4.2 61 | jsonpatch==1.33 62 | jsonpath-python==1.0.6 63 | jsonpointer==3.0.0 64 | jsonschema==4.23.0 65 | jsonschema-specifications==2024.10.1 66 | jsonschema_rs==0.25.1 67 | langchain-core==0.3.33 68 | langgraph==0.2.69 69 | langgraph-checkpoint==2.0.10 70 | langgraph-cli==0.1.71 71 | langgraph-sdk==0.1.51 72 | langsmith==0.3.6 73 | litellm==1.57.8 74 | logfire==3.1.0 75 | logfire-api==3.1.0 76 | lxml==5.3.0 77 | markdown-it-py==3.0.0 78 | MarkupSafe==3.0.2 79 | mcp==1.2.1 80 | mdurl==0.1.2 81 | mistralai==1.2.6 82 | mockito==1.5.3 83 | msgpack==1.1.0 84 | multidict==6.1.0 85 | mypy-extensions==1.0.0 86 | narwhals==1.21.1 87 | nltk==3.9.1 88 | numpy==2.2.1 89 | openai==1.59.6 90 | opentelemetry-api==1.29.0 91 | opentelemetry-exporter-otlp-proto-common==1.29.0 92 | opentelemetry-exporter-otlp-proto-http==1.29.0 93 | opentelemetry-instrumentation==0.50b0 94 | opentelemetry-proto==1.29.0 95 | opentelemetry-sdk==1.29.0 96 | opentelemetry-semantic-conventions==0.50b0 97 | orjson==3.10.15 98 | packaging==24.2 99 | pandas==2.2.3 100 | pillow==10.4.0 101 | playwright==1.49.1 102 | pluggy==1.5.0 103 | postgrest==0.19.1 104 | propcache==0.2.1 105 | protobuf==5.29.3 106 | psutil==6.1.1 107 | pyarrow==18.1.0 108 | pyasn1==0.6.1 109 | pyasn1_modules==0.4.1 110 | pycparser==2.22 111 | pydantic==2.10.5 112 | pydantic-ai==0.0.22 113 | pydantic-ai-slim==0.0.22 114 | pydantic-extra-types==2.10.2 115 | pydantic-graph==0.0.22 116 | pydantic-settings==2.7.1 117 | pydantic_core==2.27.2 118 | pydeck==0.9.1 119 | pyee==12.0.0 120 | Pygments==2.19.1 121 | PyJWT==2.10.1 122 | pyOpenSSL==24.3.0 123 | pytest==8.3.4 124 | pytest-mockito==0.0.4 125 | python-dateutil==2.9.0.post0 126 | python-dotenv==1.0.1 127 | python-multipart==0.0.20 128 | pytz==2024.2 129 | PyYAML==6.0.2 130 | rank-bm25==0.2.2 131 | realtime==2.1.0 132 | referencing==0.35.1 133 | regex==2024.11.6 134 | requests==2.32.3 135 | requests-toolbelt==1.0.0 136 | rich==13.9.4 137 | rich-toolkit==0.13.2 138 | rpds-py==0.22.3 139 | rsa==4.9 140 | shellingham==1.5.4 141 | six==1.17.0 142 | smmap==5.0.2 143 | sniffio==1.3.1 144 | snowballstemmer==2.2.0 145 | soupsieve==2.6 146 | sse-starlette==2.1.3 147 | starlette==0.45.3 148 | storage3==0.11.0 149 | streamlit==1.41.1 150 | StrEnum==0.4.15 151 | structlog==24.4.0 152 | supabase==2.11.0 153 | supafunc==0.9.0 154 | tenacity==9.0.0 155 | tf-playwright-stealth==1.1.0 156 | tiktoken==0.8.0 157 | tokenizers==0.21.0 158 | toml==0.10.2 159 | tornado==6.4.2 160 | tqdm==4.67.1 161 | typer==0.15.1 162 | types-requests==2.32.0.20241016 163 | typing-inspect==0.9.0 164 | typing_extensions==4.12.2 165 | tzdata==2024.2 166 | ujson==5.10.0 167 | urllib3==2.3.0 168 | uvicorn==0.34.0 169 | watchdog==6.0.0 170 | watchfiles==1.0.4 171 | websockets==13.1 172 | wrapt==1.17.1 173 | xxhash==3.5.0 174 | yarl==1.18.3 175 | zipp==3.21.0 176 | zstandard==0.23.0 177 | -------------------------------------------------------------------------------- /streamlit_pages/__init__.py: -------------------------------------------------------------------------------- 1 | # This file makes the streamlit_ui directory a Python package 2 | -------------------------------------------------------------------------------- /streamlit_pages/chat.py: -------------------------------------------------------------------------------- 1 | from langgraph.types import Command 2 | import streamlit as st 3 | import uuid 4 | import sys 5 | import os 6 | 7 | # Add the current directory to Python path 8 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 9 | from archon.archon_graph import agentic_flow 10 | 11 | @st.cache_resource 12 | def get_thread_id(): 13 | return str(uuid.uuid4()) 14 | 15 | thread_id = get_thread_id() 16 | 17 | async def run_agent_with_streaming(user_input: str): 18 | """ 19 | Run the agent with streaming text for the user_input prompt, 20 | while maintaining the entire conversation in `st.session_state.messages`. 21 | """ 22 | config = { 23 | "configurable": { 24 | "thread_id": thread_id 25 | } 26 | } 27 | 28 | # First message from user 29 | if len(st.session_state.messages) == 1: 30 | async for msg in agentic_flow.astream( 31 | {"latest_user_message": user_input}, config, stream_mode="custom" 32 | ): 33 | yield msg 34 | # Continue the conversation 35 | else: 36 | async for msg in agentic_flow.astream( 37 | Command(resume=user_input), config, stream_mode="custom" 38 | ): 39 | yield msg 40 | 41 | async def chat_tab(): 42 | """Display the chat interface for talking to Archon""" 43 | st.write("Describe to me an AI agent you want to build and I'll code it for you with Pydantic AI.") 44 | st.write("Example: Build me an AI agent that can search the web with the Brave API.") 45 | 46 | # Initialize chat history in session state if not present 47 | if "messages" not in st.session_state: 48 | st.session_state.messages = [] 49 | 50 | # Add a clear conversation button 51 | if st.button("Clear Conversation"): 52 | st.session_state.messages = [] 53 | st.rerun() 54 | 55 | # Display chat messages from history on app rerun 56 | for message in st.session_state.messages: 57 | message_type = message["type"] 58 | if message_type in ["human", "ai", "system"]: 59 | with st.chat_message(message_type): 60 | st.markdown(message["content"]) 61 | 62 | # Chat input for the user 63 | user_input = st.chat_input("What do you want to build today?") 64 | 65 | if user_input: 66 | # We append a new request to the conversation explicitly 67 | st.session_state.messages.append({"type": "human", "content": user_input}) 68 | 69 | # Display user prompt in the UI 70 | with st.chat_message("user"): 71 | st.markdown(user_input) 72 | 73 | # Display assistant response in chat message container 74 | response_content = "" 75 | with st.chat_message("assistant"): 76 | message_placeholder = st.empty() # Placeholder for updating the message 77 | 78 | # Add a spinner while loading 79 | with st.spinner("Archon is thinking..."): 80 | # Run the async generator to fetch responses 81 | async for chunk in run_agent_with_streaming(user_input): 82 | response_content += chunk 83 | # Update the placeholder with the current response content 84 | message_placeholder.markdown(response_content) 85 | 86 | st.session_state.messages.append({"type": "ai", "content": response_content}) -------------------------------------------------------------------------------- /streamlit_pages/styles.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the CSS styles for the Streamlit UI. 3 | """ 4 | 5 | import streamlit as st 6 | 7 | def load_css(): 8 | """ 9 | Load the custom CSS styles for the Archon UI. 10 | """ 11 | st.markdown(""" 12 | 94 | """, unsafe_allow_html=True) 95 | -------------------------------------------------------------------------------- /utils/site_pages.sql: -------------------------------------------------------------------------------- 1 | -- Enable the pgvector extension 2 | create extension if not exists vector; 3 | 4 | -- Create the documentation chunks table 5 | create table site_pages ( 6 | id bigserial primary key, 7 | url varchar not null, 8 | chunk_number integer not null, 9 | title varchar not null, 10 | summary varchar not null, 11 | content text not null, -- Added content column 12 | metadata jsonb not null default '{}'::jsonb, -- Added metadata column 13 | embedding vector(1536), -- OpenAI embeddings are 1536 dimensions 14 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 15 | 16 | -- Add a unique constraint to prevent duplicate chunks for the same URL 17 | unique(url, chunk_number) 18 | ); 19 | 20 | -- Create an index for better vector similarity search performance 21 | create index on site_pages using ivfflat (embedding vector_cosine_ops); 22 | 23 | -- Create an index on metadata for faster filtering 24 | create index idx_site_pages_metadata on site_pages using gin (metadata); 25 | 26 | -- Create a function to search for documentation chunks 27 | create function match_site_pages ( 28 | query_embedding vector(1536), 29 | match_count int default 10, 30 | filter jsonb DEFAULT '{}'::jsonb 31 | ) returns table ( 32 | id bigint, 33 | url varchar, 34 | chunk_number integer, 35 | title varchar, 36 | summary varchar, 37 | content text, 38 | metadata jsonb, 39 | similarity float 40 | ) 41 | language plpgsql 42 | as $$ 43 | #variable_conflict use_column 44 | begin 45 | return query 46 | select 47 | id, 48 | url, 49 | chunk_number, 50 | title, 51 | summary, 52 | content, 53 | metadata, 54 | 1 - (site_pages.embedding <=> query_embedding) as similarity 55 | from site_pages 56 | where metadata @> filter 57 | order by site_pages.embedding <=> query_embedding 58 | limit match_count; 59 | end; 60 | $$; 61 | 62 | -- Everything above will work for any PostgreSQL database. The below commands are for Supabase security 63 | 64 | -- Enable RLS on the table 65 | alter table site_pages enable row level security; 66 | 67 | -- Create a policy that allows anyone to read 68 | create policy "Allow public read access" 69 | on site_pages 70 | for select 71 | to public 72 | using (true); --------------------------------------------------------------------------------