From fd9462fe310b0cf77afda3bd991cea5ed3f142c1 Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Sun, 15 Mar 2026 01:30:46 -0400 Subject: [PATCH 01/16] Open-source customizable multi-agent runtime by Docker --- .../start-agents/cagent_AI_agent/.env.example | 1 + .../start-agents/cagent_AI_agent/README.md | 124 ++++++++++++++++++ .../start-agents/cagent_AI_agent/agent.yaml | 35 +++++ examples/start-agents/cagent_AI_agent/app.py | 73 +++++++++++ .../cagent_AI_agent/mcp_server.py | 15 +++ .../cagent_AI_agent/requirements.txt | 3 + 6 files changed, 251 insertions(+) create mode 100644 examples/start-agents/cagent_AI_agent/.env.example create mode 100644 examples/start-agents/cagent_AI_agent/README.md create mode 100644 examples/start-agents/cagent_AI_agent/agent.yaml create mode 100644 examples/start-agents/cagent_AI_agent/app.py create mode 100644 examples/start-agents/cagent_AI_agent/mcp_server.py create mode 100644 examples/start-agents/cagent_AI_agent/requirements.txt diff --git a/examples/start-agents/cagent_AI_agent/.env.example b/examples/start-agents/cagent_AI_agent/.env.example new file mode 100644 index 00000000..55af96a5 --- /dev/null +++ b/examples/start-agents/cagent_AI_agent/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY="sk-your-openai-api-key-here" \ No newline at end of file diff --git a/examples/start-agents/cagent_AI_agent/README.md b/examples/start-agents/cagent_AI_agent/README.md new file mode 100644 index 00000000..510637d2 --- /dev/null +++ b/examples/start-agents/cagent_AI_agent/README.md @@ -0,0 +1,124 @@ +# 🐳 Docker Agent (Multi-Agent) Production Starter + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** YAML Configuration & Web App | **Tech Stack:** Docker Agent, Python, MCP, Streamlit + +

+ Saturn Cloud + Docker + Streamlit + MCP +

+ +## πŸ“– Overview + +This template provides a production-grade multi-agent system. It utilizes **Docker Agent** as the declarative backend orchestration engine (handling memory, tool routing, and sub-agent delegation via YAML), and wraps it in a **Streamlit** web dashboard for end-user interaction. + +By decoupling the CLI runtime from the frontend using a headless execution pattern (`docker agent exec`), this architecture behaves exactly like a modern AI microservice. + +--- + +## πŸ—οΈ Local Setup & Installation + +**1. Install Python Dependencies** +Ensure you have Python installed for the MCP server and Streamlit frontend. +```bash +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt + +``` + +**2. Install Docker Agent** + +* **Mac/Windows:** Install **Docker Desktop 4.63+**, which includes the `docker agent` CLI plugin natively. +* **Linux (or Podman users):** Download the standalone binary directly from the official repository to bypass local container runtime conflicts: +```bash +curl -L -o docker-agent [https://github.com/docker/docker-agent/releases/latest/download/docker-agent-linux-amd64](https://github.com/docker/docker-agent/releases/latest/download/docker-agent-linux-amd64) +chmod +x docker-agent + +``` + + + +--- + +## πŸ” Environment Configuration + +Docker Agent runs as a compiled binary, meaning it reads environment variables directly from your active terminal session rather than automatically parsing `.env` files. + +**1. Create your `.env` file:** + +```bash +cp .env.example .env +# Edit .env and add your API keys (e.g., OPENAI_API_KEY or GOOGLE_API_KEY) + +``` + +**2. Inject the variables into your terminal session:** +Run the following command before starting the agent to auto-export your `.env` contents into your active shell environment: + +```bash +set -a; source .env; set +a + +``` + +--- + +## πŸš€ Execution Methods + +This template supports two distinct ways to interact with the multi-agent system: an interactive terminal for debugging, and a web dashboard for production usage. + +### Method 1: Interactive Terminal (TUI) + +Great for debugging and watching the agents collaborate step-by-step in real-time. + +**Run the command:** +*(If using Docker Desktop, use `docker agent run agent.yaml`)* + +```bash +./docker-agent run agent.yaml + +``` + +**Test Prompts:** +Paste these into the terminal sequentially to verify the tools and memory: + +1. *"Please analyze the following text for me: 'Docker Agent allows developers to build complex multi-agent systems using a declarative YAML syntax. It is incredibly fast and modular.'"* (Tests Python MCP execution). +2. *"From now on, I want you to remember a strict formatting preference. Whenever you write an analysis report, you must output the final result entirely in markdown bullet points, and add a short, funny haiku at the very end."* (Tests SQLite Memory database). + +### Method 2: Web Dashboard (Streamlit) + +Great for end-user interaction. The web server programmatically executes the Docker Agent headlessly in the background. + +**Run the command:** + +```bash +streamlit run app.py + +``` + +*The dashboard will automatically open in your browser at `http://localhost:8501`. Type your tasks into the chat box, and the UI will stream the final output once the backend agents complete their collaboration.* + +--- + +## ☁️ Cloud Deployment + +To deploy this multi-agent web application to production, you can provision a resource on [Saturn Cloud](https://saturncloud.io/). + +**Deployment Specifications:** + +1. **Resource Type:** Streamlit Deployment / Python Server. +2. **Environment Variables:** Inject your chosen model provider's API key directly into the Saturn Cloud secrets manager (do not commit your `.env` file). +3. **Start Command:** `streamlit run app.py --server.port 8000 --server.address 0.0.0.0` +4. **Binary Inclusion:** Ensure the Linux `docker-agent` binary is downloaded and made executable within the container workspace during the build phase so the Streamlit subprocess can successfully call it. + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Docker Agent Repository:** [Docker Agent GitHub](https://github.com/docker/docker-agent) +* **UI Framework:** [Streamlit Docs](https://docs.streamlit.io/) + diff --git a/examples/start-agents/cagent_AI_agent/agent.yaml b/examples/start-agents/cagent_AI_agent/agent.yaml new file mode 100644 index 00000000..48891c67 --- /dev/null +++ b/examples/start-agents/cagent_AI_agent/agent.yaml @@ -0,0 +1,35 @@ +version: "2" +agents: + root: + model: openai/gpt-4o-mini + description: "The primary orchestrator agent" + instruction: | + You are the coordinator of a data analysis team. + FIRST, use your 'todo' tool to create a checklist of tasks. + THEN, plan your execution using the 'think' tool, + delegate data processing to the 'analyzer', and formatting to the 'writer'. + Store important user preferences using the 'memory' tool. + Mark each todo as done. + toolsets: + - type: mcp + command: python + args: ["mcp_server.py"] + - type: think + - type: todo + - type: memory + path: agent_memory.db + sub_agents: [analyzer, writer] + + analyzer: + model: openai/gpt-4o-mini + description: "Data analysis specialist" + instruction: | + You are a data analysis agent. Use your MCP tools to analyze the text provided + by the root agent and return the exact word and character counts. + + writer: + model: openai/gpt-4o-mini + description: "Content generation specialist" + instruction: | + You are a technical writer. Take the raw metrics from the analyzer and format + them into a concise, highly professional summary report. \ No newline at end of file diff --git a/examples/start-agents/cagent_AI_agent/app.py b/examples/start-agents/cagent_AI_agent/app.py new file mode 100644 index 00000000..472e52e1 --- /dev/null +++ b/examples/start-agents/cagent_AI_agent/app.py @@ -0,0 +1,73 @@ +import streamlit as st +import subprocess +import os + +# 1. Page Configuration +st.set_page_config(page_title="Docker Agent UI", page_icon="🐳", layout="centered") +st.title("🐳 Docker Multi-Agent Dashboard") +st.markdown("A production web UI wrapping a declarative Docker Agent hierarchy.") + +# 2. Initialize Chat Memory in the Browser +if "messages" not in st.session_state: + st.session_state.messages = [] + +# 3. Render previous conversation +for msg in st.session_state.messages: + with st.chat_message(msg["role"]): + st.write(msg["content"]) + +# 4. Handle new web input +user_input = st.chat_input("Enter a task for the agent team...") + +if user_input: + # Display the user's prompt in the UI + st.session_state.messages.append({"role": "user", "content": user_input}) + with st.chat_message("user"): + st.write(user_input) + + # Execute the backend agent + with st.chat_message("assistant"): + + # Determine execution command + binary = "./docker-agent" if os.path.exists("./docker-agent") else "docker" + args = ["agent", "run", "agent.yaml", user_input] if binary == "docker" else [binary, "run", "agent.yaml", user_input] + env = os.environ.copy() + + # 5. The "Antigravity" UI: Real-time status streaming + full_log = "" + with st.status("Agent team is coordinating...", expanded=True) as status_box: + + # Popen allows us to read the terminal output line-by-line as it happens + process = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + env=env + ) + + for line in process.stdout: + full_log += line + + # Intercept CLI events and turn them into UI notifications + if line.startswith("Calling "): + tool_name = line.split("Calling ")[1].split("(")[0] + st.markdown(f"βœ… Executing tool: **`{tool_name}`**") + elif line.startswith("--- Agent:"): + agent_name = line.replace("--- Agent:", "").replace("---", "").strip() + st.markdown(f"πŸ”„ Handing off to **{agent_name.capitalize()}**...") + + # Wait for the process to finish and collapse the status box + process.wait() + status_box.update(label="Tasks completed successfully!", state="complete", expanded=False) + + # 6. Extract and display ONLY the final text + # Docker Agent tool logs always end with a closing parenthesis and a newline ")\n" + if ")\n" in full_log: + final_output = full_log.rpartition(")\n")[-1].strip() + else: + final_output = full_log.strip() + + # Display the clean result to the user + st.write(final_output) + st.session_state.messages.append({"role": "assistant", "content": final_output}) \ No newline at end of file diff --git a/examples/start-agents/cagent_AI_agent/mcp_server.py b/examples/start-agents/cagent_AI_agent/mcp_server.py new file mode 100644 index 00000000..573ba3b3 --- /dev/null +++ b/examples/start-agents/cagent_AI_agent/mcp_server.py @@ -0,0 +1,15 @@ +from mcp.server.fastmcp import FastMCP + +# Initialize the MCP tool server +mcp = FastMCP("DataAnalyzer") + +@mcp.tool() +def analyze_text_metrics(text_input: str) -> str: + """A custom Python tool that analyzes string length and word count.""" + words = len(text_input.split()) + chars = len(text_input) + return f"Analysis complete: {words} words, {chars} characters." + +if __name__ == "__main__": + # Runs the server over standard I/O so cagent can communicate with it + mcp.run() \ No newline at end of file diff --git a/examples/start-agents/cagent_AI_agent/requirements.txt b/examples/start-agents/cagent_AI_agent/requirements.txt new file mode 100644 index 00000000..0a52c63d --- /dev/null +++ b/examples/start-agents/cagent_AI_agent/requirements.txt @@ -0,0 +1,3 @@ +mcp>=1.0.0 +fastmcp>=0.1.0 +streamlit>=1.32.0 \ No newline at end of file From 2d9068a0e9372cab8e6bdd87143668374283ab09 Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Sun, 15 Mar 2026 18:23:42 -0400 Subject: [PATCH 02/16] Real-time voice infrastructure with multi-provider STT/TTS (Deepgram, ElevenLabs, Azure, Google) and WebSocket streaming --- .../sayna_voice_agent/.env.example | 2 + .../start-agents/sayna_voice_agent/README.md | 102 ++++++++++ .../start-agents/sayna_voice_agent/index.html | 176 ++++++++++++++++++ .../sayna_voice_agent/requirements.txt | 6 + .../start-agents/sayna_voice_agent/server.py | 96 ++++++++++ 5 files changed, 382 insertions(+) create mode 100644 examples/start-agents/sayna_voice_agent/.env.example create mode 100644 examples/start-agents/sayna_voice_agent/README.md create mode 100644 examples/start-agents/sayna_voice_agent/index.html create mode 100644 examples/start-agents/sayna_voice_agent/requirements.txt create mode 100644 examples/start-agents/sayna_voice_agent/server.py diff --git a/examples/start-agents/sayna_voice_agent/.env.example b/examples/start-agents/sayna_voice_agent/.env.example new file mode 100644 index 00000000..4f67a58a --- /dev/null +++ b/examples/start-agents/sayna_voice_agent/.env.example @@ -0,0 +1,2 @@ +# Core STT +DEEPGRAM_API_KEY="your-deepgram-key" diff --git a/examples/start-agents/sayna_voice_agent/README.md b/examples/start-agents/sayna_voice_agent/README.md new file mode 100644 index 00000000..766387c5 --- /dev/null +++ b/examples/start-agents/sayna_voice_agent/README.md @@ -0,0 +1,102 @@ +# πŸŽ™οΈ Sayna Voice Agent Starter + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Python Server & Web Frontend | **Tech Stack:** Deepgram Agent API, WebSocket, Python, HTML5/JS + +

+ Saturn Cloud + WebSocket + Deepgram +

+ +## πŸ“– Overview + +The **Sayna Voice Agent** is a production-ready, ultra-low latency voice infrastructure template. + +It utilizes **Deepgram's End-to-End Voice Agent API**, which dramatically reduces latency by handling the Speech-to-Text (STT), Large Language Model (LLM) routing, and Text-to-Speech (TTS) entirely on their edge servers. + +### ✨ Key Capabilities +* **Managed LLM Orchestration:** Deepgram acts as a centralized orchestrator. By passing a JSON configuration to the WebSocket, Deepgram natively routes the transcript to third-party LLMs (like Google Gemini, OpenAI, or Anthropic) on their backend. You only need a single Deepgram API key to power the entire pipeline. +* **Cross-Browser Audio Streaming:** The `index.html` frontend features a custom Web Audio API downsampler, ensuring strict browsers (like Firefox) can capture 48kHz hardware microphones and stream them at the required 16kHz without throwing `DOMExceptions`. +* **Real-Time Visualizer:** Includes a live-rendering `` audio visualizer so users know their microphone is actively capturing sound. + +--- + +## πŸ“ Project Structure + +```text +sayna_voice_agent/ +β”œβ”€β”€ .env.example # Template for API keys +β”œβ”€β”€ requirements.txt # Python dependencies (websockets, python-dotenv) +β”œβ”€β”€ server.py # Python WebSocket proxy server +β”œβ”€β”€ index.html # Frontend UI with mic capture and audio playback +└── README.md # Documentation + +``` + +--- + +## πŸ—οΈ Local Setup & Execution + +**1. Install Dependencies** +Ensure you have Python installed, then create your virtual environment. + +```bash +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt + +``` + +**2. Configure Environment** +Because Deepgram manages the LLM connection internally, you only need one API key for the entire application. + +```bash +cp .env.example .env +# Edit .env and add: DEEPGRAM_API_KEY="your-key-here" + +``` + +**3. Launch the Application** +This architecture requires two local servers running simultaneously: one for the WebSocket backend, and one to serve the secure HTML frontend. + +*Open Terminal 1 (The Backend):* + +```bash +python server.py +# Listens on ws://localhost:8000 + +``` + +*Open Terminal 2 (The Frontend):* + +```bash +python -m http.server 3000 +# Serves the UI on http://localhost:3000 + +``` + +**4. Talk to the Agent** +Open your web browser and navigate to `http://localhost:3000`. Click **Start Conversation**, allow microphone permissions, and speak! + +--- + +## ☁️ Cloud Deployment + +To deploy this low-latency voice infrastructure to a production environment, provision an instance on [Saturn Cloud](https://saturncloud.io/). + +**Deployment Specifications:** + +1. **Resource Type:** Python Server / Background Job. +2. **Environment Variables:** Inject `DEEPGRAM_API_KEY` securely into the Saturn Cloud secrets manager. +3. **Network Routing:** Ensure the deployment exposes port `8000` (for the WebSocket) and port `3000` (if serving the static HTML from the same instance). The load balancer must be configured to allow upgraded WebSocket (`ws://` or `wss://`) connections. +4. **Execution:** Run the deployment with standard Python execution: `python server.py`. + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Voice Engine:** [Deepgram Agent API Docs](https://developers.deepgram.com/docs/flux/agent) +* **Server Protocol:** [Python WebSockets](https://websockets.readthedocs.io/) \ No newline at end of file diff --git a/examples/start-agents/sayna_voice_agent/index.html b/examples/start-agents/sayna_voice_agent/index.html new file mode 100644 index 00000000..5c4a8fe3 --- /dev/null +++ b/examples/start-agents/sayna_voice_agent/index.html @@ -0,0 +1,176 @@ + + + + + + Sayna Voice Agent + + + + +

πŸŽ™οΈ Sayna Voice Agent

+

Click start, allow microphone access, and say hello!

+ + + +
Ready to connect...
+ + + + + + \ No newline at end of file diff --git a/examples/start-agents/sayna_voice_agent/requirements.txt b/examples/start-agents/sayna_voice_agent/requirements.txt new file mode 100644 index 00000000..03438aad --- /dev/null +++ b/examples/start-agents/sayna_voice_agent/requirements.txt @@ -0,0 +1,6 @@ +websockets>=12.0 +deepgram-sdk>=3.0.0 +elevenlabs>=1.0.0 +openai>=1.14.0 +python-dotenv>=1.0.0 +google-generativeai \ No newline at end of file diff --git a/examples/start-agents/sayna_voice_agent/server.py b/examples/start-agents/sayna_voice_agent/server.py new file mode 100644 index 00000000..34223866 --- /dev/null +++ b/examples/start-agents/sayna_voice_agent/server.py @@ -0,0 +1,96 @@ +import asyncio +import websockets +import json +import os +from dotenv import load_dotenv + +load_dotenv() + +# The exact JSON you copied from Deepgram! +AGENT_SETTINGS = { + "type": "Settings", + "audio": { + "input": { + "encoding": "linear16", + "sample_rate": 16000 # Adjusted to standard browser mic rate + }, + "output": { + "encoding": "linear16", + "sample_rate": 24000, + "container": "none" + } + }, + "agent": { + "language": "en", + "speak": { + "provider": { + "type": "deepgram", + "model": "aura-2-odysseus-en" + } + }, + "listen": { + "provider": { + "type": "deepgram", + "version": "v2", + "model": "flux-general-en" + } + }, + "think": { + "provider": { + "type": "google", + "model": "gemini-2.5-flash" + }, + "prompt": "You are Sayna, a highly intelligent, warm, and concise voice assistant. Keep all responses to 1-2 sentences maximum. Do not use markdown." + }, + "greeting": "Hello! I am Sayna, your voice agent. How can I help you?" + } +} + +async def handle_client(client_ws): + """Handles the connection from the frontend browser.""" + print("🟒 Browser frontend connected.") + + # Connect directly to Deepgram's conversational Agent API + dg_uri = "wss://agent.deepgram.com/v1/agent/converse" + headers = {"Authorization": f"Token {os.getenv('DEEPGRAM_API_KEY')}"} + + try: + async with websockets.connect(dg_uri, additional_headers=headers) as dg_ws: + print("πŸš€ Connected to Deepgram Voice Agent API!") + + # 1. Send the Settings JSON first to configure the brain/ears/mouth + await dg_ws.send(json.dumps(AGENT_SETTINGS)) + + # 2. Task to forward User Microphone -> Deepgram + async def mic_to_deepgram(): + async for message in client_ws: + # Only forward binary audio data to Deepgram + if isinstance(message, bytes): + await dg_ws.send(message) + + # 3. Task to forward Deepgram Audio/Events -> User Browser + async def deepgram_to_speaker(): + async for message in dg_ws: + await client_ws.send(message) + + # Optional: Print out the text logs so you can see what the agent is thinking + if isinstance(message, str): + data = json.loads(message) + if data.get("type") == "ConversationText": + role = data.get("role", "unknown") + content = data.get("content", "") + print(f"[{role.upper()}]: {content}") + + # Run both streaming tasks at the same time + await asyncio.gather(mic_to_deepgram(), deepgram_to_speaker()) + + except Exception as e: + print(f"πŸ”΄ Connection closed: {e}") + +async def main(): + print("πŸ“‘ Sayna Voice Server listening on ws://localhost:8000") + async with websockets.serve(handle_client, "localhost", 8000): + await asyncio.Future() + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file From ef54a766aeb4f046f8472d26d4f97026cc109eab Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Tue, 17 Mar 2026 11:22:28 -0400 Subject: [PATCH 03/16] Simple to multi-agent examples with web search & knowledge base --- .../agno_ai_examples/.env.example | 1 + .../01_simple_search_agent.py | 15 + .../02_knowledge_base_agent.py | 23 ++ .../agno_ai_examples/03_multi_agent_swarm.py | 42 +++ .../agno_ai_examples/04_streamlit_ui.py | 78 +++++ .../start-agents/agno_ai_examples/README.md | 120 +++++++ .../agno_ai_examples/data/company_history.txt | 8 + .../agno_ai_examples/requirements.txt | 8 + examples/start-agents/k8s-kaos/README.md | 119 +++++++ .../start-agents/k8s-kaos/kaos_bootstrap.sh | 316 ++++++++++++++++++ .../kaos_starter/manifests/00-namespace.yaml | 4 + .../manifests/01-llm-deployment.yaml | 33 ++ .../kaos_starter/manifests/02-mcp-server.yaml | 34 ++ .../manifests/03-agent-deployment.yaml | 34 ++ .../kaos_starter/manifests/04-frontend.yaml | 35 ++ .../kaos_starter/src/agent/Dockerfile | 5 + .../k8s-kaos/kaos_starter/src/agent/app.py | 30 ++ .../kaos_starter/src/frontend/Dockerfile | 5 + .../k8s-kaos/kaos_starter/src/frontend/app.py | 25 ++ .../k8s-kaos/kaos_starter/src/mcp/Dockerfile | 5 + .../k8s-kaos/kaos_starter/src/mcp/server.py | 8 + examples/start-agents/k8s-kaos/teardown.sh | 11 + 22 files changed, 959 insertions(+) create mode 100644 examples/start-agents/agno_ai_examples/.env.example create mode 100644 examples/start-agents/agno_ai_examples/01_simple_search_agent.py create mode 100644 examples/start-agents/agno_ai_examples/02_knowledge_base_agent.py create mode 100644 examples/start-agents/agno_ai_examples/03_multi_agent_swarm.py create mode 100644 examples/start-agents/agno_ai_examples/04_streamlit_ui.py create mode 100644 examples/start-agents/agno_ai_examples/README.md create mode 100644 examples/start-agents/agno_ai_examples/data/company_history.txt create mode 100644 examples/start-agents/agno_ai_examples/requirements.txt create mode 100644 examples/start-agents/k8s-kaos/README.md create mode 100644 examples/start-agents/k8s-kaos/kaos_bootstrap.sh create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/manifests/00-namespace.yaml create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/manifests/01-llm-deployment.yaml create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/manifests/02-mcp-server.yaml create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/manifests/03-agent-deployment.yaml create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/manifests/04-frontend.yaml create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/src/agent/Dockerfile create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/src/agent/app.py create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/src/frontend/Dockerfile create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/src/frontend/app.py create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/src/mcp/Dockerfile create mode 100644 examples/start-agents/k8s-kaos/kaos_starter/src/mcp/server.py create mode 100644 examples/start-agents/k8s-kaos/teardown.sh diff --git a/examples/start-agents/agno_ai_examples/.env.example b/examples/start-agents/agno_ai_examples/.env.example new file mode 100644 index 00000000..baa949d1 --- /dev/null +++ b/examples/start-agents/agno_ai_examples/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY="sk-..." diff --git a/examples/start-agents/agno_ai_examples/01_simple_search_agent.py b/examples/start-agents/agno_ai_examples/01_simple_search_agent.py new file mode 100644 index 00000000..f7fbdb92 --- /dev/null +++ b/examples/start-agents/agno_ai_examples/01_simple_search_agent.py @@ -0,0 +1,15 @@ +from dotenv import load_dotenv +load_dotenv() + +from agno.agent import Agent +from agno.tools.duckduckgo import DuckDuckGoTools + +print("🌐 Booting Web Researcher...") +agent = Agent( + tools=[DuckDuckGoTools()], + description="You are a senior web researcher. Always find the most recent information.", + instructions=["Always cite your sources."], + markdown=True +) + +agent.print_response("What are the biggest AI news headlines today?") \ No newline at end of file diff --git a/examples/start-agents/agno_ai_examples/02_knowledge_base_agent.py b/examples/start-agents/agno_ai_examples/02_knowledge_base_agent.py new file mode 100644 index 00000000..15dce0df --- /dev/null +++ b/examples/start-agents/agno_ai_examples/02_knowledge_base_agent.py @@ -0,0 +1,23 @@ +from dotenv import load_dotenv +load_dotenv() + +from agno.agent import Agent +from agno.knowledge.text import TextKnowledgeBase +from agno.vectordb.lancedb import LanceDb + +print("πŸ“š Booting Knowledge Keeper & Embedding Data...") + +knowledge_base = TextKnowledgeBase( + path="data", + vector_db=LanceDb(table_name="acme_history", uri="./lancedb"), +) +knowledge_base.load(recreate=True) + +agent = Agent( + knowledge=knowledge_base, + search_knowledge=True, + description="You are an internal corporate archivist. You answer questions strictly based on the provided knowledge base.", + markdown=True +) + +agent.print_response("When was Acme Corp founded, and what is their secret project?") \ No newline at end of file diff --git a/examples/start-agents/agno_ai_examples/03_multi_agent_swarm.py b/examples/start-agents/agno_ai_examples/03_multi_agent_swarm.py new file mode 100644 index 00000000..4c788d73 --- /dev/null +++ b/examples/start-agents/agno_ai_examples/03_multi_agent_swarm.py @@ -0,0 +1,42 @@ +from dotenv import load_dotenv +load_dotenv() + +from agno.agent import Agent +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.knowledge.text import TextKnowledgeBase +from agno.vectordb.lancedb import LanceDb + +print("πŸ‘” Booting the Swarm...") + +web_agent = Agent( + name="Web Researcher", + role="Search the web for real-time information", + tools=[DuckDuckGoTools()], + markdown=True +) + +knowledge_base = TextKnowledgeBase( + path="data", + vector_db=LanceDb(table_name="acme_history", uri="./lancedb"), +) +knowledge_agent = Agent( + name="Knowledge Keeper", + role="Search the internal company knowledge base", + knowledge=knowledge_base, + search_knowledge=True, + markdown=True +) + +editor_agent = Agent( + name="Lead Editor", + role="Synthesize web and internal data into a cohesive report", + team=[web_agent, knowledge_agent], + instructions=[ + "First, ask the Knowledge Keeper for internal context on Acme Corp's Quantum AI Engine.", + "Second, ask the Web Researcher to find current public news about 'Quantum AI'.", + "Finally, write a comparative report synthesizing both sources." + ], + markdown=True +) + +editor_agent.print_response("Write the comparative report on Acme Corp vs the current public market.", stream=True) \ No newline at end of file diff --git a/examples/start-agents/agno_ai_examples/04_streamlit_ui.py b/examples/start-agents/agno_ai_examples/04_streamlit_ui.py new file mode 100644 index 00000000..373111b9 --- /dev/null +++ b/examples/start-agents/agno_ai_examples/04_streamlit_ui.py @@ -0,0 +1,78 @@ +import streamlit as st +from dotenv import load_dotenv +from agno.agent import Agent +from agno.team import Team +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.knowledge.knowledge import Knowledge +from agno.vectordb.lancedb import LanceDb + +# Load environment variables (API Keys) +load_dotenv() + +st.set_page_config(page_title="Market Intelligence Swarm", page_icon="🐝", layout="wide") +st.title("🐝 Market Intelligence Swarm") +st.markdown("Delegate tasks to the Lead Editor. It will coordinate the **Web Researcher** and **Knowledge Keeper** to write your report.") + +@st.cache_resource +def get_swarm(): + # 1. Web Agent + web_agent = Agent( + name="Web Researcher", + role="Search the web for real-time information", + tools=[DuckDuckGoTools()], + markdown=True + ) + + # 2. Knowledge Agent + knowledge_base = Knowledge( + vector_db=LanceDb(table_name="acme_history", uri="./lancedb"), + ) + # This automatically reads and embeds everything in your data/ folder + knowledge_base.insert(path="data") + + knowledge_agent = Agent( + name="Knowledge Keeper", + role="Search the internal company knowledge base", + knowledge=knowledge_base, + search_knowledge=True, + markdown=True + ) + + # 3. The Lead Editor (Now uses the official Team class!) + editor_agent = Team( + name="Lead Editor", + members=[web_agent, knowledge_agent], + instructions=[ + "First, ask the Knowledge Keeper for internal context on the user's query.", + "Second, ask the Web Researcher to find current public news regarding the query.", + "Finally, write a comprehensive report synthesizing both sources." + ], + markdown=True + ) + return editor_agent + +# Initialize the swarm +editor_agent = get_swarm() + +# Session state for chat history +if "messages" not in st.session_state: + st.session_state.messages = [] + +# Render chat history +for msg in st.session_state.messages: + with st.chat_message(msg["role"]): + st.markdown(msg["content"]) + +# User Input +if prompt := st.chat_input("E.g., Compare Acme's secret project to today's public AI news..."): + # Show user message + st.chat_message("user").markdown(prompt) + st.session_state.messages.append({"role": "user", "content": prompt}) + + # Generate and show assistant response + with st.chat_message("assistant"): + with st.spinner("The Swarm is researching and writing (this may take a moment)..."): + response = editor_agent.run(prompt) + st.markdown(response.content) + + st.session_state.messages.append({"role": "assistant", "content": response.content}) diff --git a/examples/start-agents/agno_ai_examples/README.md b/examples/start-agents/agno_ai_examples/README.md new file mode 100644 index 00000000..fc8f67b6 --- /dev/null +++ b/examples/start-agents/agno_ai_examples/README.md @@ -0,0 +1,120 @@ +# 🐝 Agno AI Examples (Market Intelligence Swarm) + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Python Repository & Web App | **Tech Stack:** Agno (v2.5+), LanceDB, Streamlit, DuckDuckGo Search + +

+ Saturn Cloud + Agno + LanceDB + Streamlit +

+ +## πŸ“– Overview + +This template provides a simple, progressive introduction to building production-grade multi-agent systems with web search and local knowledge bases. It utilizes **Agno** (formerly Phidata) to abstract away the complexities of tool calling, unified vector embeddings, and RAG (Retrieval-Augmented Generation), wrapping the final architecture in an interactive **Streamlit** Web UI. + +### 🧩 The Market Intelligence Swarm +The final application deploys a cohesive `Team` of specialized AI agents: +1. **🌐 Web Researcher:** Scours the live internet for breaking news using DuckDuckGo. +2. **πŸ“š Knowledge Keeper:** Reads and answers questions based on private internal documents using a local LanceDB vector database. +3. **πŸ‘” Lead Editor:** The swarm leader. It takes your prompt from the chat UI, delegates research tasks to the Web and Knowledge agents, and synthesizes their findings into a polished report. + +--- + +## πŸ—οΈ Local Setup & Installation + +Because this architecture relies on a local vector database (LanceDB) rather than heavy containerization, it can be run entirely within a standard Python virtual environment. + +**1. Create the Environment & Install Dependencies** +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +**2. Prepare the Dummy Knowledge Base** +Create a `data/` folder and add a test file for the Knowledge agent to read. For example, create `data/company_history.txt`: +```text +CONFIDENTIAL INTERNAL MEMO +Company: Acme Corp +Founded: 2010 + +Major Milestones: +- 2015: Reached $1M in annual recurring revenue. +- 2022: Survived the great market crash by pivoting to cloud infrastructure. +- 2025: Secretly launched the "Quantum AI Engine", which boosted internal efficiency by 200%. The CEO plans to make this public later this year. +``` + +--- + +## πŸ” Environment Configuration + +Agno securely loads your API keys via the `python-dotenv` library. + +**1. Create your `.env` file:** +```bash +cp .env.example .env +``` + +**2. Add your Provider Keys:** +Open the `.env` file and insert your active API key. +```text +OPENAI_API_KEY="sk-your-actual-api-key-goes-here" +``` + +--- + +## πŸš€ Execution Methods & Testing + +This repository is designed progressively. Run the scripts in order to see how single agents evolve into a multi-agent swarm, culminating in a full Web UI. + +### Phase 1: CLI Fundamentals +Test the basic building blocks in your terminal: +```bash +# 1. Test basic tool calling and live web connectivity +python 01_simple_search_agent.py + +# 2. Test the RAG pipeline (Embeds the data/ folder into LanceDB) +python 02_knowledge_base_agent.py + +# 3. Test agentic delegation (Editor orchestrates the Researcher and Keeper) +python 03_multi_agent_swarm.py +``` + +### Phase 2: Production Web Dashboard +Launch the interactive Streamlit UI to chat with the Lead Editor from your browser: +```bash +streamlit run 04_streamlit_ui.py +``` + +**πŸ§ͺ The Ultimate Swarm Test Prompt:** +Once the Streamlit UI opens in your browser (`http://localhost:8501`), paste this exact question into the chat box to force the Swarm to combine your private PDF data with live internet data: + +> *"What is Acme Corp's secret project, and how does it compare to the biggest AI news today?"* + +Watch the UI as the Lead Editor seamlessly tasks the Knowledge Keeper to read the internal memo, tasks the Web Researcher to browse the internet, and synthesizes both into a final market intelligence report! + +--- + +## ☁️ Cloud Deployment + +To deploy this multi-agent web application to production, you can provision a resource on [Saturn Cloud](https://saturncloud.io/). + +**Deployment Specifications:** + +1. **Resource Type:** Streamlit Deployment / Python Server. +2. **Hardware:** CPU instance (or GPU if swapping OpenAI for a local open-weight model). +3. **Environment Variables:** Inject your `OPENAI_API_KEY` directly into the Saturn Cloud secrets manager (do not commit your `.env` file to version control). +4. **Start Command:** `streamlit run 04_streamlit_ui.py --server.port 8000 --server.address 0.0.0.0` +5. **Persistent Storage:** Ensure your `data/` and `lancedb/` directories are mounted to persistent storage so your vector embeddings survive container restarts. + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Agent Framework:** [Agno Official Docs](https://docs.agno.com/) +* **Vector Database:** [LanceDB Documentation](https://lancedb.github.io/lancedb/) +* **UI Framework:** [Streamlit Docs](https://docs.streamlit.io/) diff --git a/examples/start-agents/agno_ai_examples/data/company_history.txt b/examples/start-agents/agno_ai_examples/data/company_history.txt new file mode 100644 index 00000000..d1efc13b --- /dev/null +++ b/examples/start-agents/agno_ai_examples/data/company_history.txt @@ -0,0 +1,8 @@ +CONFIDENTIAL INTERNAL MEMO +Company: Acme Corp +Founded: 2010 + +Major Milestones: +- 2015: Reached $1M in annual recurring revenue. +- 2022: Survived the great market crash by pivoting to cloud infrastructure. +- 2025: Secretly launched the "Quantum AI Engine", which boosted internal efficiency by 200%. The CEO plans to make this public later this year. diff --git a/examples/start-agents/agno_ai_examples/requirements.txt b/examples/start-agents/agno_ai_examples/requirements.txt new file mode 100644 index 00000000..fb7ed474 --- /dev/null +++ b/examples/start-agents/agno_ai_examples/requirements.txt @@ -0,0 +1,8 @@ +agno +openai +duckduckgo-search +lancedb +tantivy +python-dotenv +ddgs +streamlit \ No newline at end of file diff --git a/examples/start-agents/k8s-kaos/README.md b/examples/start-agents/k8s-kaos/README.md new file mode 100644 index 00000000..ddb2f43a --- /dev/null +++ b/examples/start-agents/k8s-kaos/README.md @@ -0,0 +1,119 @@ +# ☸️ KAOS (Kubernetes Agent Orchestration System) Starter + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Kubernetes YAML & Web App | **Tech Stack:** Kubernetes, FastAPI, Streamlit, MCP, In-Cluster LLM + +

+ Saturn Cloud + Kubernetes + Streamlit + MCP + Ollama +

+ +## πŸ“– Overview + +This template provides a production-grade, distributed multi-agent web application built entirely on Kubernetes-native primitives. + +By decoupling the agent logic, the LLM, the tool server, and the web frontend into independent microservices, this architecture behaves exactly like a modern enterprise AI platform. It utilizes an **In-Cluster LLM** (Ollama) to guarantee zero data egress and zero per-token API costs. + +### 🧩 Microservice Architecture +1. **Frontend (Streamlit):** Provides the user-facing web chat interface. +2. **Backend Agent (FastAPI):** Acts as the API. Receives user input from the frontend, queries MCP tools, and orchestrates the LLM. +3. **Tool Engine (MCP):** A Python server executing custom capabilities (e.g., calculations, system data fetching). +4. **AI Brain (Ollama):** Hosts the open-weight LLM (e.g., Qwen) entirely offline on the cluster. + +--- + +## πŸ” Environment Configuration + +Unlike traditional setups that rely on local `.env` files, KAOS uses Kubernetes internal DNS for secure microservice communication. + +The microservices find each other using internal URLs injected as environment variables inside the YAML manifests (e.g., `http://llm-service.kaos-system.svc.cluster.local:11434`). No internal backend traffic ever touches the public internet. + +--- + +## πŸš€ Local Setup & Execution Methods + +Ensure you have Docker, `kubectl`, and Minikube installed on your host system (`minikube start --driver=docker`). This template supports two distinct ways to deploy: + +### Method 1: Automated Full-Stack Bootstrap (Linux Recommended) + +If you are running on a fresh Linux machine (like Kali or Ubuntu), use the included bootstrap script to automate the entire setup, build, and deployment process. + +```bash +chmod +x kaos_bootstrap.sh +./kaos_bootstrap.sh +``` +*This script provisions Minikube, builds all four microservice Docker images, injects them into the cluster, and downloads the `qwen2.5:0.5b` model into the LLM pod automatically.* + +### Method 2: Manual Kubernetes Deployment + +Great for learning the architecture, debugging, or making code changes. + +**1. Build & Load the Docker Images** +Because Minikube runs in an isolated environment, you must build your custom images locally and explicitly load them into the cluster's internal registry: +```bash +sudo -E docker build -t kaos-mcp:latest ./src/mcp +sudo -E docker build -t kaos-agent:latest ./src/agent +sudo -E docker build -t kaos-frontend:latest ./src/frontend + +minikube image load kaos-mcp:latest +minikube image load kaos-agent:latest +minikube image load kaos-frontend:latest +``` + +**2. Apply the Manifests & Download the Model** +```bash +kubectl apply -f manifests/ +kubectl wait --for=condition=ready pod -l app=kaos-llm -n kaos-system --timeout=300s +kubectl exec deployment/kaos-llm -n kaos-system -- ollama pull qwen2.5:0.5b +``` + +--- + +## 🌐 Accessing the Web Dashboard + +Because the Streamlit frontend is safely locked inside the Kubernetes cluster, you must explicitly route traffic from your host machine's browser into the cluster. + +We achieve this using a Kubernetes `NodePort` service combined with Minikube's built-in routing command: + +```bash +minikube service frontend-service -n kaos-system +``` +*Running this command automatically locates the exposed port, builds a secure network bridge to your local machine, and opens the Streamlit chat interface directly in your default web browser.* + +--- + +## ☁️ Cloud Deployment + +To deploy this distributed agent architecture to production, migrate the local pods to **Saturn Cloud** resources. Saturn Cloud manages the Kubernetes control plane for you. + +**Deployment Specifications:** + +1. **The LLM Brain:** Saturn Cloud Deployment (GPU-backed, running `ollama/ollama:latest` with start script `ollama pull qwen2.5:0.5b`). *Must include Env Var: `OLLAMA_HOST=0.0.0.0:8000` to satisfy Saturn Cloud routing rules*. +2. **The MCP Tool Server:** Saturn Cloud Deployment (CPU-backed, running `python server.py`). +3. **The Agent API:** Saturn Cloud Deployment (CPU-backed, running `uvicorn app:app --host 0.0.0.0 --port 8000`). Update its `LLM_HOST` and `MCP_HOST` environment variables to point to the secure internal URLs of Deployments 1 & 2. +4. **The Web Frontend:** Saturn Cloud Deployment (CPU-backed, running `streamlit run app.py --server.port 8000 --server.address 0.0.0.0`). Update its `AGENT_HOST` environment variable to point to Deployment 3. + +--- + +## 🧹 Clean Up + +To safely destroy the local cluster and free up your system's RAM and CPU, use the included teardown script: + +```bash +chmod +x teardown.sh +./teardown.sh +``` +*(This safely deletes the namespace, terminates all pods, and stops Minikube).* + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Orchestration:** [Kubernetes Official Docs](https://kubernetes.io/docs/home/) +* **Local Testing:** [Minikube Documentation](https://minikube.sigs.k8s.io/docs/) +* **UI Framework:** [Streamlit Docs](https://docs.streamlit.io/) \ No newline at end of file diff --git a/examples/start-agents/k8s-kaos/kaos_bootstrap.sh b/examples/start-agents/k8s-kaos/kaos_bootstrap.sh new file mode 100644 index 00000000..9c8e0ab1 --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_bootstrap.sh @@ -0,0 +1,316 @@ +#!/bin/bash +set -e + +echo "=======================================================" +echo "πŸš€ KAOS V2: FULL-STACK WEB ARCHITECTURE BOOTSTRAP πŸš€" +echo "=======================================================" + +echo "πŸ”„ Phase 1: Installing Dependencies (Requires Sudo)..." +sudo apt-get update +sudo apt-get install -y curl docker.io +unset DOCKER_HOST +sudo systemctl enable docker +sudo systemctl start docker + +echo "πŸ“¦ Installing kubectl & minikube..." +curl -LO --retry 3 "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" +sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl +rm kubectl +curl -LO --retry 3 https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 +sudo install minikube-linux-amd64 /usr/local/bin/minikube +rm minikube-linux-amd64 + +echo "πŸš€ Starting Minikube cluster..." +minikube start --driver=docker + +echo "=======================================================" +echo "πŸ“ Phase 2: Writing Full-Stack Microservices..." +echo "=======================================================" +mkdir -p kaos_starter/src/mcp kaos_starter/src/agent kaos_starter/src/frontend kaos_starter/manifests +cd kaos_starter + +echo "πŸ› οΈ 1/3: Writing MCP Server..." +cat << 'INNER_EOF' > src/mcp/server.py +from fastapi import FastAPI +import uvicorn +app = FastAPI() +@app.get("/tools/calculate") +def calculate(): + return {"tool": "calculator", "result": "The Kubernetes cluster is active and routing web traffic!"} +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) +INNER_EOF + +cat << 'INNER_EOF' > src/mcp/Dockerfile +FROM python:3.9-slim +WORKDIR /app +RUN pip install fastapi uvicorn +COPY server.py . +CMD ["python", "server.py"] +INNER_EOF + +echo "🧠 2/3: Writing FastAPI Agent Backend..." +cat << 'INNER_EOF' > src/agent/app.py +import os, requests +from fastapi import FastAPI +from pydantic import BaseModel + +LLM_HOST = os.getenv("LLM_HOST", "http://llm-service.kaos-system.svc.cluster.local:11434") +MCP_HOST = os.getenv("MCP_HOST", "http://mcp-service.kaos-system.svc.cluster.local:8000") + +app = FastAPI() + +class ChatRequest(BaseModel): + message: str + +@app.post("/chat") +def chat_endpoint(req: ChatRequest): + print(f"πŸ“₯ Received: {req.message}", flush=True) + try: + mcp_res = requests.get(f"{MCP_HOST}/tools/calculate").json() + tool_data = mcp_res.get("result", "No data") + except Exception: + tool_data = "Tool offline." + + dynamic_prompt = f"System Data: '{tool_data}'. User asks: '{req.message}'. Provide a helpful response." + llm_payload = {"model": "qwen2.5:0.5b", "prompt": dynamic_prompt, "stream": False} + + llm_res = requests.post(f"{LLM_HOST}/api/generate", json=llm_payload).json() + return {"reply": llm_res.get('response', 'Error generating response')} + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8080) +INNER_EOF + +cat << 'INNER_EOF' > src/agent/Dockerfile +FROM python:3.9-slim +WORKDIR /app +RUN pip install fastapi uvicorn pydantic requests +COPY app.py . +CMD ["python", "app.py"] +INNER_EOF + +echo "πŸ–₯️ 3/3: Writing Streamlit Web Frontend..." +cat << 'INNER_EOF' > src/frontend/app.py +import streamlit as st +import requests +import os + +AGENT_API_URL = os.getenv("AGENT_HOST", "http://agent-service.kaos-system.svc.cluster.local:8080/chat") +st.title("☸️ KAOS Web Interface") + +if "messages" not in st.session_state: + st.session_state.messages = [] + +for msg in st.session_state.messages: + with st.chat_message(msg["role"]): + st.markdown(msg["content"]) + +if prompt := st.chat_input("Ask the KAOS swarm..."): + st.chat_message("user").markdown(prompt) + st.session_state.messages.append({"role": "user", "content": prompt}) + with st.spinner("Agent is thinking..."): + try: + res = requests.post(AGENT_API_URL, json={"message": prompt}) + reply = res.json().get("reply", "No response from agent.") + except Exception as e: + reply = f"Error connecting to agent: {e}" + st.chat_message("assistant").markdown(reply) + st.session_state.messages.append({"role": "assistant", "content": reply}) +INNER_EOF + +cat << 'INNER_EOF' > src/frontend/Dockerfile +FROM python:3.9-slim +WORKDIR /app +RUN pip install streamlit requests +COPY app.py . +CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"] +INNER_EOF + +echo "=======================================================" +echo "🐳 Phase 3: Building & Injecting Images..." +echo "=======================================================" +sudo -E docker build -t kaos-mcp:latest ./src/mcp +sudo -E docker build -t kaos-agent:latest ./src/agent +sudo -E docker build -t kaos-frontend:latest ./src/frontend + +minikube image load kaos-mcp:latest +minikube image load kaos-agent:latest +minikube image load kaos-frontend:latest + +echo "=======================================================" +echo "☸️ Phase 4: Deploying Kubernetes Manifests..." +echo "=======================================================" +cat << 'INNER_EOF' > manifests/00-namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: kaos-system +INNER_EOF + +cat << 'INNER_EOF' > manifests/01-llm-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaos-llm + namespace: kaos-system +spec: + replicas: 1 + selector: + matchLabels: + app: kaos-llm + template: + metadata: + labels: + app: kaos-llm + spec: + containers: + - name: ollama + image: ollama/ollama:latest + ports: + - containerPort: 11434 +--- +apiVersion: v1 +kind: Service +metadata: + name: llm-service + namespace: kaos-system +spec: + selector: + app: kaos-llm + ports: + - protocol: TCP + port: 11434 + targetPort: 11434 +INNER_EOF + +cat << 'INNER_EOF' > manifests/02-mcp-server.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaos-mcp + namespace: kaos-system +spec: + replicas: 1 + selector: + matchLabels: + app: kaos-mcp + template: + metadata: + labels: + app: kaos-mcp + spec: + containers: + - name: mcp-server + image: kaos-mcp:latest + imagePullPolicy: Never + ports: + - containerPort: 8000 +--- +apiVersion: v1 +kind: Service +metadata: + name: mcp-service + namespace: kaos-system +spec: + selector: + app: kaos-mcp + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 +INNER_EOF + +cat << 'INNER_EOF' > manifests/03-agent-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaos-agent + namespace: kaos-system +spec: + replicas: 1 + selector: + matchLabels: + app: kaos-agent + template: + metadata: + labels: + app: kaos-agent + spec: + containers: + - name: agent-engine + image: kaos-agent:latest + imagePullPolicy: Never + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: agent-service + namespace: kaos-system +spec: + selector: + app: kaos-agent + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 +INNER_EOF + +cat << 'INNER_EOF' > manifests/04-frontend.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaos-frontend + namespace: kaos-system +spec: + replicas: 1 + selector: + matchLabels: + app: kaos-frontend + template: + metadata: + labels: + app: kaos-frontend + spec: + containers: + - name: frontend + image: kaos-frontend:latest + imagePullPolicy: Never + ports: + - containerPort: 8501 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-service + namespace: kaos-system +spec: + type: NodePort + selector: + app: kaos-frontend + ports: + - protocol: TCP + port: 8501 + targetPort: 8501 +INNER_EOF + +kubectl apply -f manifests/ + +echo "=======================================================" +echo "⏳ Phase 5: Waiting for Infrastructure to Boot..." +echo "=======================================================" +kubectl wait --for=condition=ready pod -l app=kaos-llm -n kaos-system --timeout=300s +echo "🧠 Downloading the Qwen AI brain into the LLM pod..." +kubectl exec deployment/kaos-llm -n kaos-system -- ollama pull qwen2.5:0.5b + +echo "=======================================================" +echo "πŸŽ‰ SUCCESS! Your Full-Stack KAOS Swarm is ALIVE!" +echo "=======================================================" +echo "To open the Web UI in your browser, run this exact command:" +echo "" +echo " minikube service frontend-service -n kaos-system" +echo "" +echo "=======================================================" diff --git a/examples/start-agents/k8s-kaos/kaos_starter/manifests/00-namespace.yaml b/examples/start-agents/k8s-kaos/kaos_starter/manifests/00-namespace.yaml new file mode 100644 index 00000000..162197ab --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/manifests/00-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kaos-system diff --git a/examples/start-agents/k8s-kaos/kaos_starter/manifests/01-llm-deployment.yaml b/examples/start-agents/k8s-kaos/kaos_starter/manifests/01-llm-deployment.yaml new file mode 100644 index 00000000..bf10666e --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/manifests/01-llm-deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaos-llm + namespace: kaos-system +spec: + replicas: 1 + selector: + matchLabels: + app: kaos-llm + template: + metadata: + labels: + app: kaos-llm + spec: + containers: + - name: ollama + image: ollama/ollama:latest + ports: + - containerPort: 11434 +--- +apiVersion: v1 +kind: Service +metadata: + name: llm-service + namespace: kaos-system +spec: + selector: + app: kaos-llm + ports: + - protocol: TCP + port: 11434 + targetPort: 11434 diff --git a/examples/start-agents/k8s-kaos/kaos_starter/manifests/02-mcp-server.yaml b/examples/start-agents/k8s-kaos/kaos_starter/manifests/02-mcp-server.yaml new file mode 100644 index 00000000..a52de09d --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/manifests/02-mcp-server.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaos-mcp + namespace: kaos-system +spec: + replicas: 1 + selector: + matchLabels: + app: kaos-mcp + template: + metadata: + labels: + app: kaos-mcp + spec: + containers: + - name: mcp-server + image: kaos-mcp:latest + imagePullPolicy: Never + ports: + - containerPort: 8000 +--- +apiVersion: v1 +kind: Service +metadata: + name: mcp-service + namespace: kaos-system +spec: + selector: + app: kaos-mcp + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 diff --git a/examples/start-agents/k8s-kaos/kaos_starter/manifests/03-agent-deployment.yaml b/examples/start-agents/k8s-kaos/kaos_starter/manifests/03-agent-deployment.yaml new file mode 100644 index 00000000..cf8350bf --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/manifests/03-agent-deployment.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaos-agent + namespace: kaos-system +spec: + replicas: 1 + selector: + matchLabels: + app: kaos-agent + template: + metadata: + labels: + app: kaos-agent + spec: + containers: + - name: agent-engine + image: kaos-agent:latest + imagePullPolicy: Never + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: agent-service + namespace: kaos-system +spec: + selector: + app: kaos-agent + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 diff --git a/examples/start-agents/k8s-kaos/kaos_starter/manifests/04-frontend.yaml b/examples/start-agents/k8s-kaos/kaos_starter/manifests/04-frontend.yaml new file mode 100644 index 00000000..16b23ca0 --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/manifests/04-frontend.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaos-frontend + namespace: kaos-system +spec: + replicas: 1 + selector: + matchLabels: + app: kaos-frontend + template: + metadata: + labels: + app: kaos-frontend + spec: + containers: + - name: frontend + image: kaos-frontend:latest + imagePullPolicy: Never + ports: + - containerPort: 8501 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-service + namespace: kaos-system +spec: + type: NodePort + selector: + app: kaos-frontend + ports: + - protocol: TCP + port: 8501 + targetPort: 8501 diff --git a/examples/start-agents/k8s-kaos/kaos_starter/src/agent/Dockerfile b/examples/start-agents/k8s-kaos/kaos_starter/src/agent/Dockerfile new file mode 100644 index 00000000..da3efb83 --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/src/agent/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.9-slim +WORKDIR /app +RUN pip install fastapi uvicorn pydantic requests +COPY app.py . +CMD ["python", "app.py"] diff --git a/examples/start-agents/k8s-kaos/kaos_starter/src/agent/app.py b/examples/start-agents/k8s-kaos/kaos_starter/src/agent/app.py new file mode 100644 index 00000000..9e96e327 --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/src/agent/app.py @@ -0,0 +1,30 @@ +import os, requests +from fastapi import FastAPI +from pydantic import BaseModel + +LLM_HOST = os.getenv("LLM_HOST", "http://llm-service.kaos-system.svc.cluster.local:11434") +MCP_HOST = os.getenv("MCP_HOST", "http://mcp-service.kaos-system.svc.cluster.local:8000") + +app = FastAPI() + +class ChatRequest(BaseModel): + message: str + +@app.post("/chat") +def chat_endpoint(req: ChatRequest): + print(f"πŸ“₯ Received: {req.message}", flush=True) + try: + mcp_res = requests.get(f"{MCP_HOST}/tools/calculate").json() + tool_data = mcp_res.get("result", "No data") + except Exception: + tool_data = "Tool offline." + + dynamic_prompt = f"System Data: '{tool_data}'. User asks: '{req.message}'. Provide a helpful response." + llm_payload = {"model": "qwen2.5:0.5b", "prompt": dynamic_prompt, "stream": False} + + llm_res = requests.post(f"{LLM_HOST}/api/generate", json=llm_payload).json() + return {"reply": llm_res.get('response', 'Error generating response')} + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8080) diff --git a/examples/start-agents/k8s-kaos/kaos_starter/src/frontend/Dockerfile b/examples/start-agents/k8s-kaos/kaos_starter/src/frontend/Dockerfile new file mode 100644 index 00000000..02ad696d --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/src/frontend/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.9-slim +WORKDIR /app +RUN pip install streamlit requests +COPY app.py . +CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"] diff --git a/examples/start-agents/k8s-kaos/kaos_starter/src/frontend/app.py b/examples/start-agents/k8s-kaos/kaos_starter/src/frontend/app.py new file mode 100644 index 00000000..1e12f87e --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/src/frontend/app.py @@ -0,0 +1,25 @@ +import streamlit as st +import requests +import os + +AGENT_API_URL = os.getenv("AGENT_HOST", "http://agent-service.kaos-system.svc.cluster.local:8080/chat") +st.title("☸️ KAOS Web Interface") + +if "messages" not in st.session_state: + st.session_state.messages = [] + +for msg in st.session_state.messages: + with st.chat_message(msg["role"]): + st.markdown(msg["content"]) + +if prompt := st.chat_input("Ask the KAOS swarm..."): + st.chat_message("user").markdown(prompt) + st.session_state.messages.append({"role": "user", "content": prompt}) + with st.spinner("Agent is thinking..."): + try: + res = requests.post(AGENT_API_URL, json={"message": prompt}) + reply = res.json().get("reply", "No response from agent.") + except Exception as e: + reply = f"Error connecting to agent: {e}" + st.chat_message("assistant").markdown(reply) + st.session_state.messages.append({"role": "assistant", "content": reply}) diff --git a/examples/start-agents/k8s-kaos/kaos_starter/src/mcp/Dockerfile b/examples/start-agents/k8s-kaos/kaos_starter/src/mcp/Dockerfile new file mode 100644 index 00000000..c07e5ac6 --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/src/mcp/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.9-slim +WORKDIR /app +RUN pip install fastapi uvicorn +COPY server.py . +CMD ["python", "server.py"] diff --git a/examples/start-agents/k8s-kaos/kaos_starter/src/mcp/server.py b/examples/start-agents/k8s-kaos/kaos_starter/src/mcp/server.py new file mode 100644 index 00000000..47bc7f06 --- /dev/null +++ b/examples/start-agents/k8s-kaos/kaos_starter/src/mcp/server.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI +import uvicorn +app = FastAPI() +@app.get("/tools/calculate") +def calculate(): + return {"tool": "calculator", "result": "The Kubernetes cluster is active and routing web traffic!"} +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/examples/start-agents/k8s-kaos/teardown.sh b/examples/start-agents/k8s-kaos/teardown.sh new file mode 100644 index 00000000..5799a65b --- /dev/null +++ b/examples/start-agents/k8s-kaos/teardown.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +echo "πŸ›‘ Destroying KAOS Kubernetes namespace..." +# This deletes all pods, deployments, and services safely +kubectl delete namespace kaos-system + +echo "πŸ’€ Stopping Minikube cluster..." +# This spins down the virtualized Kubernetes environment +minikube stop + +echo "🧹 Clean up complete! System RAM and CPU freed." From 6396133b268073bca5172614dded4da3a2861481 Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Sun, 22 Mar 2026 12:41:10 -0400 Subject: [PATCH 04/16] Interactive UI for web & finance agents --- examples/start-agents/agno_ui/.env.example | 1 + examples/start-agents/agno_ui/README.md | 98 +++++++++++++++++++ examples/start-agents/agno_ui/app.py | 92 +++++++++++++++++ .../start-agents/agno_ui/requirements.txt | 6 ++ 4 files changed, 197 insertions(+) create mode 100644 examples/start-agents/agno_ui/.env.example create mode 100644 examples/start-agents/agno_ui/README.md create mode 100644 examples/start-agents/agno_ui/app.py create mode 100644 examples/start-agents/agno_ui/requirements.txt diff --git a/examples/start-agents/agno_ui/.env.example b/examples/start-agents/agno_ui/.env.example new file mode 100644 index 00000000..baa949d1 --- /dev/null +++ b/examples/start-agents/agno_ui/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY="sk-..." diff --git a/examples/start-agents/agno_ui/README.md b/examples/start-agents/agno_ui/README.md new file mode 100644 index 00000000..4a2b44c2 --- /dev/null +++ b/examples/start-agents/agno_ui/README.md @@ -0,0 +1,98 @@ +# πŸ“ˆ Agno UI (Finance & Web Agent Dashboard) + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Web Application | **Tech Stack:** Agno (v2.5+), Streamlit, Yahoo Finance, DuckDuckGo + +

+ Saturn Cloud + Agno + Streamlit + YFinance +

+ +## πŸ“– Overview + +This template provides a production-grade interactive Web UI for financial and research agents. It utilizes **Agno** to seamlessly bind Large Language Models to external tool APIs, and wraps the logic in a dynamic **Streamlit** dashboard. + +### 🧩 The Agent Dashboard +The application features a sidebar that allows users to seamlessly switch between different specialized AI modes: +1. **🌐 Web Researcher:** Queries DuckDuckGo for live internet news and context. +2. **πŸ“ˆ Finance Analyst:** Queries the Yahoo Finance API for live ticker prices, company fundamentals, and analyst ratings. +3. **πŸ‘” Finance Swarm:** A multi-agent `Team` that coordinates the web and finance agents to synthesize comprehensive investment reports. + +--- + +## πŸ—οΈ Local Setup & Installation + +This web application runs entirely within a standard Python virtual environment, requiring no heavy containerization for local development. + +**1. Create the Environment & Install Dependencies** +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +--- + +## πŸ” Environment Configuration + +Agno securely loads your API keys via the `python-dotenv` library. + +**1. Create your `.env` file:** +```bash +cp .env.example .env +``` + +**2. Add your Provider Keys:** +Open the `.env` file and insert your active API key. +```text +OPENAI_API_KEY="sk-your-actual-api-key-goes-here" +``` + +--- + +## πŸš€ Execution & Testing + +Launch the interactive Streamlit UI directly from your terminal: + +```bash +streamlit run app.py +``` + +**πŸ§ͺ Recommended Testing Flow:** +Once the UI opens in your browser (default `http://localhost:8501`), use the sidebar to switch between agents and paste these exact prompts to verify their tool connectivity: + +**1. Test the Web Researcher** +> *"What are the top 3 biggest AI or tech news stories from this week? Please summarize each one briefly and cite the source."* +*(Verifies the DuckDuckGo standard web search API is connected and formatting properly).* + +**2. Test the Finance Analyst** +> *"Pull the current stock price, market cap, and the latest analyst consensus recommendation for Tesla (TSLA)."* +*(Verifies the Yahoo Finance API is successfully pulling live market data and analyst ratings).* + +**3. Test the Finance Swarm (The Ultimate Test)** +> *"Write a comprehensive investment report on NVIDIA (NVDA). I need you to pull the latest stock data and analyst recommendations, then search the web for recent news about their upcoming AI chips. Synthesize everything into a final verdict."* +*(Verifies multi-agent orchestration. Watch as the Swarm Leader delegates tasks to the Analyst and Researcher before writing the final report).* + +--- + +## ☁️ Cloud Deployment + +To deploy this multi-agent web application to production, you can provision a resource on [Saturn Cloud](https://saturncloud.io/). + +**Deployment Specifications:** + +1. **Resource Type:** Streamlit Deployment / Python Server. +2. **Hardware:** CPU instance (financial APIs and web searches are extremely lightweight). +3. **Environment Variables:** Inject your `OPENAI_API_KEY` directly into the Saturn Cloud secrets manager (do not commit your `.env` file to version control). +4. **Start Command:** `streamlit run app.py --server.port 8000 --server.address 0.0.0.0` + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Agent Framework:** [Agno Official Docs](https://docs.agno.com/) +* **UI Framework:** [Streamlit Docs](https://docs.streamlit.io/) diff --git a/examples/start-agents/agno_ui/app.py b/examples/start-agents/agno_ui/app.py new file mode 100644 index 00000000..03d7587d --- /dev/null +++ b/examples/start-agents/agno_ui/app.py @@ -0,0 +1,92 @@ +import streamlit as st +from dotenv import load_dotenv +from agno.agent import Agent +from agno.team import Team +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.tools.yfinance import YFinanceTools + +# Load API keys +load_dotenv() + +st.set_page_config(page_title="Agno Agent UI", page_icon="πŸ“ˆ", layout="wide") + +# --- UI Sidebar --- +st.sidebar.title("πŸ€– Agent Selector") +st.sidebar.markdown("Choose which AI agent you want to interact with:") +agent_type = st.sidebar.radio( + "Active Agent:", + ["🌐 Web Researcher", "πŸ“ˆ Finance Analyst", "πŸ‘” Finance Swarm"] +) + +# --- Agent Initialization --- +@st.cache_resource +def get_agent(choice): + # πŸ›‘οΈ HARDENED TOOLS: + # 1. Disable DDG's flaky 'news' endpoint using the correct 'enable_' syntax + safe_web_tools = [DuckDuckGoTools(enable_search=True, enable_news=False)] + # 2. Ensure all finance tools are explicitly enabled + safe_finance_tools = [YFinanceTools(enable_stock_price=True, enable_analyst_recommendations=True, enable_company_info=True)] + + if choice == "🌐 Web Researcher": + return Agent( + name="Web Researcher", + role="Search the web for real-time information.", + tools=safe_web_tools, + markdown=True + ) + elif choice == "πŸ“ˆ Finance Analyst": + return Agent( + name="Finance Analyst", + role="Analyze live stock data, fundamentals, and analyst recommendations.", + tools=safe_finance_tools, + markdown=True + ) + else: + # The Swarm Team + web = Agent(name="Web Researcher", tools=safe_web_tools, markdown=True) + finance = Agent(name="Finance Analyst", tools=safe_finance_tools, markdown=True) + return Team( + name="Finance Swarm", + members=[web, finance], + instructions=[ + "1. Ask the Finance Analyst to pull the company's live stock data and recommendations.", + "2. Ask the Web Researcher to find the latest public news about the company.", + "3. Synthesize both into a comprehensive investment report." + ], + markdown=True + ) + +# Get the currently selected agent +active_agent = get_agent(choice=agent_type) + +# --- Chat Interface --- +st.title(f"{active_agent.name} πŸ’¬") + +# Provide fallback description if the active agent is a Team +agent_role = getattr(active_agent, "role", "Orchestrate the Web and Finance agents to write a comprehensive report.") +st.markdown(f"**Role:** {agent_role}") + +if "messages" not in st.session_state: + st.session_state.messages = [] + +# Clear chat button +if st.sidebar.button("πŸ—‘οΈ Clear Chat History"): + st.session_state.messages = [] + st.rerun() + +# Render history +for msg in st.session_state.messages: + with st.chat_message(msg["role"]): + st.markdown(msg["content"]) + +# Handle user input +if prompt := st.chat_input("E.g., Analyze NVDA's recent performance..."): + st.chat_message("user").markdown(prompt) + st.session_state.messages.append({"role": "user", "content": prompt}) + + with st.chat_message("assistant"): + with st.spinner(f"{active_agent.name} is working..."): + response = active_agent.run(prompt) + st.markdown(response.content) + + st.session_state.messages.append({"role": "assistant", "content": response.content}) diff --git a/examples/start-agents/agno_ui/requirements.txt b/examples/start-agents/agno_ui/requirements.txt new file mode 100644 index 00000000..2e736c34 --- /dev/null +++ b/examples/start-agents/agno_ui/requirements.txt @@ -0,0 +1,6 @@ +agno +openai +streamlit +yfinance +ddgs +python-dotenv From 14f5c0e23f496d046592ddc41db153cbb5867a95 Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Wed, 25 Mar 2026 03:41:59 -0400 Subject: [PATCH 05/16] Chat interface for Nebius Token Factory --- .../start-agents/nebius_chat/.env.example | 1 + examples/start-agents/nebius_chat/README.md | 95 +++++++++++++++++++ examples/start-agents/nebius_chat/app.py | 84 ++++++++++++++++ .../start-agents/nebius_chat/nebius_chat.py | 76 +++++++++++++++ .../start-agents/nebius_chat/requirements.txt | 3 + 5 files changed, 259 insertions(+) create mode 100644 examples/start-agents/nebius_chat/.env.example create mode 100644 examples/start-agents/nebius_chat/README.md create mode 100644 examples/start-agents/nebius_chat/app.py create mode 100644 examples/start-agents/nebius_chat/nebius_chat.py create mode 100644 examples/start-agents/nebius_chat/requirements.txt diff --git a/examples/start-agents/nebius_chat/.env.example b/examples/start-agents/nebius_chat/.env.example new file mode 100644 index 00000000..995fcb00 --- /dev/null +++ b/examples/start-agents/nebius_chat/.env.example @@ -0,0 +1 @@ +NEBIUS_API_KEY="your-nebius-token-factory-key-here" \ No newline at end of file diff --git a/examples/start-agents/nebius_chat/README.md b/examples/start-agents/nebius_chat/README.md new file mode 100644 index 00000000..6983a9eb --- /dev/null +++ b/examples/start-agents/nebius_chat/README.md @@ -0,0 +1,95 @@ +# πŸ’¬ Nebius Chat (Token Factory Interface) + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Python Script & Web App | **Tech Stack:** Nebius AI, OpenAI SDK, Streamlit + +

+ Saturn Cloud + Nebius + Streamlit + Python +

+ +## πŸ“– Overview + +This template provides a production-ready, streaming chat interface for the **Nebius Token Factory** (Nebius AI Studio). + +Because Nebius provides an OpenAI-compatible API endpoint, this repository demonstrates how to seamlessly drop high-performance, open-source models (like Llama 3.3, Qwen, or DeepSeek) into existing OpenAI workflows simply by overriding the `base_url`. + +### 🧩 Key Features +1. **Interactive Web UI:** A full Streamlit dashboard for a ChatGPT-like experience. +2. **Model Hot-Swapping:** Use the UI sidebar to instantly switch between Llama, DeepSeek, and Qwen without changing code. +3. **Contextual Memory:** Automatically maintains message history, allowing the models to remember prior questions and handle follow-up queries contextually. + +--- + +## πŸ—οΈ Local Setup & Installation + +**1. Create the Environment & Install Dependencies** +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +--- + +## πŸ” Environment Configuration + +**1. Create your `.env` file:** +```bash +cp .env.example .env +``` + +**2. Add your Provider Keys:** +Open the `.env` file and insert your active Nebius Token Factory API key. *(You can generate a free key at [studio.nebius.ai](https://studio.nebius.ai/))*. +```text +NEBIUS_API_KEY="your-actual-api-key-goes-here" +``` + +--- + +## πŸš€ Execution & Testing + +This repository is designed progressively. You can interact with Nebius via the terminal, or launch the full production dashboard. + +### Phase 1: The Terminal CLI +Launch the lightweight, streaming terminal interface: +```bash +python nebius_chat.py +``` + +### Phase 2: The Streamlit Web Dashboard +Launch the interactive web UI to chat and hot-swap models: +```bash +streamlit run app.py +``` + +**πŸ§ͺ Recommended Testing Flow (Testing Memory):** +Once the UI boots up, test its contextual awareness by asking a multi-part question: +1. **Prompt 1:** *"I am traveling to Tokyo next week. What are three must-see neighborhoods?"* +2. **Prompt 2:** *"Which of those three is best for finding vintage electronics?"* +*(If the history array is working correctly, it will remember the three neighborhoods it just suggested and single out Akihabara without you needing to repeat yourself).* + +--- + +## ☁️ Cloud Deployment + +To deploy this multi-agent web application to production, you can provision a resource on [Saturn Cloud](https://saturncloud.io/). + +**Deployment Specifications:** + +1. **Resource Type:** Streamlit Deployment / Python Server. +2. **Hardware:** CPU instance (since the heavy inference processing is completely offloaded to Nebius's high-speed GPU endpoints). +3. **Environment Variables:** Inject your `NEBIUS_API_KEY` directly into the Saturn Cloud secrets manager (do not commit your `.env` file to version control). +4. **Start Command:** `streamlit run app.py --server.port 8000 --server.address 0.0.0.0` + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Model Provider:** [Nebius Token Factory Docs](https://docs.tokenfactory.nebius.com/) +* **SDK Reference:** [OpenAI Python Library](https://github.com/openai/openai-python) +* **UI Framework:** [Streamlit Docs](https://docs.streamlit.io/) diff --git a/examples/start-agents/nebius_chat/app.py b/examples/start-agents/nebius_chat/app.py new file mode 100644 index 00000000..1d262d2f --- /dev/null +++ b/examples/start-agents/nebius_chat/app.py @@ -0,0 +1,84 @@ +import os +import streamlit as st +from dotenv import load_dotenv +from openai import OpenAI + +# Load environment variables +load_dotenv() + +st.set_page_config(page_title="Nebius Chat UI", page_icon="πŸ’¬", layout="wide") + +# Ensure API key is present +api_key = os.getenv("NEBIUS_API_KEY") +if not api_key: + st.error("❌ Error: NEBIUS_API_KEY not found in .env file.") + st.stop() + +# Initialize the OpenAI client pointing to Nebius Token Factory +@st.cache_resource +def get_client(): + return OpenAI( + base_url="https://api.studio.nebius.ai/v1/", + api_key=api_key, + ) + +client = get_client() + +# --- UI Sidebar --- +st.sidebar.title("βš™οΈ Settings") +st.sidebar.markdown("Swap between open-source models instantly!") +model_name = st.sidebar.selectbox( + "Active Model:", + [ + "meta-llama/Llama-3.3-70B-Instruct", + "deepseek-ai/DeepSeek-V3.2", + "Qwen/Qwen3-Coder-480B-A35B-Instruct" + ] +) + +if st.sidebar.button("πŸ—‘οΈ Clear Chat History"): + st.session_state.messages = [ + {"role": "system", "content": "You are a helpful, highly intelligent AI assistant powered by Nebius Token Factory."} + ] + st.rerun() + +# --- Main Chat UI --- +st.title("πŸ’¬ Nebius Token Factory Dashboard") +st.markdown("A production-grade chat interface connected to Nebius AI Studio via the OpenAI SDK.") + +# Initialize chat history +if "messages" not in st.session_state: + st.session_state.messages = [ + {"role": "system", "content": "You are a helpful, highly intelligent AI assistant powered by Nebius Token Factory."} + ] + +# Display chat messages from history on app rerun (skipping the hidden system prompt) +for message in st.session_state.messages: + if message["role"] != "system": + with st.chat_message(message["role"]): + st.markdown(message["content"]) + +# Accept user input +if prompt := st.chat_input("E.g., Write a python script to reverse a string..."): + # Add user message to chat history + st.session_state.messages.append({"role": "user", "content": prompt}) + + # Display user message in chat message container + with st.chat_message("user"): + st.markdown(prompt) + + # Display assistant response in chat message container + with st.chat_message("assistant"): + # Stream the response from the Nebius API + stream = client.chat.completions.create( + model=model_name, + messages=st.session_state.messages, + stream=True, + temperature=0.7, + ) + + # Streamlit natively handles chunk streaming from OpenAI clients! + response = st.write_stream(stream) + + # Add assistant response to memory so it remembers context + st.session_state.messages.append({"role": "assistant", "content": response}) diff --git a/examples/start-agents/nebius_chat/nebius_chat.py b/examples/start-agents/nebius_chat/nebius_chat.py new file mode 100644 index 00000000..041b9057 --- /dev/null +++ b/examples/start-agents/nebius_chat/nebius_chat.py @@ -0,0 +1,76 @@ +import os +import sys +from dotenv import load_dotenv +from openai import OpenAI + +# Load environment variables +load_dotenv() + +# Ensure API key is present +api_key = os.getenv("NEBIUS_API_KEY") +if not api_key: + print("❌ Error: NEBIUS_API_KEY not found in .env file.") + sys.exit(1) + +# Initialize the OpenAI client pointing to Nebius Token Factory +client = OpenAI( + base_url="https://api.studio.nebius.ai/v1/", + api_key=api_key, +) + +# You can easily swap this out for "Qwen/Qwen2.5-72B", "deepseek-ai/DeepSeek-V3", etc. +MODEL_NAME = "meta-llama/Llama-3.3-70B-Instruct" + +print("==================================================") +print(f"πŸ’¬ Nebius Token Factory Chat Initialized") +print(f"🧠 Active Model: {MODEL_NAME}") +print("πŸ’‘ Type 'exit' or 'quit' to end the conversation.") +print("==================================================\n") + +# Store conversation history for contextual awareness +messages = [ + {"role": "system", "content": "You are a helpful, highly intelligent AI assistant powered by Nebius Token Factory. Provide clear, concise, and accurate answers."} +] + +while True: + try: + user_input = input("\nπŸ§‘ You: ") + if user_input.lower() in ['exit', 'quit']: + print("πŸ‘‹ Ending session. Goodbye!") + break + if not user_input.strip(): + continue + + # Add user message to memory + messages.append({"role": "user", "content": user_input}) + + print("πŸ€– Nebius: ", end="", flush=True) + + # Stream the response from the Nebius API + response = client.chat.completions.create( + model=MODEL_NAME, + messages=messages, + temperature=0.7, + stream=True + ) + + assistant_reply = "" + for chunk in response: + if chunk.choices and chunk.choices[0].delta.content is not None: + text = chunk.choices[0].delta.content + print(text, end="", flush=True) + assistant_reply += text + + print() # Add a newline after the streaming completes + + # Add assistant response to memory so it remembers context + messages.append({"role": "assistant", "content": assistant_reply}) + + except KeyboardInterrupt: + print("\nπŸ‘‹ Session interrupted. Goodbye!") + break + except Exception as e: + print(f"\n❌ An error occurred: {e}") + # Remove the last user message if the API call failed so we don't corrupt history + if messages and messages[-1]["role"] == "user": + messages.pop() \ No newline at end of file diff --git a/examples/start-agents/nebius_chat/requirements.txt b/examples/start-agents/nebius_chat/requirements.txt new file mode 100644 index 00000000..6a6b1658 --- /dev/null +++ b/examples/start-agents/nebius_chat/requirements.txt @@ -0,0 +1,3 @@ +openai +python-dotenv +streamlit \ No newline at end of file From 71ba9068cf41a227cfd06b151c741397e2f46bfe Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Wed, 25 Mar 2026 17:53:38 -0400 Subject: [PATCH 06/16] Intelligent model routing with RouteLLM \(GPT-4o-mini vs Nebius Llama\) for cost optimization --- .../start-agents/routellm_chat/.env.example | 2 + examples/start-agents/routellm_chat/README.md | 0 examples/start-agents/routellm_chat/app.py | 88 +++++++++++++++++++ .../routellm_chat/requirements.txt | 4 + .../routellm_chat/routellm_chat.py | 72 +++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 examples/start-agents/routellm_chat/.env.example create mode 100644 examples/start-agents/routellm_chat/README.md create mode 100644 examples/start-agents/routellm_chat/app.py create mode 100644 examples/start-agents/routellm_chat/requirements.txt create mode 100644 examples/start-agents/routellm_chat/routellm_chat.py diff --git a/examples/start-agents/routellm_chat/.env.example b/examples/start-agents/routellm_chat/.env.example new file mode 100644 index 00000000..99155811 --- /dev/null +++ b/examples/start-agents/routellm_chat/.env.example @@ -0,0 +1,2 @@ +OPENAI_API_KEY="sk-..." +NEBIUS_API_KEY="your-nebius-token-factory-key-here" \ No newline at end of file diff --git a/examples/start-agents/routellm_chat/README.md b/examples/start-agents/routellm_chat/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/start-agents/routellm_chat/app.py b/examples/start-agents/routellm_chat/app.py new file mode 100644 index 00000000..f6a8c3e8 --- /dev/null +++ b/examples/start-agents/routellm_chat/app.py @@ -0,0 +1,88 @@ +import os +import streamlit as st +from dotenv import load_dotenv +from routellm.controller import Controller + +# Load environment variables +load_dotenv() + +st.set_page_config(page_title="RouteLLM Dashboard", page_icon="🚦", layout="wide") + +# Verify keys +openai_key = os.getenv("OPENAI_API_KEY") +nebius_key = os.getenv("NEBIUS_API_KEY") + +if not openai_key or not nebius_key: + st.error("❌ Error: Both OPENAI_API_KEY and NEBIUS_API_KEY must be set in your .env file.") + st.stop() + +# πŸ› οΈ The LiteLLM Routing Trick: Map Nebius to the hosted_vllm prefix +os.environ["HOSTED_VLLM_API_KEY"] = nebius_key +os.environ["HOSTED_VLLM_API_BASE"] = "https://api.studio.nebius.ai/v1" + +# Initialize the RouteLLM Controller +@st.cache_resource +def get_router(): + return Controller( + routers=["mf"], + strong_model="hosted_vllm/meta-llama/Llama-3.3-70B-Instruct", + weak_model="gpt-4o-mini", + ) + +client = get_router() +ROUTER_MODEL = "router-mf-0.11593" + +# --- UI Sidebar --- +st.sidebar.title("🚦 Intelligent Router") +st.sidebar.markdown("**Strong Model:** Llama-3.3-70B (Nebius)") +st.sidebar.markdown("**Weak Model:** GPT-4o-mini (OpenAI)") +st.sidebar.info("RouteLLM dynamically analyzes your prompt's complexity to route trivial questions to the cheap model, and complex questions to the strong model!") + +if st.sidebar.button("πŸ—‘οΈ Clear Chat History"): + st.session_state.messages = [] + st.rerun() + +# --- Main Chat UI --- +st.title("🚦 RouteLLM Intelligent Dashboard") + +if "messages" not in st.session_state: + st.session_state.messages = [] + +# Render chat history +for msg in st.session_state.messages: + with st.chat_message(msg["role"]): + if "model" in msg: + st.caption(f"✨ *Routed to: {msg['model']}*") + st.markdown(msg["content"]) + +# Handle user input +if prompt := st.chat_input("E.g., What is 2+2? vs Write a complex C memory allocator..."): + # Show user message + st.session_state.messages.append({"role": "user", "content": prompt}) + with st.chat_message("user"): + st.markdown(prompt) + + # Generate assistant response + with st.chat_message("assistant"): + with st.spinner("Analyzing complexity and routing query..."): + # RouteLLM handles the decision logic internally + response = client.chat.completions.create( + model=ROUTER_MODEL, + messages=[{"role": "user", "content": prompt}] + ) + + model_used = response.model + content = response.choices[0].message.content + + # Clean up the model name for a better UI display + display_model = "Llama-3.3-70B (Nebius)" if "llama" in model_used.lower() else "GPT-4o-mini (OpenAI)" + + st.caption(f"✨ *Routed to: **{display_model}***") + st.markdown(content) + + # Save to history + st.session_state.messages.append({ + "role": "assistant", + "content": content, + "model": display_model + }) diff --git a/examples/start-agents/routellm_chat/requirements.txt b/examples/start-agents/routellm_chat/requirements.txt new file mode 100644 index 00000000..35afcf26 --- /dev/null +++ b/examples/start-agents/routellm_chat/requirements.txt @@ -0,0 +1,4 @@ +routellm +python-dotenv +openai +streamlit \ No newline at end of file diff --git a/examples/start-agents/routellm_chat/routellm_chat.py b/examples/start-agents/routellm_chat/routellm_chat.py new file mode 100644 index 00000000..41709571 --- /dev/null +++ b/examples/start-agents/routellm_chat/routellm_chat.py @@ -0,0 +1,72 @@ +import os +import sys +from dotenv import load_dotenv +from routellm.controller import Controller + +# Load environment variables +load_dotenv() + +# Verify keys +openai_key = os.getenv("OPENAI_API_KEY") +nebius_key = os.getenv("NEBIUS_API_KEY") + +if not openai_key or not nebius_key: + print("❌ Error: Both OPENAI_API_KEY and NEBIUS_API_KEY must be set in your .env file.") + sys.exit(1) + +# πŸ› οΈ The LiteLLM Routing Trick: +# Since both GPT-4o-mini and Nebius Llama use the OpenAI SDK format, we map Nebius +# to the 'hosted_vllm' provider prefix. This prevents the API Base URLs from colliding! +os.environ["HOSTED_VLLM_API_KEY"] = nebius_key +os.environ["HOSTED_VLLM_API_BASE"] = "https://api.studio.nebius.ai/v1" + +# Initialize the RouteLLM Controller +# We use the Matrix Factorization (mf) router, trained on Chatbot Arena preference data +client = Controller( + routers=["mf"], + strong_model="hosted_vllm/meta-llama/Llama-3.3-70B-Instruct", + weak_model="gpt-4o-mini", +) + +print("==================================================") +print("🚦 RouteLLM Intelligent Router Initialized") +print("πŸ’ͺ Strong Model: Nebius Llama 3.3 70B") +print("πŸƒ Weak Model: GPT-4o-mini") +print("πŸ’‘ Type 'exit' to quit.") +print("==================================================\n") + +# Use a calibrated cost threshold of 0.11593 +# (This routes approximately 50% strong / 50% weak based on public benchmarks) +ROUTER_MODEL = "router-mf-0.11593" + +while True: + try: + user_input = input("\nπŸ§‘ You: ") + if user_input.lower() in ['exit', 'quit']: + print("πŸ‘‹ Ending session. Goodbye!") + break + if not user_input.strip(): + continue + + print("🚦 Routing query... ", end="", flush=True) + + # RouteLLM automatically decides which model to use based on prompt complexity + response = client.chat.completions.create( + model=ROUTER_MODEL, + messages=[{"role": "user", "content": user_input}] + ) + + model_used = response.model + content = response.choices[0].message.content + + # Clean up the model name for a better UI display + display_model = "Llama-3.3-70B (Nebius)" if "llama" in model_used.lower() else "GPT-4o-mini (OpenAI)" + + print(f"✨ [Routed to: {display_model}]") + print(f"πŸ€– {content}") + + except KeyboardInterrupt: + print("\nπŸ‘‹ Session interrupted. Goodbye!") + break + except Exception as e: + print(f"\n❌ An error occurred: {e}") \ No newline at end of file From a32821b37e6d669586b44df544aac75ad6c980e3 Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Wed, 25 Mar 2026 18:12:46 -0400 Subject: [PATCH 07/16] Intelligent model routing with RouteLLM \(GPT-4o-mini vs Nebius Llama\) for cost optimization --- examples/start-agents/routellm_chat/README.md | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/examples/start-agents/routellm_chat/README.md b/examples/start-agents/routellm_chat/README.md index e69de29b..0b87d5c9 100644 --- a/examples/start-agents/routellm_chat/README.md +++ b/examples/start-agents/routellm_chat/README.md @@ -0,0 +1,97 @@ +# 🚦 RouteLLM Chat (Intelligent Model Routing) + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Python Script & Web App | **Tech Stack:** RouteLLM, Nebius AI, OpenAI, Streamlit + +

+ Saturn Cloud + RouteLLM + Streamlit +

+ +## πŸ“– Overview + +This template demonstrates how to implement intelligent model routing using **RouteLLM** to dramatically optimize enterprise inference costs. + +Instead of sending every user query to a massive, expensive model, this architecture uses a highly calibrated router to analyze the complexity of an incoming prompt. It routes trivial requests to the fast, low-cost **GPT-4o-mini**, while seamlessly routing complex reasoning tasks to the heavy-weight **Nebius Llama 3.3 70B**. + +### 🧩 Key Features +1. **Matrix Factorization (MF) Router:** Utilizes the lightweight `mf` routing model trained on Chatbot Arena preference data. +2. **Cost-to-Quality Optimization:** Sets a specific routing threshold (`0.11593`) to optimally balance API costs against response intelligence. +3. **Multi-Provider Workaround:** Implements a LiteLLM workaround (`hosted_vllm`) to allow two distinct OpenAI-compatible base URLs to operate simultaneously within the same application. + +--- + +## πŸ—οΈ Local Setup & Installation + +**1. Create the Environment & Install Dependencies** +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +--- + +## πŸ” Environment Configuration + +**1. Create your `.env` file:** +```bash +cp .env.example .env +``` + +**2. Add your Provider Keys:** +Open the `.env` file and insert both of your active API keys. +```text +OPENAI_API_KEY="sk-your-openai-key" +NEBIUS_API_KEY="your-nebius-token-factory-key" +``` + +--- + +## πŸš€ Execution & Testing + +This repository includes both a CLI terminal script and a full interactive Web Dashboard. + +### Phase 1: The Terminal CLI +Launch the lightweight routing interface in your terminal: +```bash +python routellm_chat.py +``` + +### Phase 2: The Streamlit Web Dashboard +Launch the interactive web UI to visually see the routing decisions: +```bash +streamlit run app.py +``` + +**πŸ§ͺ Recommended Testing Flow:** +Try giving the router a trivial task versus a highly complex reasoning task to see how it dynamically switches the target model in real time! + +1. **The Simple Test (Should route to GPT-4o-mini):** +> *"What is the capital of France?"* + +2. **The Complex Test (Should route to Llama 3.3 70B):** +> *"Write a Python script that implements a custom memory allocator in C using `mmap`, and explain the pointer arithmetic involved."* + +--- + +## ☁️ Cloud Deployment + +To deploy this intelligent routing gateway to production as a Web App on [Saturn Cloud](https://saturncloud.io/): + +**Deployment Specifications:** + +1. **Resource Type:** Streamlit Deployment / Python Server. +2. **Hardware:** CPU instance. +3. **Environment Variables:** Inject your `OPENAI_API_KEY` and `NEBIUS_API_KEY` directly into the Saturn Cloud secrets manager. +4. **Start Command:** `streamlit run app.py --server.port 8000 --server.address 0.0.0.0` + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Router Framework:** [RouteLLM GitHub](https://github.com/lm-sys/RouteLLM) +* **UI Framework:** [Streamlit Docs](https://docs.streamlit.io/) \ No newline at end of file From 5e6351800f38c4cb71d24912c1a1572aa05e3d59 Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Wed, 25 Mar 2026 20:55:31 -0400 Subject: [PATCH 08/16] Multi-agent conversation framework for collaborative coding and local code execution. --- .../microsoft_autogen/.env.example | 1 + .../start-agents/microsoft_autogen/README.md | 98 +++++++++++++++++++ .../start-agents/microsoft_autogen/app.py | 67 +++++++++++++ .../microsoft_autogen/autogen_cli.py | 55 +++++++++++ .../microsoft_autogen/requirements.txt | 8 ++ 5 files changed, 229 insertions(+) create mode 100644 examples/start-agents/microsoft_autogen/.env.example create mode 100644 examples/start-agents/microsoft_autogen/README.md create mode 100644 examples/start-agents/microsoft_autogen/app.py create mode 100644 examples/start-agents/microsoft_autogen/autogen_cli.py create mode 100644 examples/start-agents/microsoft_autogen/requirements.txt diff --git a/examples/start-agents/microsoft_autogen/.env.example b/examples/start-agents/microsoft_autogen/.env.example new file mode 100644 index 00000000..141ca06f --- /dev/null +++ b/examples/start-agents/microsoft_autogen/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY="sk-your-openai-key-goes-here" \ No newline at end of file diff --git a/examples/start-agents/microsoft_autogen/README.md b/examples/start-agents/microsoft_autogen/README.md new file mode 100644 index 00000000..816cf306 --- /dev/null +++ b/examples/start-agents/microsoft_autogen/README.md @@ -0,0 +1,98 @@ +# πŸ€– Microsoft AutoGen Starter + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Python Script & Web App | **Tech Stack:** AutoGen, Python, Streamlit + +

+ Saturn Cloud + AutoGen + Streamlit + Python +

+ +## πŸ“– Overview + +This template provides a production-ready framework for multi-agent conversations using **Microsoft AutoGen**. It demonstrates how to orchestrate autonomous AI agents that can collaborate, write code, and execute it locally on the host machine to solve complex tasks. + +### 🧩 Key Features +1. **The Assistant Agent:** Acts as the cognitive engine, generating Python code to fulfill custom user requests. +2. **The User Proxy Agent:** Acts as the execution engine, taking the Assistant's code, running it in a secure local environment, and feeding any terminal errors back to the Assistant for autonomous debugging. +3. **Local Code Execution:** Utilizes `LocalCommandLineCodeExecutor` to sandbox file creation, script execution, and artifact generation into a dedicated `/coding` directory. +4. **Dynamic Artifact Rendering:** The Streamlit UI automatically scans the workspace and renders any generated image files directly in the chat interface. + +--- + +## πŸ—οΈ Local Setup & Installation + +**1. Create the Environment & Install Dependencies** +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +--- + +## πŸ” Environment Configuration + +**1. Create your `.env` file:** +```bash +cp .env.example .env +``` + +**2. Add your Provider Keys:** +Open the `.env` file and insert your active API key. +```text +OPENAI_API_KEY="sk-your-openai-key-goes-here" +``` + +--- + +## πŸš€ Execution & Testing + +This repository includes both a CLI terminal script and a full interactive Web Dashboard. Both interfaces allow you to type custom requests for the agents to execute. + +### Phase 1: The Terminal CLI +Launch the lightweight terminal interface: +```bash +python autogen_cli.py +``` + +### Phase 2: The Streamlit Web Dashboard +Launch the visual interface to see the conversation history and generated artifacts directly in your browser: +```bash +streamlit run app.py +``` + +**πŸ§ͺ Recommended Testing Flow:** +Once the app is running, paste these prompts into the chat box to test the agents' coding and debugging capabilities: + +**1. The Data Visualization Test:** +> *"Fetch the Year-To-Date (YTD) stock prices for NVDA and MSFT using yfinance. Plot the prices on a single line chart using matplotlib. Save the plot to a file named 'tech_stocks.png'."* +*(Watch the agents fetch live data, handle missing dependencies if necessary, and render the chart in your browser!)* + +**2. The Algorithm & File System Test:** +> *"Write a Python script that calculates the first 50 Fibonacci numbers, saves them to a CSV file called 'fibonacci.csv', and then plots a line chart showing their exponential growth. Save the chart as a PNG."* +*(Verifies the agents can write custom mathematical algorithms, interact with your local file system, and dynamically save multiple file types).* + +--- + +## ☁️ Cloud Deployment + +To deploy this multi-agent workflow to production as a Web App on [Saturn Cloud](https://saturncloud.io/): + +**Deployment Specifications:** + +1. **Resource Type:** Streamlit Deployment / Python Server. +2. **Hardware:** CPU instance. +3. **Environment Variables:** Inject your `OPENAI_API_KEY` directly into the Saturn Cloud secrets manager (do not commit your `.env` file to version control). +4. **Start Command:** `streamlit run app.py --server.port 8000 --server.address 0.0.0.0` + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Agent Framework:** [AutoGen Official Docs](https://microsoft.github.io/autogen/) +* **UI Framework:** [Streamlit Docs](https://docs.streamlit.io/) diff --git a/examples/start-agents/microsoft_autogen/app.py b/examples/start-agents/microsoft_autogen/app.py new file mode 100644 index 00000000..9a1d5d9f --- /dev/null +++ b/examples/start-agents/microsoft_autogen/app.py @@ -0,0 +1,67 @@ +import os +import glob +import streamlit as st +from dotenv import load_dotenv +import autogen +from autogen.coding import LocalCommandLineCodeExecutor + +# Load environment variables +load_dotenv() + +st.set_page_config(page_title="AutoGen Workspace", page_icon="πŸ€–", layout="wide") + +st.title("πŸ€– AutoGen Collaborative Workspace") +st.markdown("Ask the AI Assistant to write code. The User Proxy Agent will autonomously execute and debug it locally!") + +if prompt := st.chat_input("E.g., Fetch TSLA stock prices for the last 30 days and plot them..."): + st.chat_message("user").markdown(prompt) + + api_key = os.getenv("OPENAI_API_KEY") + if not api_key: + st.error("❌ OPENAI_API_KEY not found in .env file.") + st.stop() + + work_dir = "coding" + os.makedirs(work_dir, exist_ok=True) + + llm_config = {"model": "gpt-4o-mini", "api_key": api_key} + + assistant = autogen.AssistantAgent( + name="Assistant", + llm_config=llm_config, + system_message="""You are a helpful AI assistant. You write Python code to solve tasks. +Always wrap your code in standard markdown code blocks. +The user proxy will execute your code and report the output back to you. +DO NOT output 'TERMINATE' in the same message where you write code. +WAIT for the user proxy to run the code and confirm it worked. +ONLY output 'TERMINATE' when the user proxy confirms the execution was successful and the task is fully complete.""", + ) + + executor = LocalCommandLineCodeExecutor(timeout=60, work_dir=work_dir) + user_proxy = autogen.UserProxyAgent( + name="UserProxy", + human_input_mode="NEVER", + max_consecutive_auto_reply=10, + is_termination_msg=lambda x: x.get("content", "") and x.get("content", "").rstrip().endswith("TERMINATE"), + code_execution_config={"executor": executor}, + ) + + with st.spinner("πŸ€– Agents are currently writing, executing, and debugging code... please wait!"): + chat_result = user_proxy.initiate_chat(assistant, message=prompt) + + st.success("βœ… Task Complete! Here is the conversation history:") + + for msg in chat_result.chat_history: + role = "assistant" if msg["role"] == "assistant" else "user" + name = msg.get("name", role) + + with st.chat_message(role): + st.markdown(f"**{name}**") + st.markdown(msg["content"]) + + png_files = glob.glob(os.path.join(work_dir, "*.png")) + if png_files: + st.divider() + st.subheader("πŸ“Š Generated Artifacts") + for img_path in png_files: + st.image(img_path, caption=os.path.basename(img_path)) diff --git a/examples/start-agents/microsoft_autogen/autogen_cli.py b/examples/start-agents/microsoft_autogen/autogen_cli.py new file mode 100644 index 00000000..bfecb686 --- /dev/null +++ b/examples/start-agents/microsoft_autogen/autogen_cli.py @@ -0,0 +1,55 @@ +import os +import sys +from dotenv import load_dotenv +import autogen +from autogen.coding import LocalCommandLineCodeExecutor + +# Load environment variables +load_dotenv() + +api_key = os.getenv("OPENAI_API_KEY") +if not api_key: + print("❌ Error: OPENAI_API_KEY not found in .env file.") + sys.exit(1) + +work_dir = "coding" +os.makedirs(work_dir, exist_ok=True) + +llm_config = {"model": "gpt-4o-mini", "api_key": api_key} + +print("==================================================") +print("πŸ€– Microsoft AutoGen Starter Initialized") +print("πŸ› οΈ Mode: Collaborative Coding & Local Execution (CLI)") +print(f"πŸ“‚ Workspace: ./{work_dir}/") +print("==================================================\n") + +assistant = autogen.AssistantAgent( + name="Assistant", + llm_config=llm_config, + system_message="""You are a helpful AI assistant. You write Python code to solve tasks. +Always wrap your code in standard markdown code blocks. +The user proxy will execute your code and report the output back to you. +DO NOT output 'TERMINATE' in the same message where you write code. +WAIT for the user proxy to run the code and confirm it worked. +ONLY output 'TERMINATE' when the user proxy confirms the execution was successful and the task is fully complete.""", +) + +executor = LocalCommandLineCodeExecutor(timeout=60, work_dir=work_dir) +user_proxy = autogen.UserProxyAgent( + name="UserProxy", + human_input_mode="NEVER", + max_consecutive_auto_reply=10, + is_termination_msg=lambda x: x.get("content", "") and x.get("content", "").rstrip().endswith("TERMINATE"), + code_execution_config={"executor": executor}, +) + +task = input("🎯 What would you like the agents to build today?\n> ") + +if not task.strip(): + print("No task provided. Exiting.") + sys.exit(0) + +print("\nπŸ’¬ Starting agent conversation...\n") + +user_proxy.initiate_chat(assistant, message=task) +print("\nβœ… Task Complete! Check the 'coding' folder for any generated scripts or files.") diff --git a/examples/start-agents/microsoft_autogen/requirements.txt b/examples/start-agents/microsoft_autogen/requirements.txt new file mode 100644 index 00000000..135713da --- /dev/null +++ b/examples/start-agents/microsoft_autogen/requirements.txt @@ -0,0 +1,8 @@ +pyautogen +python-dotenv +yfinance +matplotlib +pandas +openai +autogen +streamlit From b89cb19c756f32f2be03cd379d5b7459e9853624 Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Fri, 27 Mar 2026 17:23:54 -0400 Subject: [PATCH 09/16] Lightweight system demonstrating seamless agent-to-agent handoffs for customer support routing --- .../start-agents/swarm_triage/.env.example | 1 + examples/start-agents/swarm_triage/README.md | 94 +++++++++++++++ examples/start-agents/swarm_triage/app.py | 109 ++++++++++++++++++ .../swarm_triage/requirements.txt | 4 + .../start-agents/swarm_triage/triage_cli.py | 101 ++++++++++++++++ 5 files changed, 309 insertions(+) create mode 100644 examples/start-agents/swarm_triage/.env.example create mode 100644 examples/start-agents/swarm_triage/README.md create mode 100644 examples/start-agents/swarm_triage/app.py create mode 100644 examples/start-agents/swarm_triage/requirements.txt create mode 100644 examples/start-agents/swarm_triage/triage_cli.py diff --git a/examples/start-agents/swarm_triage/.env.example b/examples/start-agents/swarm_triage/.env.example new file mode 100644 index 00000000..1b1abd51 --- /dev/null +++ b/examples/start-agents/swarm_triage/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY="sk-your-openai-key-goes-here" diff --git a/examples/start-agents/swarm_triage/README.md b/examples/start-agents/swarm_triage/README.md new file mode 100644 index 00000000..480342b7 --- /dev/null +++ b/examples/start-agents/swarm_triage/README.md @@ -0,0 +1,94 @@ +# 🐝 OpenAI Swarm Triage + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Python Script & Web App | **Tech Stack:** OpenAI Swarm, Python, Streamlit + +

+ Saturn Cloud + Swarm + Streamlit +

+ +## πŸ“– Overview + +This template provides a lightweight, highly effective multi-agent system using **OpenAI Swarm**. It demonstrates seamless agent-to-agent handoffs for a customer support routing scenario. + +Instead of a monolithic prompt, this architecture relies on a specialized **Triage Agent** that assesses the user's intent and dynamically transfers the conversation to the specialized **Sales**, **Tech Support**, or **Billing** agents by utilizing Python functions as handoff triggers. + +--- + +## πŸ—οΈ Local Setup & Installation + +**1. Create the Environment & Install Dependencies** +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +--- + +## πŸ” Environment Configuration + +**1. Create your `.env` file:** +```bash +cp .env.example .env +``` + +**2. Add your Provider Keys:** +Open the `.env` file and insert your active API key. +```text +OPENAI_API_KEY="sk-your-openai-key-goes-here" +``` + +--- + +## πŸš€ Execution & Testing + +This repository includes both a terminal script and a full interactive Web Dashboard. + +### Phase 1: The Terminal CLI +Watch the agents seamlessly hand off the conversation in your terminal: +```bash +python triage_cli.py +``` + +### Phase 2: The Streamlit Web Dashboard +Launch the visual interface to track exactly which agent is currently active via the sidebar indicator: +```bash +streamlit run app.py +``` + +**πŸ§ͺ Recommended Testing Flow:** +To see the Triage agent properly hand off the conversation, try typing these distinct scenarios one by one: +1. **The Sales Test:** *"Hi, I am looking to upgrade my account to the enterprise tier. How much is it?"* +2. **The Billing Test:** *"Wait, I actually need a refund for my last invoice. It charged me twice."* +3. **The Tech Test:** *"Also, my application keeps throwing an 'Error 500' when I try to log in. Please fix it."* + +*(Watch how the active agent name changes to handle each specific domain!)* + +--- + +## ☁️ Cloud Deployment + +To deploy this multi-agent routing system to production on [Saturn Cloud](https://saturncloud.io/): + +**Deployment Specifications:** + +1. **Resource Type:** Streamlit Deployment / Python Server. +2. **Hardware:** CPU instance (Swarm orchestration is extremely lightweight). +3. **Environment Variables:** Inject your `OPENAI_API_KEY` directly into the Saturn Cloud secrets manager. +4. **Start Command:** `streamlit run app.py --server.port 8000 --server.address 0.0.0.0` + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Agent Framework:** [OpenAI Swarm GitHub](https://github.com/openai/swarm) +EOF +``` + +### πŸš€ Let's Test It! +Make sure to add your OpenAI key to your `.env` file, then run `streamlit run app.py`. Pay close attention to the sidebarβ€”you will see the "Currently speaking with" status dynamically change from *Triage Agent* to *Tech Support Agent* based purely on what you ask! diff --git a/examples/start-agents/swarm_triage/app.py b/examples/start-agents/swarm_triage/app.py new file mode 100644 index 00000000..dcdd07e9 --- /dev/null +++ b/examples/start-agents/swarm_triage/app.py @@ -0,0 +1,109 @@ +import os +import streamlit as st +from dotenv import load_dotenv +from swarm import Swarm, Agent + +load_dotenv() + +st.set_page_config(page_title="Swarm Support", page_icon="🐝", layout="wide") + +if not os.getenv("OPENAI_API_KEY"): + st.error("❌ OPENAI_API_KEY not found in .env file.") + st.stop() + +@st.cache_resource +def get_swarm_client(): + return Swarm() + +client = get_swarm_client() + +# --- 1. Define Strict Agents --- +triage_agent = Agent( + name="Triage Agent", + instructions="You are a routing agent. Determine the user's need and call the appropriate transfer function IMMEDIATELY. DO NOT output conversational text." +) +sales_agent = Agent( + name="Sales Agent", + instructions="You handle sales inquiries and pricing. If the user asks about tech issues or billing, you MUST call the correct transfer tool immediately. DO NOT say 'I will transfer you', just execute the tool." +) +tech_agent = Agent( + name="Tech Support Agent", + instructions="You handle tech issues and error codes. If the user asks about pricing or billing, you MUST call the correct transfer tool immediately. DO NOT say 'I will transfer you', just execute the tool." +) +billing_agent = Agent( + name="Billing Agent", + instructions="You handle billing and refunds. If the user asks about pricing or tech issues, you MUST call the correct transfer tool immediately. DO NOT say 'I will transfer you', just execute the tool." +) +human_agent = Agent( + name="Human Escalation", + instructions="You are the safety net. Tell the user a human will contact them. If they change their mind and ask about sales, tech, or billing, call the correct transfer tool immediately. DO NOT say 'I will transfer you', just execute the tool." +) + +# --- 2. Define Strict Handoff Functions --- +def transfer_to_sales(): + """Call this tool immediately to transfer the user to Sales for pricing and purchases.""" + return sales_agent +def transfer_to_tech(): + """Call this tool immediately to transfer the user to Tech Support for bugs, crashes, and Error 500s.""" + return tech_agent +def transfer_to_billing(): + """Call this tool immediately to transfer the user to Billing for refunds and invoices.""" + return billing_agent +def transfer_to_triage(): + """Call this tool immediately to transfer the user to Triage to start over.""" + return triage_agent +def escalate_to_human(): + """Call this tool immediately to escalate to a human if the user is angry or demands a manager.""" + return human_agent + +# --- 3. Attach Omni-Directional Routing --- +triage_agent.functions = [transfer_to_sales, transfer_to_tech, transfer_to_billing, escalate_to_human] +sales_agent.functions = [transfer_to_tech, transfer_to_billing, transfer_to_triage, escalate_to_human] +tech_agent.functions = [transfer_to_sales, transfer_to_billing, transfer_to_triage, escalate_to_human] +billing_agent.functions = [transfer_to_sales, transfer_to_tech, transfer_to_triage, escalate_to_human] +human_agent.functions = [transfer_to_sales, transfer_to_tech, transfer_to_billing, transfer_to_triage] + +# --- Streamlit State Management --- +if "messages" not in st.session_state: + st.session_state.messages = [] +if "active_agent" not in st.session_state: + st.session_state.active_agent = triage_agent + +# --- UI Setup --- +st.title("🐝 OpenAI Swarm Support Triage") +st.sidebar.title("🏒 Call Center Status") + +status_color = "πŸ”΄" if st.session_state.active_agent.name == "Human Escalation" else "🟒" +st.sidebar.info(f"**Currently speaking with:**\n\n{status_color} {st.session_state.active_agent.name}") + +if st.sidebar.button("πŸ—‘οΈ Reset Conversation"): + st.session_state.messages = [] + st.session_state.active_agent = triage_agent + st.rerun() + +# Render chat history cleanly +for msg in st.session_state.messages: + if msg["role"] == "assistant" and msg.get("content"): + with st.chat_message("assistant"): + sender = msg.get("sender", "Agent") + st.markdown(f"**[{sender}]** {msg['content']}") + elif msg["role"] == "user": + with st.chat_message("user"): + st.markdown(msg["content"]) + +# Handle user input +if prompt := st.chat_input("E.g., I want to buy a pro license, or my app keeps crashing..."): + + st.session_state.messages.append({"role": "user", "content": prompt}) + + with st.spinner(f"{st.session_state.active_agent.name} is processing..."): + response = client.run( + agent=st.session_state.active_agent, + messages=st.session_state.messages + ) + + st.session_state.messages = response.messages + st.session_state.active_agent = response.agent + + # Trigger a clean UI reload so the history loop draws everything natively! + st.rerun() diff --git a/examples/start-agents/swarm_triage/requirements.txt b/examples/start-agents/swarm_triage/requirements.txt new file mode 100644 index 00000000..ce9842c3 --- /dev/null +++ b/examples/start-agents/swarm_triage/requirements.txt @@ -0,0 +1,4 @@ +openai +python-dotenv +streamlit +git+https://github.com/openai/swarm.git diff --git a/examples/start-agents/swarm_triage/triage_cli.py b/examples/start-agents/swarm_triage/triage_cli.py new file mode 100644 index 00000000..8f20d92e --- /dev/null +++ b/examples/start-agents/swarm_triage/triage_cli.py @@ -0,0 +1,101 @@ +import os +import sys +from dotenv import load_dotenv +from swarm import Swarm, Agent + +load_dotenv() + +if not os.getenv("OPENAI_API_KEY"): + print("❌ Error: OPENAI_API_KEY not found in .env file.") + sys.exit(1) + +client = Swarm() + +# --- 1. Define the Agents --- +triage_agent = Agent( + name="Triage Agent", + instructions="You are the frontline customer support router. Greet the user and transfer them to the correct department (Sales, Tech, or Billing) immediately. Do not answer specific questions yourself.", +) + +sales_agent = Agent( + name="Sales Agent", + instructions="You handle sales inquiries. Explain pricing tiers (Basic $29, Pro $59, Enterprise $99) and try to close the deal. If the user asks a technical question, use transfer_to_tech. If they ask about refunds, use transfer_to_billing. If they demand a human, use escalate_to_human.", +) + +tech_agent = Agent( + name="Tech Support Agent", + instructions="You handle technical issues. Ask for error codes and provide troubleshooting steps. If the user asks about pricing, use transfer_to_sales. If they ask about refunds, use transfer_to_billing. If they demand a human, use escalate_to_human.", +) + +billing_agent = Agent( + name="Billing Agent", + instructions="You handle billing and refund requests. Be polite and ask for invoice numbers. If the user asks about pricing, use transfer_to_sales. If they ask a technical question, use transfer_to_tech. If they demand a human, use escalate_to_human.", +) + +human_agent = Agent( + name="Human Escalation", + instructions="You are the safety net. Inform the user that you are escalating their ticket to a real human representative who will email them shortly. Do not attempt to solve their problem. If they want to start over, use transfer_to_triage.", +) + +# --- 2. Define Handoff Functions (Docstrings are read by the AI!) --- +def transfer_to_sales(): + """Transfer to the Sales department for pricing, upgrades, and purchases.""" + return sales_agent + +def transfer_to_tech(): + """Transfer to the Technical Support department for bugs, crashes, and errors.""" + return tech_agent + +def transfer_to_billing(): + """Transfer to the Billing department for refunds, invoices, and payment issues.""" + return billing_agent + +def transfer_to_triage(): + """Transfer back to the main Triage routing menu if the user wants to start over.""" + return triage_agent + +def escalate_to_human(): + """Escalate to a human agent if the user is angry, asks for a human, or asks something completely off-topic.""" + return human_agent + +# --- 3. Attach Omni-Directional Routing Capabilities --- +triage_agent.functions = [transfer_to_sales, transfer_to_tech, transfer_to_billing, escalate_to_human] +sales_agent.functions = [transfer_to_tech, transfer_to_billing, transfer_to_triage, escalate_to_human] +tech_agent.functions = [transfer_to_sales, transfer_to_billing, transfer_to_triage, escalate_to_human] +billing_agent.functions = [transfer_to_sales, transfer_to_tech, transfer_to_triage, escalate_to_human] +human_agent.functions = [transfer_to_triage] + +# --- 4. The Interactive Loop --- +print("==================================================") +print("🐝 OpenAI Swarm Triage (Full Mesh Routing)") +print("πŸ”„ Active Agents: Triage, Sales, Tech Support, Billing, Escalation") +print("πŸ’‘ Type 'exit' to quit.") +print("==================================================\n") + +active_agent = triage_agent +messages = [] + +while True: + try: + user_input = input("\nπŸ§‘ You: ") + if user_input.lower() in ['exit', 'quit']: + break + if not user_input.strip(): + continue + + messages.append({"role": "user", "content": user_input}) + + response = client.run( + agent=active_agent, + messages=messages + ) + + messages = response.messages + active_agent = response.agent + + print(f"πŸ€– [{active_agent.name}]: {response.messages[-1]['content']}") + + except KeyboardInterrupt: + break + except Exception as e: + print(f"\n❌ An error occurred: {e}") From 992998d9a754fca623bd24cd11a237a3360145fc Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Sat, 28 Mar 2026 22:42:24 -0400 Subject: [PATCH 10/16] Minimalist web-surfing and tool-calling agent using Hugging Faces lightweight framework. --- examples/start-agents/smolagents/.env.example | 1 + examples/start-agents/smolagents/README.md | 0 examples/start-agents/smolagents/app.py | 94 +++++++++++++++++++ .../start-agents/smolagents/requirements.txt | 4 + .../start-agents/smolagents/smolagents_cli.py | 50 ++++++++++ 5 files changed, 149 insertions(+) create mode 100644 examples/start-agents/smolagents/.env.example create mode 100644 examples/start-agents/smolagents/README.md create mode 100644 examples/start-agents/smolagents/app.py create mode 100644 examples/start-agents/smolagents/requirements.txt create mode 100644 examples/start-agents/smolagents/smolagents_cli.py diff --git a/examples/start-agents/smolagents/.env.example b/examples/start-agents/smolagents/.env.example new file mode 100644 index 00000000..bff609c9 --- /dev/null +++ b/examples/start-agents/smolagents/.env.example @@ -0,0 +1 @@ +HF_TOKEN="hf_your_huggingface_token_here" diff --git a/examples/start-agents/smolagents/README.md b/examples/start-agents/smolagents/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/start-agents/smolagents/app.py b/examples/start-agents/smolagents/app.py new file mode 100644 index 00000000..1ff1a825 --- /dev/null +++ b/examples/start-agents/smolagents/app.py @@ -0,0 +1,94 @@ +import os +import re +import io +from contextlib import redirect_stdout, redirect_stderr +import streamlit as st +from dotenv import load_dotenv +from smolagents import CodeAgent, DuckDuckGoSearchTool, InferenceClientModel + +load_dotenv() + +st.set_page_config(page_title="smolagents UI", page_icon="πŸ€—", layout="wide") + +if not os.getenv("HF_TOKEN"): + st.error("❌ HF_TOKEN not found in .env file.") + st.stop() + +@st.cache_resource +def get_agent(): + model = InferenceClientModel(model_id="Qwen/Qwen2.5-Coder-32B-Instruct") + + custom_instructions = """You are a Code Agent. Your job is to search the web and provide answers. +CRITICAL RULES FOR TEXT PARSING: +1. NEVER write Python code to parse, clean, or format text. +2. NEVER use RegEx (re.search, re.findall) or string manipulation (.split, .replace) on search results. +3. Once you call the web_search tool, simply READ the output in your context window, and pass a natural language summary directly into the final_answer() tool.""" + + return CodeAgent( + tools=[DuckDuckGoSearchTool()], + model=model, + additional_authorized_imports=["datetime", "math"], + instructions=custom_instructions + ) + +agent = get_agent() + +def strip_ansi(text): + """Removes weird terminal color codes from the output so it looks clean in Streamlit.""" + ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') + return ansi_escape.sub('', text) + +# --- UI Setup --- +st.title("πŸ€— Hugging Face smolagents") +st.markdown("A lightweight Code Agent that writes Python on the fly to search the web and solve problems.") + +if "messages" not in st.session_state: + st.session_state.messages = [] + +if st.sidebar.button("πŸ—‘οΈ Reset Conversation"): + st.session_state.messages = [] + st.rerun() + +# Render chat history +for msg in st.session_state.messages: + with st.chat_message(msg["role"]): + st.markdown(msg["content"]) + +# Handle user input +if prompt := st.chat_input("E.g., Search the web for the distance between Earth and Mars right now..."): + + st.session_state.messages.append({"role": "user", "content": prompt}) + + with st.chat_message("user"): + st.markdown(prompt) + + with st.chat_message("assistant"): + # Create a beautiful expandable status box + with st.status("πŸ€– Agent is writing Python code and searching the web...", expanded=True) as status: + + # Create a string buffer to capture terminal output + f = io.StringIO() + + try: + # Secretly hijack both standard output and error logs + with redirect_stdout(f), redirect_stderr(f): + response = agent.run(prompt) + + # Clean the captured terminal logs and display them! + terminal_logs = strip_ansi(f.getvalue()) + st.code(terminal_logs, language="text") + + # Close the status box once finished + status.update(label="βœ… Agent finished its task!", state="complete", expanded=False) + + except Exception as e: + # If it crashes, still show the terminal logs so you can debug! + terminal_logs = strip_ansi(f.getvalue()) + st.code(terminal_logs, language="text") + st.error(f"Error executing agent: {e}") + status.update(label="❌ Agent crashed!", state="error", expanded=True) + st.stop() + + # Display the final, polished answer outside the box + st.markdown(f"**Final Answer:** {response}") + st.session_state.messages.append({"role": "assistant", "content": f"**Final Answer:** {response}"}) diff --git a/examples/start-agents/smolagents/requirements.txt b/examples/start-agents/smolagents/requirements.txt new file mode 100644 index 00000000..a4266316 --- /dev/null +++ b/examples/start-agents/smolagents/requirements.txt @@ -0,0 +1,4 @@ +smolagents +python-dotenv +ddgs +streamlit \ No newline at end of file diff --git a/examples/start-agents/smolagents/smolagents_cli.py b/examples/start-agents/smolagents/smolagents_cli.py new file mode 100644 index 00000000..4ec96a59 --- /dev/null +++ b/examples/start-agents/smolagents/smolagents_cli.py @@ -0,0 +1,50 @@ +import os +import sys +from dotenv import load_dotenv +from smolagents import CodeAgent, DuckDuckGoSearchTool, InferenceClientModel + +load_dotenv() + +if not os.getenv("HF_TOKEN"): + print("❌ Error: HF_TOKEN not found in .env file.") + sys.exit(1) + +model = InferenceClientModel(model_id="Qwen/Qwen2.5-Coder-32B-Instruct") + +custom_instructions = """You are a Code Agent. Your job is to search the web and provide answers. +CRITICAL RULES FOR TEXT PARSING: +1. NEVER write Python code to parse, clean, or format text. +2. NEVER use RegEx (re.search, re.findall) or string manipulation (.split, .replace) on search results. +3. Once you call the web_search tool, simply READ the output in your context window, and pass a natural language summary directly into the final_answer() tool.""" + + +# Notice the change here: 'instructions' instead of 'system_prompt' +agent = CodeAgent( + tools=[DuckDuckGoSearchTool()], + model=model, + additional_authorized_imports=["datetime", "math"], + instructions=custom_instructions +) + +print("==================================================") +print("πŸ€— Hugging Face smolagents Initialized") +print("πŸ› οΈ Mode: Minimalist Web-Surfing Code Agent") +print("πŸ’‘ Type 'exit' to quit.") +print("==================================================\n") + +while True: + try: + user_input = input("\nπŸ§‘ You: ") + if user_input.lower() in ['exit', 'quit']: + break + if not user_input.strip(): + continue + + print("πŸ€– Agent is thinking and writing code...\n") + response = agent.run(user_input) + print(f"\n✨ Final Answer: {response}") + + except KeyboardInterrupt: + break + except Exception as e: + print(f"\n❌ An error occurred: {e}") From e8e8055cd9649aca8c238b373ed93977a211688c Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Sat, 28 Mar 2026 22:47:10 -0400 Subject: [PATCH 11/16] Minimalist web-surfing and tool-calling agent using Hugging Faces lightweight framework. --- .../.env.example | 0 .../start-agents/smolagents-web/README.md | 104 ++++++++++++++++++ .../{smolagents => smolagents-web}/app.py | 0 .../requirements.txt | 0 .../smolagents_cli.py | 0 examples/start-agents/smolagents/README.md | 0 6 files changed, 104 insertions(+) rename examples/start-agents/{smolagents => smolagents-web}/.env.example (100%) create mode 100644 examples/start-agents/smolagents-web/README.md rename examples/start-agents/{smolagents => smolagents-web}/app.py (100%) rename examples/start-agents/{smolagents => smolagents-web}/requirements.txt (100%) rename examples/start-agents/{smolagents => smolagents-web}/smolagents_cli.py (100%) delete mode 100644 examples/start-agents/smolagents/README.md diff --git a/examples/start-agents/smolagents/.env.example b/examples/start-agents/smolagents-web/.env.example similarity index 100% rename from examples/start-agents/smolagents/.env.example rename to examples/start-agents/smolagents-web/.env.example diff --git a/examples/start-agents/smolagents-web/README.md b/examples/start-agents/smolagents-web/README.md new file mode 100644 index 00000000..a0e8ae05 --- /dev/null +++ b/examples/start-agents/smolagents-web/README.md @@ -0,0 +1,104 @@ +# πŸ€— Hugging Face smolagents + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Python Script & Web App | **Tech Stack:** smolagents, Hugging Face, Streamlit + +

+ Saturn Cloud + smolagents + Streamlit + Python +

+ +## πŸ“– Overview + +This template provides a minimalist, production-ready implementation of Hugging Face's lightweight **smolagents** framework. + +Unlike traditional LLM agents that rely on rigid JSON tool-calling and often hallucinate logic, `smolagents` utilizes **Code Agents**. When faced with a problem, the agent dynamically writes Python code, executes it in a secure local sandbox, and uses the exact output to formulate its final, factual response. + +### 🧩 Key Features +1. **CodeAgent Engine:** Powered by `Qwen2.5-Coder-32B-Instruct` via Hugging Face's Serverless Inference API for state-of-the-art Python generation. +2. **Web Surfing Engine:** Equipped with `DuckDuckGoSearchTool` (`ddgs`) to pull real-time data and news from the internet. +3. **Native Python Execution:** Authorized to import standard libraries (`math`, `datetime`) to solve complex geometric, mathematical, and temporal problems on the fly. +4. **Transparent UI Processing:** The Streamlit dashboard intercepts the agent's hidden terminal outputs, allowing you to read the exact Python scripts it writes in the background! + +--- + +## πŸ—οΈ Local Setup & Installation + +**1. Create the Environment & Install Dependencies** +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` +*(Note for Kali Linux / strict Debian users: If you hit a PEP 668 environment error, use `python3 -m pip install -r requirements.txt` to install safely into the venv).* + +--- + +## πŸ” Environment Configuration + +**1. Create your `.env` file:** +```bash +cp .env.example .env +``` + +**2. Add your Provider Keys:** +Open the `.env` file and insert your active Hugging Face token. *(You can generate a free "Fine-grained" token at [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens). Ensure it has **Serverless Inference API** permissions).* +```text +HF_TOKEN="hf_your_token_here" +``` + +--- + +## πŸš€ Execution & Testing + +This repository includes both a terminal script and a highly interactive Web Dashboard. + +### Phase 1: The Terminal CLI +Watch the agent output its coding process directly in your terminal: +```bash +python smolagents_cli.py +``` + +### Phase 2: The Streamlit Web Dashboard +Launch the visual interface to see the intercepted Python sandbox logs directly in your browser: +```bash +streamlit run app.py +``` + +**πŸ§ͺ Recommended Testing Flow:** +To see the true power of a Code Agent, test its ability to dynamically write Python scripts for different scenarios: + +**1. The Pure Logic & Math Test:** +> *"Using the math library, calculate the volume of a sphere with a radius of 42.7 inches. Show me the exact Python code you used to figure it out."* + +**2. The Live Web & Parsing Test:** +> *"Search the web and tell me the current stock price of NVIDIA (NVDA) right now. Also, summarize the top two most recent news headlines about the company."* + +**3. The Multi-Step Hybrid Test (The Ultimate Stress Test):** +> *"Search the web for the current population of Tokyo, Japan. Once you find it, assume the average human weighs 62 kg. Calculate the total combined weight of Tokyo's entire population in pounds (1 kg = 2.204 lbs)."* + +*(In the Streamlit UI, click the expandable **"Agent is writing Python code..."** box to literally read the code it wrote to solve these prompts!)* + +--- + +## ☁️ Cloud Deployment + +To deploy this minimalist agent to production on [Saturn Cloud](https://saturncloud.io/): + +**Deployment Specifications:** + +1. **Resource Type:** Streamlit Deployment / Python Server. +2. **Hardware:** CPU instance. +3. **Environment Variables:** Inject your `HF_TOKEN` directly into the Saturn Cloud secrets manager. +4. **Start Command:** `streamlit run app.py --server.port 8000 --server.address 0.0.0.0` + +--- + +## πŸ“š Official Documentation & References + +* **Deployment Infrastructure:** [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* **Agent Framework:** [smolagents GitHub](https://github.com/huggingface/smolagents) + diff --git a/examples/start-agents/smolagents/app.py b/examples/start-agents/smolagents-web/app.py similarity index 100% rename from examples/start-agents/smolagents/app.py rename to examples/start-agents/smolagents-web/app.py diff --git a/examples/start-agents/smolagents/requirements.txt b/examples/start-agents/smolagents-web/requirements.txt similarity index 100% rename from examples/start-agents/smolagents/requirements.txt rename to examples/start-agents/smolagents-web/requirements.txt diff --git a/examples/start-agents/smolagents/smolagents_cli.py b/examples/start-agents/smolagents-web/smolagents_cli.py similarity index 100% rename from examples/start-agents/smolagents/smolagents_cli.py rename to examples/start-agents/smolagents-web/smolagents_cli.py diff --git a/examples/start-agents/smolagents/README.md b/examples/start-agents/smolagents/README.md deleted file mode 100644 index e69de29b..00000000 From 71309ebfbfdbc3543e9a8d88423bf68790e5a3a9 Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Fri, 3 Apr 2026 09:56:11 -0400 Subject: [PATCH 12/16] Stateful agent with infinite long-term memory management for personalized interactions --- .../start-agents/letta_starter/Dockerfile | 28 +++++ examples/start-agents/letta_starter/README.md | 83 +++++++++++++ examples/start-agents/letta_starter/app.py | 110 ++++++++++++++++++ .../letta_starter/docker-compose.yml | 30 +++++ examples/start-agents/letta_starter/init.sql | 1 + .../start-agents/letta_starter/letta_cli.py | 61 ++++++++++ .../letta_starter/requirements.txt | 5 + examples/start-agents/letta_starter/run.py | 37 ++++++ 8 files changed, 355 insertions(+) create mode 100644 examples/start-agents/letta_starter/Dockerfile create mode 100644 examples/start-agents/letta_starter/README.md create mode 100644 examples/start-agents/letta_starter/app.py create mode 100644 examples/start-agents/letta_starter/docker-compose.yml create mode 100644 examples/start-agents/letta_starter/init.sql create mode 100644 examples/start-agents/letta_starter/letta_cli.py create mode 100644 examples/start-agents/letta_starter/requirements.txt create mode 100644 examples/start-agents/letta_starter/run.py diff --git a/examples/start-agents/letta_starter/Dockerfile b/examples/start-agents/letta_starter/Dockerfile new file mode 100644 index 00000000..abf393f0 --- /dev/null +++ b/examples/start-agents/letta_starter/Dockerfile @@ -0,0 +1,28 @@ +# Use a lightweight Python base image +FROM python:3.11-slim + +# Set the working directory +WORKDIR /app + +# Install system dependencies required for Letta and Postgres connections +RUN apt-get update && apt-get install -y \ + build-essential \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements and install them +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the application code +COPY . . + +# Expose ports (Streamlit: 8000, Letta Server: 8283) +EXPOSE 8000 +EXPOSE 8283 + +# Set environment variable to trigger auto-start in run.py +ENV CLOUD_ENV=true + +# Start the Master Orchestrator +CMD ["python", "run.py"] diff --git a/examples/start-agents/letta_starter/README.md b/examples/start-agents/letta_starter/README.md new file mode 100644 index 00000000..2eb355ea --- /dev/null +++ b/examples/start-agents/letta_starter/README.md @@ -0,0 +1,83 @@ +# 🧠 Letta (MemGPT) Production Starter + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Docker Compose & Python UI | **Tech Stack:** Letta, Streamlit, PostgreSQL, Docker + +

+ Saturn Cloud + Letta + Streamlit + Postgres + Docker +

+ +## πŸ“– Overview + +This template provides a production-ready, full-stack implementation of **Letta (formerly MemGPT)**. + +Traditional LLMs suffer from context window limitations, causing them to forget information over time. Letta resolves this using an "LLM Operating System" architecture that divides memory into Core Memory (immediate context) and Archival Memory (infinite vector storage). The agent autonomously edits, appends, and replaces its own memory blocks, resulting in a perpetually stateful AI companion. + +### Infrastructure Deployment +This template uses a decoupled microservice architecture for maximum scalability: +* **The Backend (Docker Compose):** Manages the Letta API Server and a dedicated PostgreSQL database. It utilizes an `init.sql` script to automatically inject the `pgvector` extension on boot, ensuring the Archival Memory tables compile perfectly. +* **The Frontend (Python Launcher):** A lightweight client layer (`run.py`) that connects to the backend API, allowing you to interface with the agent via a headless Terminal CLI or a rich Streamlit Web Dashboard. + +--- + +## βœ… Prerequisites + +1. **Docker & Docker Compose:** Required to run the Letta backend and PostgreSQL database. +2. **Python 3.10+:** Required for the frontend UI clients. +3. **OpenAI API Key:** Required for the core LLM and Embedding models. Generate one at the [OpenAI Developer Platform](https://platform.openai.com/api-keys). + +--- + +## πŸ—οΈ Setup & Deployment + +**1. Configure Environment Variables** +```bash +cp .env.example .env +``` +Open `.env` and add your active `OPENAI_API_KEY`. Ensure `LETTA_PG_URI` is pointing to the Dockerized database (`postgresql+asyncpg://letta:letta@letta_db:5432/letta`). + +**2. Spin Up the Backend Services** +Launch the Letta Server and Postgres database. On the very first boot, the system will inject the `vector` extension and run ~150 Alembic schema migrations. +```bash +sudo docker compose up -d +``` +*(Note: Wait ~15-30 seconds on the initial boot for the database tables to build before launching the frontend. You can verify readiness with `sudo docker logs letta-server`).* + +**3. Setup the Frontend Environment** +Create a virtual environment and install the client dependencies: +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +**4. Launch the UI** +Run the frontend orchestrator to connect to the backend: +```bash +python run.py +``` +*(Select Option 1 for the Streamlit UI, or Option 2 for headless terminal testing).* + +--- + +## πŸ’‘ Usage Guide + +Once the Streamlit Web Dashboard is running, you can interact with the agent and monitor its internal state in real-time. Use the **Database Inspector** in the right-hand sidebar to visualize the agent actively editing its own Postgres memory blocks. + +**Example Prompts to Test Stateful Memory:** +* *"Hi, my name is Alex and I am severely allergic to peanuts."* (Click 'Refresh Database' to watch the agent rewrite its 'Human' core memory block). +* *"I'm going to a baseball game today, what kind of snacks should I get?"* (The agent will proactively exclude peanuts based on its retained memory). +* *"What was my name again, and what did I tell you about my diet?"* + +--- + +## πŸ“š Official Documentation & References + +* [Saturn Cloud Documentation](https://saturncloud.io/docs/) +* [Letta Official Documentation](https://docs.letta.com/) +* [pgvector Documentation](https://github.com/pgvector/pgvector) diff --git a/examples/start-agents/letta_starter/app.py b/examples/start-agents/letta_starter/app.py new file mode 100644 index 00000000..ddea50e3 --- /dev/null +++ b/examples/start-agents/letta_starter/app.py @@ -0,0 +1,110 @@ +import os +import time +import streamlit as st +from dotenv import load_dotenv +from letta_client import Letta + +load_dotenv() + +st.set_page_config(page_title="Letta Companion", page_icon="🧠", layout="wide") + +if not os.getenv("OPENAI_API_KEY"): + st.error("❌ OPENAI_API_KEY not found in .env file.") + st.stop() + +@st.cache_resource +def init_letta_client(): + try: + # Connects to your Dockerized Letta Server + return Letta(base_url="http://localhost:8283") + except Exception: + return None + +client = init_letta_client() + +if not client: + st.error("❌ Cannot connect to Letta Server. Is Docker running?") + st.stop() + +@st.cache_resource +def get_or_create_agent(): + # Initializing the default memory for your agent + memory_blocks = [ + {"label": "human", "value": "User information is unknown. Ask the user for their name and preferences."}, + {"label": "persona", "value": "Your name is Echo. You are a personalized, highly empathetic AI companion. You actively update your database to remember facts about the user."} + ] + return client.agents.create( + name=f"echo-ui-{int(time.time())}", + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + memory_blocks=memory_blocks, + ) + +agent_state = get_or_create_agent() + +# --- UI Setup --- +st.title("🧠 Letta (MemGPT) Companion") +st.markdown("A stateful AI companion with perpetual memory. Watch it edit its own PostgreSQL database to remember what you tell it!") + +col1, col2 = st.columns([2, 1]) + +with col1: + if "messages" not in st.session_state: + st.session_state.messages = [] + + if st.button("πŸ—‘οΈ Reset Conversation"): + st.session_state.messages = [] + st.rerun() + + for msg in st.session_state.messages: + with st.chat_message(msg["role"]): + st.markdown(msg["content"]) + + if prompt := st.chat_input("E.g., Hi, my name is Alex and my favorite color is blue..."): + st.session_state.messages.append({"role": "user", "content": prompt}) + with st.chat_message("user"): + st.markdown(prompt) + + with st.chat_message("assistant"): + with st.spinner("πŸ€– Echo is thinking (and updating its Postgres database)..."): + try: + response = client.agents.messages.create( + agent_id=agent_state.id, + messages=[{"role": "user", "content": prompt}] + ) + + final_answer = "" + for msg in response.messages: + if hasattr(msg, 'message_type'): + # Capture internal thoughts + if msg.message_type == 'internal_monologue': + with st.expander("πŸ’­ Agent Thought Process"): + st.write(msg.content) + # Capture final spoken message + elif msg.message_type == 'assistant_message' and msg.content: + final_answer = msg.content + st.markdown(final_answer) + st.session_state.messages.append({"role": "assistant", "content": final_answer}) + except Exception as e: + st.error(f"Error executing agent: {e}") + +with col2: + st.subheader("πŸ—‚οΈ Postgres DB Inspector") + st.info("Watch the agent edit its Core Memory in real-time!") + + # We use a button to manually refresh the view of the database + if st.button("πŸ”„ Refresh Database"): + pass + + try: + # Fetch live memory blocks directly from the Letta Server + human_block = client.agents.blocks.retrieve(agent_id=agent_state.id, block_label="human") + persona_block = client.agents.blocks.retrieve(agent_id=agent_state.id, block_label="persona") + + st.markdown("**User (Human) Block:**") + st.code(human_block.value, language="text") + + st.markdown("**Agent (Persona) Block:**") + st.code(persona_block.value, language="text") + except Exception as e: + st.warning("Memory blocks initializing...") diff --git a/examples/start-agents/letta_starter/docker-compose.yml b/examples/start-agents/letta_starter/docker-compose.yml new file mode 100644 index 00000000..0cd95816 --- /dev/null +++ b/examples/start-agents/letta_starter/docker-compose.yml @@ -0,0 +1,30 @@ +version: '3.8' + +services: + letta_db: + image: ankane/pgvector:latest + container_name: letta-postgres + environment: + - POSTGRES_USER=letta + - POSTGRES_PASSWORD=letta + - POSTGRES_DB=letta + ports: + - "5432:5432" + volumes: + - letta_db_data:/var/lib/postgresql/data + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + + letta_server: + image: letta/letta:latest + container_name: letta-server + depends_on: + - letta_db + ports: + - "8283:8283" + env_file: + - .env + environment: + - LETTA_PG_URI=postgresql+asyncpg://letta:letta@letta_db:5432/letta + +volumes: + letta_db_data: diff --git a/examples/start-agents/letta_starter/init.sql b/examples/start-agents/letta_starter/init.sql new file mode 100644 index 00000000..0aa0fc22 --- /dev/null +++ b/examples/start-agents/letta_starter/init.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS vector; diff --git a/examples/start-agents/letta_starter/letta_cli.py b/examples/start-agents/letta_starter/letta_cli.py new file mode 100644 index 00000000..d9aceffe --- /dev/null +++ b/examples/start-agents/letta_starter/letta_cli.py @@ -0,0 +1,61 @@ +import os +import sys +import time +from dotenv import load_dotenv +from letta_client import Letta + +load_dotenv() + +if not os.getenv("OPENAI_API_KEY"): + print("❌ Error: OPENAI_API_KEY not found in .env file.") + sys.exit(1) + +try: + client = Letta(base_url="http://localhost:8283") +except Exception: + print("❌ Error: Could not connect to Letta server. Please run using `python run.py`") + sys.exit(1) + +print("==================================================") +print("🧠 Letta (MemGPT) CLI Companion") +print("πŸ’‘ Type 'exit' to quit.") +print("==================================================\n") + +memory_blocks = [ + {"label": "human", "value": "User information is unknown. Ask the user for their name and preferences."}, + {"label": "persona", "value": "Your name is Echo. You are a personalized, highly empathetic AI companion. You actively update your memory to remember facts about the user."} +] + +agent_state = client.agents.create( + name=f"echo-cli-{int(time.time())}", + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + memory_blocks=memory_blocks, +) + +while True: + try: + user_input = input("\nπŸ§‘ You: ") + if user_input.lower() in ['exit', 'quit']: + break + if not user_input.strip(): + continue + + print("πŸ€– Echo is thinking...\n") + + response = client.agents.messages.create( + agent_id=agent_state.id, + messages=[{"role": "user", "content": user_input}] + ) + + for msg in response.messages: + if hasattr(msg, 'message_type'): + if msg.message_type == 'internal_monologue': + print(f" [Thought: {msg.content}]") + elif msg.message_type == 'assistant_message' and msg.content: + print(f"\n✨ Echo: {msg.content}") + + except KeyboardInterrupt: + break + except Exception as e: + print(f"\n❌ An error occurred: {e}") diff --git a/examples/start-agents/letta_starter/requirements.txt b/examples/start-agents/letta_starter/requirements.txt new file mode 100644 index 00000000..026b728d --- /dev/null +++ b/examples/start-agents/letta_starter/requirements.txt @@ -0,0 +1,5 @@ +letta +letta-client +python-dotenv +streamlit +asyncpg \ No newline at end of file diff --git a/examples/start-agents/letta_starter/run.py b/examples/start-agents/letta_starter/run.py new file mode 100644 index 00000000..69e03cf3 --- /dev/null +++ b/examples/start-agents/letta_starter/run.py @@ -0,0 +1,37 @@ +import subprocess +import sys +import os + +def main(): + print("==================================================") + print("πŸš€ Letta (MemGPT) Frontend Launcher") + print("==================================================") + + # Check if running in a Cloud environment + is_cloud = os.getenv("CLOUD_ENV", "false").lower() == "true" + + try: + if is_cloud: + print("\n☁️ Cloud environment detected. Auto-launching Streamlit...") + subprocess.run([sys.executable, "-m", "streamlit", "run", "app.py", "--server.port=8000", "--server.address=0.0.0.0"]) + else: + print("\nWhich interface would you like to run?") + print("1. Streamlit Web Dashboard (UI Testing)") + print("2. Terminal CLI (Headless Testing)") + + choice = input("\n> ") + + if choice == '1': + print("\n🌐 Launching Streamlit Dashboard...") + subprocess.run([sys.executable, "-m", "streamlit", "run", "app.py"]) + elif choice == '2': + print("\nπŸ–₯️ Launching Terminal CLI...") + subprocess.run([sys.executable, "letta_cli.py"]) + else: + print("Invalid choice. Shutting down.") + + except KeyboardInterrupt: + print("\nβœ… Exiting frontend launcher...") + +if __name__ == "__main__": + main() From c029b41113a96de6050bdb81bdf26ee58ee1f3cf Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Sat, 4 Apr 2026 14:28:44 -0400 Subject: [PATCH 13/16] Agent that directly controls a headless browser to navigate sites, click elements, and extract data --- .../browser_automator/.env.example | 1 + .../start-agents/browser_automator/README.md | 73 +++++++++++++++++++ .../start-agents/browser_automator/app.py | 51 +++++++++++++ .../browser_automator/automator_cli.py | 32 ++++++++ .../browser_automator/requirements.txt | 5 ++ .../start-agents/browser_automator/run.py | 36 +++++++++ 6 files changed, 198 insertions(+) create mode 100644 examples/start-agents/browser_automator/.env.example create mode 100644 examples/start-agents/browser_automator/README.md create mode 100644 examples/start-agents/browser_automator/app.py create mode 100644 examples/start-agents/browser_automator/automator_cli.py create mode 100644 examples/start-agents/browser_automator/requirements.txt create mode 100644 examples/start-agents/browser_automator/run.py diff --git a/examples/start-agents/browser_automator/.env.example b/examples/start-agents/browser_automator/.env.example new file mode 100644 index 00000000..66010f5b --- /dev/null +++ b/examples/start-agents/browser_automator/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY="sk-your-key-here" diff --git a/examples/start-agents/browser_automator/README.md b/examples/start-agents/browser_automator/README.md new file mode 100644 index 00000000..e1688c67 --- /dev/null +++ b/examples/start-agents/browser_automator/README.md @@ -0,0 +1,73 @@ +# 🌐 Browser-Use Automator + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Python Script | **Tech Stack:** Browser-Use, Playwright, LangChain + +

+ Saturn Cloud + Browser-Use + Playwright + Streamlit +

+ +## πŸ“– Overview + +The **Browser-Use Automator** is a production-grade template designed for AI-driven web navigation and data extraction. Unlike traditional scrapers that rely on fragile CSS selectors, this agent utilizes a vision-capable LLM to "see" the browser's Document Object Model (DOM). It interacts with elements (buttons, inputs, complex JS dropdowns) exactly like a human user, making it ideal for automating tasks across dynamic or authenticated websites. + +### Infrastructure Deployment +* **Compute & Vision:** The agent requires an LLM with strong reasoning capabilities (GPT-4o) to process visual element coordinates and DOM trees. +* **Environment Isolation:** The template utilizes Playwright as the high-performance browser controller, supporting both **Headless** (background execution) and **Headed** (visible window) modes. +* **Binary Provisioning:** To ensure cross-distribution compatibility (e.g., Kali, Ubuntu, Debian), the setup script automatically handles fallback Chromium and FFmpeg binary installations. + +--- + +## βœ… Prerequisites + +1. **Python 3.10+**: Required for the asynchronous automation loop. +2. **OpenAI API Key**: Generate one via the [OpenAI Platform Dashboard](https://platform.openai.com/api-keys). +3. **System Dependencies**: Ensure `libgbm-dev` and `libnss3` are installed on your Linux host. + +--- + +## πŸ—οΈ Setup & Deployment + +**1. Environment Setup** +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +python3 -m playwright install chromium +``` + +**2. Configure Secrets** +```bash +cp .env.example .env +# Enter your OPENAI_API_KEY in the .env file +``` + +**3. Execution & Testing** +The master orchestrator allows for dual-mode testing depending on your debugging requirements: +```bash +python run.py +``` + +--- + +## πŸ’‘ Usage Guide + +* **Terminal CLI:** Optimized for high-speed data extraction and automated cron-job workflows. +* **Streamlit UI:** Provides a "Headed" toggle to watch the browser actions in real-time. Use the text area to define natural language instructions. + +**Example Tasks:** +* *"Go to Amazon, search for 'RTX 5090', and give me the price of the first result that is in stock."* +* *"Navigate to the GitHub trending page and summarize the top 3 Python repositories today."* +* *"Search for the latest stock price of NVIDIA and tell me the daily change percentage."* + +--- + +## πŸ“š Official Documentation & References + +* [Browser-Use Github Repository](https://github.com/browser-use/browser-use) +* [Playwright Python Documentation](https://playwright.dev/python/docs/intro) +* [Saturn Cloud Help Center](https://saturncloud.io/docs/) diff --git a/examples/start-agents/browser_automator/app.py b/examples/start-agents/browser_automator/app.py new file mode 100644 index 00000000..8e4e4396 --- /dev/null +++ b/examples/start-agents/browser_automator/app.py @@ -0,0 +1,51 @@ +import streamlit as st +import asyncio +import os +from browser_use import Agent +from browser_use.llm import ChatOpenAI +from dotenv import load_dotenv + +load_dotenv() + +st.set_page_config(page_title="Browser-Use Automator", page_icon="πŸ€–", layout="wide") + +st.title("🌐 Browser-Use Automator") +st.markdown("---") + +# Native wrapper for stability +class BrowserUseLLM(ChatOpenAI): + def __init__(self, **kwargs): + super().__init__(**kwargs) + +with st.sidebar: + st.header("βš™οΈ Configuration") + model_name = st.selectbox("LLM Model", ["gpt-4o", "gpt-4o-mini"]) + st.info("πŸ’‘ **Pro Tip:** Use gpt-4o for complex navigation tasks.") + +prompt = st.text_area("🎯 Enter Automation Task:", "Go to news.ycombinator.com and find the top story title.", height=100) + +if st.button("πŸš€ Execute Automation", use_container_width=True): + if not prompt: + st.warning("Please enter a task first.") + else: + async def run_automation(): + llm = ChatOpenAI(model=model_name) + agent = Agent(task=prompt, llm=llm) + return await agent.run() + + with st.status("πŸ€– Agent is navigating the web...", expanded=True) as status: + try: + st.write("Initializing Browser...") + result = asyncio.run(run_automation()) + status.update(label="βœ… Automation Complete!", state="complete", expanded=False) + + st.subheader("🏁 Final Result") + # Extract the final string answer from the history list + final_answer = result.final_result() + st.success(final_answer) + + with st.expander("πŸ› οΈ View Technical Execution Logs (JSON)"): + st.json(result.model_dump()) + + except Exception as e: + st.error(f"❌ Automation failed: {e}") diff --git a/examples/start-agents/browser_automator/automator_cli.py b/examples/start-agents/browser_automator/automator_cli.py new file mode 100644 index 00000000..04d9b2f0 --- /dev/null +++ b/examples/start-agents/browser_automator/automator_cli.py @@ -0,0 +1,32 @@ +import asyncio +import os +from dotenv import load_dotenv +from browser_use import Agent +# Note: Switching from langchain_openai to the native browser_use wrapper +from browser_use.llm import ChatOpenAI + +load_dotenv() + +async def main(): + # The native wrapper handles all 'provider' and 'ainvoke' issues internally + llm = ChatOpenAI(model="gpt-4o") + + task = "Go to https://news.ycombinator.com and tell me the title of the top post." + + agent = Agent( + task=task, + llm=llm, + # We increase the failure limit slightly for complex sites + max_failures=5 + ) + + print(f"πŸš€ Running Production Task: {task}") + try: + result = await agent.run() + print("\nβœ… Final Report:") + print(result) + except Exception as e: + print(f"❌ Automation failed: {e}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/start-agents/browser_automator/requirements.txt b/examples/start-agents/browser_automator/requirements.txt new file mode 100644 index 00000000..afaa2113 --- /dev/null +++ b/examples/start-agents/browser_automator/requirements.txt @@ -0,0 +1,5 @@ +browser-use +playwright +langchain-openai +streamlit +python-dotenv \ No newline at end of file diff --git a/examples/start-agents/browser_automator/run.py b/examples/start-agents/browser_automator/run.py new file mode 100644 index 00000000..9159c45d --- /dev/null +++ b/examples/start-agents/browser_automator/run.py @@ -0,0 +1,36 @@ +import subprocess +import sys +import os +from dotenv import load_dotenv + +load_dotenv() + +def main(): + print("==================================================") + print("🌐 Browser-Use Automator Launcher") + print("==================================================") + + if not os.getenv("OPENAI_API_KEY"): + print("❌ Error: OPENAI_API_KEY not found in .env") + sys.exit(1) + + print("\nSelect Testing Mode:") + print("1. Streamlit Web UI (Visual Debugging)") + print("2. Terminal CLI (Headless Extraction)") + + choice = input("\n> ") + + try: + if choice == '1': + print("\nπŸ–₯️ Launching Streamlit...") + subprocess.run([sys.executable, "-m", "streamlit", "run", "app.py"]) + elif choice == '2': + print("\nπŸ“Ÿ Launching CLI...") + subprocess.run([sys.executable, "automator_cli.py"]) + else: + print("Invalid choice. Exiting.") + except KeyboardInterrupt: + print("\nπŸ‘‹ Shutdown complete.") + +if __name__ == "__main__": + main() From 429f287fce39dfa914a171a9e1778efb282fa6a9 Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Tue, 7 Apr 2026 06:53:56 -0400 Subject: [PATCH 14/16] Model Context Protocol (MCP) server exposing local SQLite database tools to compatible agents --- .../mcp_sqlite_server/.env.example | 2 + .../start-agents/mcp_sqlite_server/README.md | 88 +++++++++++++++++++ .../mcp_sqlite_server/requirements.txt | 4 + .../start-agents/mcp_sqlite_server/run.py | 43 +++++++++ .../start-agents/mcp_sqlite_server/server.py | 40 +++++++++ 5 files changed, 177 insertions(+) create mode 100644 examples/start-agents/mcp_sqlite_server/.env.example create mode 100644 examples/start-agents/mcp_sqlite_server/README.md create mode 100644 examples/start-agents/mcp_sqlite_server/requirements.txt create mode 100644 examples/start-agents/mcp_sqlite_server/run.py create mode 100644 examples/start-agents/mcp_sqlite_server/server.py diff --git a/examples/start-agents/mcp_sqlite_server/.env.example b/examples/start-agents/mcp_sqlite_server/.env.example new file mode 100644 index 00000000..75b8c398 --- /dev/null +++ b/examples/start-agents/mcp_sqlite_server/.env.example @@ -0,0 +1,2 @@ +# The path to your local SQLite database file +SQLITE_DB_PATH="local_data.db" diff --git a/examples/start-agents/mcp_sqlite_server/README.md b/examples/start-agents/mcp_sqlite_server/README.md new file mode 100644 index 00000000..635aa31c --- /dev/null +++ b/examples/start-agents/mcp_sqlite_server/README.md @@ -0,0 +1,88 @@ +# πŸ—„οΈ MCP SQLite Agent + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** CPU/GPU | **Resource:** Python Server | **Tech Stack:** MCP, SQLite, Python + +

+ Saturn Cloud + MCP + SQLite + Python +

+ +## πŸ“– Overview + +The **MCP SQLite Agent** is a production-grade Model Context Protocol server. It acts as a bridge between local SQLite databases and AI models, allowing agents to perform natural language data analysis via structured SQL execution and schema reflection. + +### Infrastructure Deployment +* **Hybrid Interface:** Supports both **Stdio Transport** (for production AI connections) and **Web Inspector** (for developer testing). +* **Environment Isolation:** Uses a dedicated Python virtual environment to manage `aiosqlite` and the `mcp` SDK without system-level conflicts. +* **Auto-Provisioning:** The orchestrator automatically initializes a sample `users` table if no database is detected, ensuring immediate "plug-and-play" functionality. + +--- + +## βœ… Prerequisites + +1. **Python 3.10+**: Core runtime. +2. **Node.js & NPM**: Mandatory for the **MCP Inspector** UI. +3. **Claude Desktop**: (Optional) Recommended client for production testing. + +--- + +## πŸ—οΈ Setup & Deployment + +**1. System & Python Setup** +```bash +sudo apt update && sudo apt install -y nodejs npm +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +**2. Secret Configuration** +```bash +cp .env.example .env +# Ensure SQLITE_DB_PATH points to your desired .db file +``` + +**3. Launching the Orchestrator** +```bash +python run.py +``` + +--- + +## πŸ’‘ Usage Guide + +### Mode 1: Testing with MCP Inspector (Web UI) +Use this mode to verify your tools are working before connecting to an AI. +1. Run `python run.py` and select **Option 2**. +2. In the browser, click the **Connect** button (ensure command is `python3` and args is `server.py`). +3. Navigate to the **Tools** tab. +4. Select `query_db` and enter `SELECT * FROM users;` to test data retrieval. + +### Mode 2: Production (Claude Desktop) +To give Claude "eyes" on your local database: +1. Locate your Claude Desktop config (typically `~/Library/Application Support/Claude/claude_desktop_config.json`). +2. Add the following entry to the `mcpServers` object: +```json +"mcp-sqlite": { + "command": "/path/to/your/venv/bin/python3", + "args": ["/path/to/your/mcp_sqlite_server/server.py"], + "env": { "SQLITE_DB_PATH": "/path/to/your/local_data.db" } +} +``` +3. Restart Claude and look for the πŸ”¨ icon. + +**Example Prompts:** +* *"List the tables in my local database."* +* *"Who are the users registered as 'Engineer'?"* + +--- + +## πŸ“š Official Documentation & References + +* [MCP Documentation](https://modelcontextprotocol.io/) +* [FastMCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) +* [aiosqlite Reference](https://aiosqlite.omnilib.dev/) diff --git a/examples/start-agents/mcp_sqlite_server/requirements.txt b/examples/start-agents/mcp_sqlite_server/requirements.txt new file mode 100644 index 00000000..2d3b9d23 --- /dev/null +++ b/examples/start-agents/mcp_sqlite_server/requirements.txt @@ -0,0 +1,4 @@ +mcp +aiosqlite +python-dotenv + diff --git a/examples/start-agents/mcp_sqlite_server/run.py b/examples/start-agents/mcp_sqlite_server/run.py new file mode 100644 index 00000000..93de97f3 --- /dev/null +++ b/examples/start-agents/mcp_sqlite_server/run.py @@ -0,0 +1,43 @@ +import subprocess +import sys +import os +from dotenv import load_dotenv + +load_dotenv() + +def main(): + print("==================================================") + print("πŸ—„οΈ MCP SQLite Server Orchestrator") + print("==================================================") + + db_path = os.getenv("SQLITE_DB_PATH") + if not os.path.exists(db_path): + print(f"⚠️ Warning: Database '{db_path}' not found. Creating a sample DB...") + import sqlite3 + conn = sqlite3.connect(db_path) + conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, role TEXT)") + conn.execute("INSERT INTO users (name, role) VALUES ('Admin', 'Superuser'), ('Dev', 'Engineer')") + conn.commit() + conn.close() + + print("\nSelect Mode:") + print("1. Run MCP Server (Standard Stdio)") + print("2. Run MCP Inspector (Web UI for Testing Tools)") + + choice = input("\n> ") + + try: + if choice == '1': + print("πŸš€ Server starting... (Connect your MCP client to this process)") + subprocess.run([sys.executable, "server.py"]) + elif choice == '2': + print("πŸ” Launching MCP Inspector...") + # This requires the mcp[cli] extra + subprocess.run(["npx", "@modelcontextprotocol/inspector", "python3", "server.py"]) + else: + print("Invalid choice.") + except KeyboardInterrupt: + print("\nπŸ‘‹ Shutdown complete.") + +if __name__ == "__main__": + main() diff --git a/examples/start-agents/mcp_sqlite_server/server.py b/examples/start-agents/mcp_sqlite_server/server.py new file mode 100644 index 00000000..36cf65c8 --- /dev/null +++ b/examples/start-agents/mcp_sqlite_server/server.py @@ -0,0 +1,40 @@ +import asyncio +import os +import aiosqlite +from mcp.server.fastmcp import FastMCP +from dotenv import load_dotenv + +load_dotenv() + +# Initialize FastMCP Server +mcp = FastMCP("SQLite-Local-Server") +DB_PATH = os.getenv("SQLITE_DB_PATH", "local_data.db") + +@mcp.tool() +async def query_db(sql: str): + """Execute a read-only SQL query on the local SQLite database.""" + async with aiosqlite.connect(DB_PATH) as db: + db.row_factory = aiosqlite.Row + async with db.execute(sql) as cursor: + rows = await cursor.fetchall() + return [dict(row) for row in rows] + +@mcp.tool() +async def list_tables(): + """List all tables available in the local database.""" + async with aiosqlite.connect(DB_PATH) as db: + async with db.execute("SELECT name FROM sqlite_master WHERE type='table';") as cursor: + rows = await cursor.fetchall() + return [row[0] for row in rows] + +@mcp.tool() +async def describe_table(table_name: str): + """Get the schema/columns for a specific table.""" + async with aiosqlite.connect(DB_PATH) as db: + async with db.execute(f"PRAGMA table_info({table_name});") as cursor: + rows = await cursor.fetchall() + return [dict(row) for row in rows] + +if __name__ == "__main__": + # Run the server using the MCP stdio transport + mcp.run() From 8ff6d972ea004f063868b11bea0822a4f16b300b Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Tue, 7 Apr 2026 23:54:27 -0400 Subject: [PATCH 15/16] Enterprise-grade RAG and plugin architecture using Microsoft's Semantic Kernel --- .../semantic_kernel_copilot/.env.example | 4 + .../semantic_kernel_copilot/app.py | 82 +++++++++++++++++++ .../semantic_kernel_copilot/copilot.py | 80 ++++++++++++++++++ .../semantic_kernel_copilot/requirements.txt | 4 + .../semantic_kernel_copilot/run.py | 25 ++++++ 5 files changed, 195 insertions(+) create mode 100644 examples/start-agents/semantic_kernel_copilot/.env.example create mode 100644 examples/start-agents/semantic_kernel_copilot/app.py create mode 100644 examples/start-agents/semantic_kernel_copilot/copilot.py create mode 100644 examples/start-agents/semantic_kernel_copilot/requirements.txt create mode 100644 examples/start-agents/semantic_kernel_copilot/run.py diff --git a/examples/start-agents/semantic_kernel_copilot/.env.example b/examples/start-agents/semantic_kernel_copilot/.env.example new file mode 100644 index 00000000..2b7cc80c --- /dev/null +++ b/examples/start-agents/semantic_kernel_copilot/.env.example @@ -0,0 +1,4 @@ +# Choose your provider: "openai" or "azure_openai" +GLOBAL_LLM_SERVICE="openai" +OPENAI_API_KEY="sk-..." +OPENAI_CHAT_MODEL="gpt-4o" diff --git a/examples/start-agents/semantic_kernel_copilot/app.py b/examples/start-agents/semantic_kernel_copilot/app.py new file mode 100644 index 00000000..fb15ae6b --- /dev/null +++ b/examples/start-agents/semantic_kernel_copilot/app.py @@ -0,0 +1,82 @@ +import streamlit as st +import asyncio +import os +from dotenv import load_dotenv +from semantic_kernel import Kernel +from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettings +from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior +from semantic_kernel.functions import kernel_function +from semantic_kernel.contents import ChatHistory + +load_dotenv() + +# --- 1. SET PAGE CONFIG --- +st.set_page_config(page_title="SK Copilot", page_icon="🧩", layout="wide") + +# --- 2. DEFINE PLUGINS --- +class ProjectManagerPlugin: + @kernel_function( + description="Calculates project completion date based on weeks.", + name="CalculateTimeline" + ) + def calculate_timeline(self, start_date: str, weeks: int) -> str: + return f"πŸ“… Analysis: Starting {start_date}, completion is scheduled in {weeks} weeks." + +# --- 3. KERNEL INITIALIZATION (Cached) --- +@st.cache_resource +def create_kernel(): + kernel = Kernel() + api_key = os.getenv("OPENAI_API_KEY") + model_id = os.getenv("OPENAI_CHAT_MODEL", "gpt-4o") + service_id = "chat-gpt" + + kernel.add_service( + OpenAIChatCompletion(service_id=service_id, ai_model_id=model_id, api_key=api_key) + ) + kernel.add_plugin(ProjectManagerPlugin(), plugin_name="ProjectManager") + return kernel, service_id + +# --- 4. UI COMPONENTS --- +st.title("🧩 Semantic Kernel Copilot") +st.markdown("Enterprise-grade orchestration with Native Python Plugins.") + +if "messages" not in st.session_state: + st.session_state.messages = ChatHistory() + st.session_state.messages.add_system_message("You are an enterprise assistant. Use the ProjectManager plugin for timeline queries.") + +# Display chat history +for msg in st.session_state.messages.messages: + if msg.role == "system": continue + with st.chat_message(msg.role): + st.markdown(msg.content) + +# --- 5. CHAT LOGIC --- +if prompt := st.chat_input("Ask about your project timeline..."): + with st.chat_message("user"): + st.markdown(prompt) + + st.session_state.messages.add_user_message(prompt) + + async def generate_response(): + kernel, service_id = create_kernel() + execution_settings = OpenAIChatPromptExecutionSettings( + service_id=service_id, + function_choice_behavior=FunctionChoiceBehavior.Auto() + ) + + result = await kernel.get_service(service_id).get_chat_message_content( + chat_history=st.session_state.messages, + settings=execution_settings, + kernel=kernel, + ) + return result.content + + with st.chat_message("assistant"): + with st.spinner("Thinking..."): + try: + # Run the async kernel in the streamlit loop + response = asyncio.run(generate_response()) + st.markdown(response) + st.session_state.messages.add_assistant_message(response) + except Exception as e: + st.error(f"Kernel Error: {str(e)}") diff --git a/examples/start-agents/semantic_kernel_copilot/copilot.py b/examples/start-agents/semantic_kernel_copilot/copilot.py new file mode 100644 index 00000000..c7cab997 --- /dev/null +++ b/examples/start-agents/semantic_kernel_copilot/copilot.py @@ -0,0 +1,80 @@ +import asyncio +import os +from dotenv import load_dotenv +from semantic_kernel import Kernel +from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettings +from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior +from semantic_kernel.functions import kernel_function +from semantic_kernel.contents import ChatHistory + +load_dotenv() + +class BusinessLogicPlugin: + @kernel_function( + description="Calculates the project completion date based on start date and duration.", + name="CalculateTimeline" + ) + def calculate_timeline(self, start_date: str, weeks: int) -> str: + return f"Based on a start date of {start_date}, the project will conclude in {weeks} weeks." + +async def main(): + api_key = os.getenv("OPENAI_API_KEY") + model_id = os.getenv("OPENAI_CHAT_MODEL", "gpt-4o") + + if not api_key or api_key == "sk-...": + print("❌ ERROR: OpenAI API Key is missing or default in .env file.") + return + + # 1. Initialize the Kernel + kernel = Kernel() + + # 2. Add the AI Service + service_id = "chat-gpt" + kernel.add_service( + OpenAIChatCompletion( + service_id=service_id, + ai_model_id=model_id, + api_key=api_key, + ) + ) + + # 3. Register Plugins + kernel.add_plugin(BusinessLogicPlugin(), plugin_name="ProjectManager") + + # 4. Initialize Chat History + history = ChatHistory() + history.add_system_message("You are an enterprise assistant. Use the ProjectManager plugin for timeline queries.") + + print(f"🧩 Semantic Kernel Copilot Active [Model: {model_id}]") + print("(Type 'exit' to quit)") + + # πŸ’‘ THE FIX: Use FunctionChoiceBehavior instead of manual tool_choice + # This tells the service to automatically discover and send tools to OpenAI. + execution_settings = OpenAIChatPromptExecutionSettings( + service_id=service_id, + function_choice_behavior=FunctionChoiceBehavior.Auto() + ) + + while True: + user_input = input("\nUser: ") + if user_input.lower() == "exit": + break + + history.add_user_message(user_input) + + try: + # The Kernel now handles the tool mapping automatically + result = await kernel.get_service(service_id).get_chat_message_content( + chat_history=history, + settings=execution_settings, + kernel=kernel, + ) + + print(f"Copilot: {result.content}") + history.add_assistant_message(result.content) + + except Exception as e: + print(f"❌ API ERROR: {str(e)}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/start-agents/semantic_kernel_copilot/requirements.txt b/examples/start-agents/semantic_kernel_copilot/requirements.txt new file mode 100644 index 00000000..d06292cd --- /dev/null +++ b/examples/start-agents/semantic_kernel_copilot/requirements.txt @@ -0,0 +1,4 @@ +semantic-kernel>=1.41.1 +streamlit +python-dotenv +openai diff --git a/examples/start-agents/semantic_kernel_copilot/run.py b/examples/start-agents/semantic_kernel_copilot/run.py new file mode 100644 index 00000000..158f80c4 --- /dev/null +++ b/examples/start-agents/semantic_kernel_copilot/run.py @@ -0,0 +1,25 @@ +import os +import sys +import subprocess + +def main(): + print("==================================================") + print("🧩 Semantic Kernel Copilot Orchestrator") + print("==================================================") + print("\nSelect Mode:") + print("1. Terminal CLI (Rapid Testing)") + print("2. Streamlit Web UI (Production View)") + + choice = input("\n> ") + + if choice == '1': + print("πŸ“Ÿ Launching CLI...") + subprocess.run([sys.executable, "copilot.py"]) + elif choice == '2': + print("πŸ–₯️ Launching Web UI...") + subprocess.run(["streamlit", "run", "app.py"]) + else: + print("Invalid choice.") + +if __name__ == "__main__": + main() From 8c1b13f87ca635491ef51eb43ff4bdf5e1a459cb Mon Sep 17 00:00:00 2001 From: Olusegun Durojaye Date: Sun, 12 Apr 2026 08:50:57 -0400 Subject: [PATCH 16/16] Simulates a software company (PM, Architect, Engineer) to output a complete repo from a prompt --- .../start-agents/meta_gpt_factory/Dockerfile | 26 +++++++ .../start-agents/meta_gpt_factory/README.md | 71 +++++++++++++++++++ examples/start-agents/meta_gpt_factory/app.py | 36 ++++++++++ .../meta_gpt_factory/config2.yaml | 5 ++ .../meta_gpt_factory/docker-compose.yml | 12 ++++ .../meta_gpt_factory/requirements.txt | 21 ++++++ examples/start-agents/meta_gpt_factory/run.py | 25 +++++++ 7 files changed, 196 insertions(+) create mode 100644 examples/start-agents/meta_gpt_factory/Dockerfile create mode 100644 examples/start-agents/meta_gpt_factory/README.md create mode 100644 examples/start-agents/meta_gpt_factory/app.py create mode 100644 examples/start-agents/meta_gpt_factory/config2.yaml create mode 100644 examples/start-agents/meta_gpt_factory/docker-compose.yml create mode 100644 examples/start-agents/meta_gpt_factory/requirements.txt create mode 100644 examples/start-agents/meta_gpt_factory/run.py diff --git a/examples/start-agents/meta_gpt_factory/Dockerfile b/examples/start-agents/meta_gpt_factory/Dockerfile new file mode 100644 index 00000000..1520ab9a --- /dev/null +++ b/examples/start-agents/meta_gpt_factory/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.11-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + git curl libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \ + libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \ + libxext6 libxfixes3 libxrandr2 libgbm1 libasound2 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Step 1: Install the Anchors and Pip upgrade +COPY requirements.txt . +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# Step 2: Install Playwright Chromium +RUN playwright install chromium + +RUN mkdir -p /app/config /app/workspace +COPY . . + +EXPOSE 8501 +ENV METAGPT_CONFIG_PATH=/app/config/config2.yaml + +CMD ["streamlit", "run", "app.py", "--server.address=0.0.0.0"] \ No newline at end of file diff --git a/examples/start-agents/meta_gpt_factory/README.md b/examples/start-agents/meta_gpt_factory/README.md new file mode 100644 index 00000000..708403d2 --- /dev/null +++ b/examples/start-agents/meta_gpt_factory/README.md @@ -0,0 +1,71 @@ +# 🏭 MetaGPT Software Factory + +*Cloud deployment architecture verified for [Saturn Cloud](https://saturncloud.io/).* + +**Hardware:** 4+ vCPU / 8GB+ RAM | **Resource:** Python Server | **Tech Stack:** MetaGPT, Docker, Python + + +

+ Saturn Cloud + MetaGPT + Docker + Python +

+ +## πŸ“– Overview + +The **MetaGPT Software Factory** is a production-grade multi-agent framework that simulates an entire software company. By assigning specialized rolesβ€”Product Manager, Architect, and Engineerβ€”the system transforms a single natural language requirement into a comprehensive repository including PRDs, system designs, and executable code. + +### Infrastructure Deployment +* **Environment Isolation:** Containerized via **Docker** using a Python 3.11-slim base and 2026 dependency anchors to prevent conflicts and ensure a "clean room" execution environment. +* **Persistent Workspace:** Uses Docker volume mounting to map internal agent outputs to the local `./workspace` directory, ensuring all generated assets remain persistent. +* **Headless Research:** Integrated with **Playwright** to allow the Product Manager agent to perform real-time market research via an automated Chromium browser. + +--- + +## βœ… Prerequisites + +1. **OpenAI API Key**: Required for the agentic reasoning engine (`gpt-4o`). [Get Key](https://platform.openai.com/) +2. **Docker & Docker Compose**: Mandatory for environment orchestration and isolation. +3. **Kali Linux Users**: Run `export DOCKER_HOST=unix:///var/run/docker.sock` to ensure Docker takes priority over Podman. +4. **Hardware Context**: Minimum 8GB RAM recommended for parallel agent processing. + +--- + +## πŸ—οΈ Setup & Deployment + +**1. Secret Configuration** +```bash +cat << 'EOF' > config2.yaml +llm: + api_type: "openai" + api_key: "sk-YOUR_KEY_HERE" + model: "gpt-4o" + base_url: "https://api.openai.com/v1" +EOF +``` + +**2. Launching the Factory** +```bash +docker-compose up --build +``` + +--- + +## πŸ’‘ Usage Guide + +### Mode 1: Streamlit Dashboard (Visual SOP) +1. Run `docker-compose up` and navigate to `http://localhost:8501`. +2. Input your software project idea. +3. Click **"Start Production"** to trigger the waterfall process. + +### Mode 2: CLI Power-User +```bash +docker-compose run factory python run.py "Create a secure Flask API with JWT authentication" +``` + +## πŸ“š Official Documentation & References + +* [MetaGPT Official Documentation](https://docs.deepwisdom.ai/main/en/) +* [Docker Compose Specification](https://docs.docker.com/compose/) +* [Playwright Python SDK](https://playwright.dev/python/docs/intro) diff --git a/examples/start-agents/meta_gpt_factory/app.py b/examples/start-agents/meta_gpt_factory/app.py new file mode 100644 index 00000000..23f942d7 --- /dev/null +++ b/examples/start-agents/meta_gpt_factory/app.py @@ -0,0 +1,36 @@ +import streamlit as st +import asyncio +from metagpt.team import Team +from metagpt.roles import ProductManager, Architect, ProjectManager, Engineer + +# UI Setup +st.set_page_config(page_title="MetaGPT Factory", layout="wide") +st.title("πŸ—οΈ AI Software Factory") + +idea = st.text_area("What should the factory build?", placeholder="e.g. Create a CLI-based Password Manager in Python") + +if st.button("Start Production"): + if idea: + async def run_factory(): + # The engine finds the config automatically via the Docker ENV variable + team = Team() + team.hire([ProductManager(), Architect(), ProjectManager(), Engineer()]) + team.invest(3.0) + + # Run_project is a sync method in 0.8.2 + team.run_project(idea) + + # This is the part that actually runs the agents + await team.run(n_round=5) + st.success("Software Production Complete! Check your /workspace folder.") + + with st.spinner("Agents are collaborating... check terminal for live logs."): + try: + # We use a new event loop to avoid conflicts with Streamlit's loop + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(run_factory()) + except Exception as e: + st.error(f"Execution Error: {e}") + else: + st.warning("Please enter a project idea.") \ No newline at end of file diff --git a/examples/start-agents/meta_gpt_factory/config2.yaml b/examples/start-agents/meta_gpt_factory/config2.yaml new file mode 100644 index 00000000..8c1b754e --- /dev/null +++ b/examples/start-agents/meta_gpt_factory/config2.yaml @@ -0,0 +1,5 @@ +llm: + api_type: "openai" + api_key: "sk-YOUR_KEY_HERE" + model: "gpt-4o" + base_url: "https://api.openai.com/v1" diff --git a/examples/start-agents/meta_gpt_factory/docker-compose.yml b/examples/start-agents/meta_gpt_factory/docker-compose.yml new file mode 100644 index 00000000..7755882b --- /dev/null +++ b/examples/start-agents/meta_gpt_factory/docker-compose.yml @@ -0,0 +1,12 @@ +services: + factory: + build: . + container_name: software_factory + volumes: + - ./workspace:/app/workspace + - ./config2.yaml:/app/config/config2.yaml + ports: + - "8501:8501" + environment: + - PYTHONUNBUFFERED=1 + - METAGPT_CONFIG_PATH=/app/config/config2.yaml \ No newline at end of file diff --git a/examples/start-agents/meta_gpt_factory/requirements.txt b/examples/start-agents/meta_gpt_factory/requirements.txt new file mode 100644 index 00000000..c991c78d --- /dev/null +++ b/examples/start-agents/meta_gpt_factory/requirements.txt @@ -0,0 +1,21 @@ +# --- PRIMARY ENGINES --- +metagpt==0.8.2 +openai==1.39.0 +httpx==0.27.2 +pydantic==2.9.2 + +# --- THE ANCHORS --- +protobuf==4.25.3 +google-api-core==2.19.1 +google-auth==2.29.0 +googleapis-common-protos==1.63.0 +proto-plus==1.23.0 +opentelemetry-api==1.24.0 +opentelemetry-sdk==1.24.0 +importlib-metadata==7.0.0 + +# --- UI & SUPPORT --- +streamlit==1.32.0 +playwright +beautifulsoup4 +python-docx \ No newline at end of file diff --git a/examples/start-agents/meta_gpt_factory/run.py b/examples/start-agents/meta_gpt_factory/run.py new file mode 100644 index 00000000..fa53e957 --- /dev/null +++ b/examples/start-agents/meta_gpt_factory/run.py @@ -0,0 +1,25 @@ +import asyncio +from metagpt.team import Team +from metagpt.roles import ProductManager, Architect, ProjectManager, Engineer + +async def startup(idea: str): + # Create the Software Company Team + company = Team() + + # Hire the standard production roles + company.hire([ + ProductManager(), + Architect(), + ProjectManager(), + Engineer() + ]) + + # Run the production line + company.run_project(idea) + await company.run(n_round=5) + print(f"βœ… Production Finished! Check your /workspace folder.") + +if __name__ == "__main__": + import sys + idea = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else input("πŸš€ Project Idea: ") + asyncio.run(startup(idea=idea)) \ No newline at end of file