# Examples and Usage Guide

Practical examples for common Logos ADK patterns.

## Table of Contents

1. [Basic Agent](#basic-agent)
2. [Tools](#tools)
3. [Memory](#memory)
4. [Guardrails](#guardrails)
5. [RAG](#rag)
6. [Multi-Agent Teams](#multi-agent-teams)
7. [MCP Integration](#mcp-integration)
8. [Streaming](#streaming)
9. [Structured Output](#structured-output)
10. [Caching](#caching)
11. [Observability](#observability)
12. [Web Server](#web-server)
13. [YAML Configuration](#yaml-configuration)
14. [Testing](#testing)

---

## Basic Agent

### Simple Chatbot

```python
import asyncio
from logos_adk import Agent

async def main():
    agent = Agent(
        "assistant",
        model="claude-sonnet-4-20250514",
        system="You are a friendly, concise assistant."
    )
    
    # Single interaction
    response = await agent.run("Hello! What can you do?")
    print(response.content)
    
    # Maintain conversation with session
    session_id = "conv-123"
    
    response = await agent.run("Tell me about yourself", session_id=session_id)
    print(response.content)
    
    response = await agent.run("What was my first question?", session_id=session_id)
    # Agent remembers context

asyncio.run(main())
```

### Provider Configuration

```python
import asyncio
from logos_adk import Agent
from logos_adk.providers import OpenAICompatibleProvider

# Using provider directly
provider = OpenAICompatibleProvider(
    model="llama3",
    base_url="http://localhost:11434/v1",
    api_key="not-needed"
)

agent = Agent("local_bot", model=provider)
response = await agent.run("Hello!")
print(response.content)

# Using model string (auto-resolves provider)
agent = Agent("cloud_bot", model="gpt-4o")
response = await agent.run("Hello!")
```

### Multiple Providers

```python
from logos_adk import Agent, Team

# Different agents with different providers
claude_agent = Agent(
    "claude_bot",
    model="claude-sonnet-4-20250514",
    system="You use Claude."
)

gpt_agent = Agent(
    "gpt_bot",
    model="gpt-4o",
    system="You use GPT-4."
)

ollama_agent = Agent(
    "local_bot",
    model="ollama:llama3",
    system="You run locally."
)
```

---

## Tools

### Basic Tool

```python
import asyncio
from logos_adk import Agent, tool

@tool
async def get_weather(city: str) -> str:
    """Get current weather for a city."""
    # In production, call actual API
    return f"It's +22°C and sunny in {city}"

@tool
async def calculate(expression: str) -> str:
    """Evaluate mathematical expression."""
    try:
        result = eval(expression)
        return str(result)
    except Exception as e:
        return f"Error: {e}"

async def main():
    agent = (
        Agent("helper", model="claude-sonnet-4-20250514")
        .system("Use tools to answer questions.")
        .tools([get_weather, calculate])
    )
    
    response = await agent.run("What's the weather in London?")
    print(response.content)
    
    response = await agent.run("Calculate 2 + 2 * 10")
    print(response.content)

asyncio.run(main())
```

### Tool with Complex Arguments

```python
from logos_adk import tool
from typing import Optional

@tool
async def search_articles(
    query: str,
    category: str = "all",
    limit: int = 10,
    sort_by: str = "relevance"
) -> str:
    """Search for articles.
    
    Args:
        query: Search query
        category: Article category (all, tech, science, business)
        limit: Maximum results to return
        sort_by: Sort order (relevance, date, popularity)
    """
    # Implementation
    results = await db.search(query, category, limit, sort_by)
    return format_results(results)
```

### Tool Error Handling

```python
from logos_adk import tool

@tool
async def fetch_url(url: str) -> str:
    """Fetch content from URL."""
    import httpx
    
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(url, timeout=10)
            response.raise_for_status()
            return response.text[:1000]
    except httpx.TimeoutException:
        return "Error: Request timed out"
    except httpx.HTTPError as e:
        return f"Error: HTTP {e}"
    except Exception as e:
        return f"Error: {type(e).__name__}: {e}"
```

### Tool with External API

```python
from logos_adk import tool
import httpx

@tool
async def get_stock_price(symbol: str) -> str:
    """Get current stock price."""
    api_key = "YOUR_API_KEY"
    url = f"https://api.example.com/quote/{symbol}"
    
    async with httpx.AsyncClient() as client:
        response = await client.get(
            url,
            headers={"Authorization": f"Bearer {api_key}"}
        )
        data = response.json()
    
    price = data["price"]
    change = data["change"]
    return f"{symbol}: ${price} ({change:+.2f}%)"

@tool
async def translate(text: str, target: str = "en") -> str:
    """Translate text to target language."""
    api_key = "YOUR_API_KEY"
    url = "https://api.example.com/translate"
    
    async with httpx.AsyncClient() as client:
        response = await client.post(
            url,
            json={"text": text, "target": target},
            headers={"Authorization": f"Bearer {api_key}"}
        )
        data = response.json()
    
    return data["translated_text"]
```

### Using Built-in Tools

```python
from logos_adk import Agent, cli_tool, claude_code_tool

async def main():
    # CLI execution tool
    cli = cli_tool()
    
    agent = (
        Agent("dev_bot", model="claude-sonnet-4-20250514")
        .system("You can execute CLI commands.")
        .tools([cli])
    )
    
    response = await agent.run("List files in current directory")
    print(response.content)
    
    # Claude Code integration
    cc = claude_code_tool()
    agent2 = (
        Agent("coder", model="claude-sonnet-4-20250514")
        .system("You write code using Claude Code.")
        .tools([cc])
    )
    
    response = await agent2.run(
        "Create a Python function that calculates fibonacci numbers"
    )
    print(response.content)

asyncio.run(main())
```

---

## Memory

### Session Memory

```python
from logos_adk import Agent, SessionMemory

# Sliding window - keep last 20 messages
memory = SessionMemory(strategy="sliding_window", max_messages=20)

agent = (
    Agent("chatbot", model="claude-sonnet-4-20250514")
    .memory(memory)
)

# Conversation is automatically managed
response = await agent.run("My name is Alice")
response = await agent.run("What's my name?")  # Remembers: Alice
```

### Summarization Memory

```python
from logos_adk import Agent, SessionMemory

# Summarize old messages, keep last 10 fresh
memory = SessionMemory(strategy="summarize", max_messages=10)

agent = (
    Agent("long_chat", model="gpt-4o")
    .memory(memory)
)

# After 10 messages, older ones get summarized
```

### Token Limit Memory

```python
from logos_adk import Agent, SessionMemory

# Keep messages under 16000 tokens
memory = SessionMemory(strategy="token_limit", max_tokens=16000)

agent = (
    Agent("efficient_bot", model="claude-sonnet-4-20250514")
    .memory(memory)
)
```

### Long-Term Memory with SQLite

```python
from logos_adk import Agent, LongTermMemory, SQLiteBackend

# Persistent storage
backend = SQLiteBackend("memory.db")
memory = LongTermMemory(
    backend=backend,
    retrieval="hybrid",  # keyword + semantic
    limit=5
)

agent = (
    Agent("persistent_bot", model="claude-sonnet-4-20250514")
    .memory(memory)
)

# Memories persist across sessions
response = await agent.run("I love pizza", session_id="session1")
response = await agent.run("What do I love?", session_id="session2")  # Remembers!
```

### Shared Memory Across Agents

```python
from logos_adk import Agent, SharedMemory, SQLiteBackend

# Shared knowledge base
backend = SQLiteBackend("shared.db")
shared = SharedMemory(backend=backend, retrieval="hybrid", limit=10)

# Multiple agents share the same memory
agent1 = Agent("agent1", model="...").memory(shared)
agent2 = Agent("agent2", model="...").memory(shared)

# What agent1 learns, agent2 can access
await agent1.run("The capital of France is Paris", session_id="s1")
response = await agent2.run("What is the capital of France?", session_id="s2")
# Response mentions Paris
```

---

## Guardrails

### Content Filter

```python
from logos_adk import Agent, ContentFilter

# Block inappropriate content
content_filter = ContentFilter(
    blocked=["spam", "scam", "inappropriate_word"],
    case_sensitive=False,
    action="block"
)

agent = (
    Agent("safe_bot", model="claude-sonnet-4-20250514")
    .input_guardrail(content_filter)
)

try:
    response = await agent.run("spam message")
except GuardrailError as e:
    print(f"Blocked: {e.result.message}")
```

### Warning Instead of Blocking

```python
from logos_adk import Agent, KeywordGuard

# Warn but continue
keyword_guard = KeywordGuard(
    keywords=["competitor", "rival_company"],
    action="warn",
    message="Competitor mention detected"
)

agent = (
    Agent("monitoring_bot", model="gpt-4o")
    .input_guardrail(keyword_guard)
)

# Continues but logs warning
response = await agent.run("What about competitor X?")
```

### Override Response

```python
from logos_adk import Agent, ContentFilter

# Override blocked content
content_filter = ContentFilter(
    blocked=["politics", "election"],
    action="override",
    override_with="I prefer not to discuss political topics."
)

agent = (
    Agent("neutral_bot", model="claude-sonnet-4-20250514")
    .input_guardrail(content_filter)
    .output_guardrail(content_filter)
)

# Returns override message instead of blocking
response = await agent.run("What about the election?")
print(response.content)  # "I prefer not to discuss political topics."
```

### Regex Guard for PII

```python
from logos_adk import Agent, RegexGuard

# Block email addresses
email_guard = RegexGuard(
    deny=[r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"],
    action="block",
    message="Email addresses are not allowed"
)

# Block phone numbers
phone_guard = RegexGuard(
    deny=[r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b"],
    action="block",
    message="Phone numbers are not allowed"
)

agent = (
    Agent("privacy_bot", model="gpt-4o")
    .input_guardrail(email_guard, phone_guard)
)
```

### Max Length Guard

```python
from logos_adk import Agent, MaxLengthGuard

# Limit input and output length
input_guard = MaxLengthGuard(max_chars=1000, action="block")
output_guard = MaxLengthGuard(max_chars=500, action="warn")

agent = (
    Agent("concise_bot", model="claude-sonnet-4-20250514")
    .input_guardrail(input_guard)
    .output_guardrail(output_guard)
)
```

### Multiple Guardrails

```python
from logos_adk import Agent, ContentFilter, RegexGuard, MaxLengthGuard

agent = (
    Agent("secure_bot", model="gpt-4o")
    .input_guardrail(
        ContentFilter(blocked=["spam", "abuse"]),
        RegexGuard(deny=[r"password", r"secret"]),
        MaxLengthGuard(max_chars=2000)
    )
    .output_guardrail(
        ContentFilter(blocked=["I don't know"], action="override", override_with="Let me research that."),
        MaxLengthGuard(max_chars=500)
    )
)
```

---

## RAG

### Basic RAG Setup

```python
import asyncio
from logos_adk import Agent, KnowledgeBase
from logos_adk.rag import (
    TextLoader,
    RecursiveCharacterSplitter,
    InMemoryVectorStore
)

async def embed_fn(text: str) -> list[float]:
    """Simple embedding function - replace with real model."""
    # In production, use sentence-transformers or API
    return [0.1] * 384  # Placeholder

async def main():
    # Load documents
    loader = TextLoader("docs/")
    documents = loader.load()
    
    # Split into chunks
    splitter = RecursiveCharacterSplitter(
        chunk_size=500,
        chunk_overlap=50
    )
    
    # Create vector store
    vector_store = InMemoryVectorStore(embed_fn=embed_fn)
    vector_store.add_documents(documents, splitter)
    
    # Create knowledge base
    kb = KnowledgeBase(vector_store)
    
    # Attach to agent
    agent = (
        Agent("expert", model="claude-sonnet-4-20250514")
        .knowledge(kb, mode="memory", limit=5)
    )
    
    # Agent now has access to document knowledge
    response = await agent.run("What does the documentation say about authentication?")
    print(response.content)

asyncio.run(main())
```

### RAG as Tool

```python
from logos_adk import Agent, rag_tool
from logos_adk.rag import KnowledgeBase

# Agent explicitly searches when needed
agent = (
    Agent("researcher", model="gpt-4o")
    .tools([rag_tool(kb, limit=5)])
)

response = await agent.run("Search for information about API rate limits")
```

### Markdown Documentation

```python
from logos_adk.rag import MarkdownLoader, KnowledgeBase
from logos_adk.rag.vector_sqlite import SQLiteVectorStore

# Load markdown docs
loader = MarkdownLoader("docs/markdown/")
documents = loader.load()

# SQLite vector store for persistence
vector_store = SQLiteVectorStore("vectors.db", embed_fn=embed_fn)
vector_store.add_documents(documents, splitter)

kb = KnowledgeBase(vector_store)
agent = Agent("docs_bot", model="...").knowledge(kb)
```

### PDF Documents

```python
from logos_adk.rag import PDFLoader, KnowledgeBase

# Load PDFs (requires pypdf)
loader = PDFLoader("docs/pdfs/")
documents = loader.load()

# ... continue with splitter and vector store
```

### Custom Document Loader

```python
from logos_adk.rag import DocumentLoader, Document

class DatabaseLoader(DocumentLoader):
    """Load documents from database."""
    
    def __init__(self, connection_string: str, query: str):
        self.connection_string = connection_string
        self.query = query
    
    def load(self, path: str = None) -> list[Document]:
        import sqlite3
        
        conn = sqlite3.connect(self.connection_string)
        cursor = conn.cursor()
        cursor.execute(self.query)
        
        documents = []
        for row in cursor.fetchall():
            documents.append(Document(
                content=row[1],  # content column
                metadata={"id": row[0], "source": "database"}
            ))
        
        conn.close()
        return documents

loader = DatabaseLoader("docs.db", "SELECT id, content FROM articles")
documents = loader.load()
```

---

## Multi-Agent Teams

### Basic Team

```python
import asyncio
from logos_adk import Agent, Team

async def main():
    researcher = Agent(
        "researcher",
        model="claude-sonnet-4-20250514",
        system="You research topics thoroughly. Provide facts and data."
    )
    
    writer = Agent(
        "writer",
        model="claude-sonnet-4-20250514",
        system="You write engaging content. Use facts provided."
    )
    
    editor = Agent(
        "editor",
        model="claude-sonnet-4-20250514",
        system="You edit content for clarity and accuracy."
    )
    
    team = Team(
        "content_team",
        agents=[researcher, writer, editor],
        orchestrator="researcher",
        topology="mesh",
        max_rounds=10
    )
    
    response = await team.run("Write an article about renewable energy")
    print(response.content)

asyncio.run(main())
```

### Custom Topology

```python
from logos_adk import Agent, Team

# Define communication paths
topology = {
    "manager": ["analyst1", "analyst2"],
    "analyst1": ["manager"],
    "analyst2": ["manager"]
}

team = Team(
    "analysis_team",
    agents=[manager, analyst1, analyst2],
    orchestrator="manager",
    topology=topology
)
```

### Team as Tool

```python
from logos_adk import Agent, Team

# Create specialized team
research_team = Team(
    "research",
    agents=[researcher, analyst],
    orchestrator="researcher"
)

# Use team as a tool in another agent
main_agent = (
    Agent("coordinator", model="gpt-4o")
    .tools([research_team.as_tool()])
)

response = await main_agent.run(
    "Research the history of artificial intelligence"
)
```

### Agent Communication

```python
from logos_adk import Agent, Team, handler
from dataclasses import dataclass

@dataclass
class ResearchRequest:
    topic: str
    depth: str = "shallow"

class ResearchAgent:
    def __init__(self):
        self.agent = Agent("researcher", model="...")
    
    @handler
    async def handle_research(self, req: ResearchRequest):
        response = await self.agent.run(
            f"Research {req.topic} at {req.depth} depth"
        )
        return response.content

# In team, messages are routed to handlers
```

---

## MCP Integration

### Connect to MCP Server

```python
from logos_adk import Agent

# SSE connection
agent = (
    Agent("mcp_bot", model="claude-sonnet-4-20250514")
    .mcp("http://localhost:3000/sse")
)

# stdio connection
agent = (
    Agent("github_bot", model="claude-sonnet-4-20250514")
    .mcp("npx @server-github")
)

response = await agent.run("List my GitHub repositories")
```

### Multiple MCP Servers

```python
from logos_adk import Agent

agent = (
    Agent("multi_mcp", model="gpt-4o")
    .mcp("http://localhost:3000/sse")  # File system
    .mcp("npx @server-github")  # GitHub
    .mcp("npx @server-postgres")  # Database
)

response = await agent.run(
    "Read README.md from my main repo and save analysis to database"
)
```

### MCPClient Direct Usage

```python
from logos_adk import Agent, MCPClient

client = MCPClient.from_string("http://localhost:3000/sse")
await client.connect()

# Get available tools
tools = client.get_tools()
print(f"Available tools: {[t.name for t in tools]}")

# Use in agent
agent = Agent("bot", model="...").mcp(client)
```

---

## Streaming

### Basic Streaming

```python
from logos_adk import Agent
from logos_adk.types import TextChunk, ToolStart, ToolEnd, StreamDone

async def stream_response():
    agent = Agent("streamer", model="claude-sonnet-4-20250514")
    
    async for chunk in agent.stream("Tell me a long story"):
        if isinstance(chunk, TextChunk):
            print(chunk.text, end="", flush=True)
        elif isinstance(chunk, ToolStart):
            print(f"\n🔧 Calling {chunk.name}...")
        elif isinstance(chunk, ToolEnd):
            print(f"✓ Done")
        elif isinstance(chunk, StreamDone):
            print(f"\n\nTotal tokens: {chunk.response.usage}")

asyncio.run(stream_response())
```

### Streaming with Callbacks

```python
from logos_adk import Agent

async def on_text(chunk):
    print(chunk.text, end="")

async def on_tool_start(tool_call):
    print(f"\nUsing tool: {tool_call.name}")

async def on_tool_end(result):
    if result.error:
        print(f"Error: {result.error}")
    else:
        print(f"Result: {result.content[:50]}...")

async def on_done(response):
    print(f"\n\nComplete! Tokens: {response.usage}")

agent = Agent("bot", model="...")

response = await agent.run(
    "What's the weather and time?",
    on_text=on_text,
    on_tool_start=on_tool_start,
    on_tool_end=on_tool_end,
    on_done=on_done
)
```

### Streaming to Frontend

```python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json

app = FastAPI()

@app.post("/agent/stream")
async def stream_agent(prompt: str):
    agent = Agent("bot", model="...")
    
    async def generate():
        async for chunk in agent.stream(prompt):
            if hasattr(chunk, 'text') and chunk.text:
                yield f"data: {json.dumps({'text': chunk.text})}\n\n"
    
    return StreamingResponse(generate(), media_type="text/event-stream")
```

---

## Structured Output

### Pydantic Schema

```python
from pydantic import BaseModel
from logos_adk import Agent

class SentimentAnalysis(BaseModel):
    sentiment: str  # positive, negative, neutral
    confidence: float  # 0.0 to 1.0
    keywords: list[str]
    summary: str

agent = (
    Agent("analyst", model="gpt-4o")
    .output_schema(SentimentAnalysis)
)

response = await agent.run("Analyze: This product exceeded my expectations!")

# Access parsed data
parsed: SentimentAnalysis = response.parsed
print(f"Sentiment: {parsed.sentiment}")
print(f"Confidence: {parsed.confidence}")
print(f"Keywords: {parsed.keywords}")
```

### Nested Schema

```python
from pydantic import BaseModel
from typing import Optional

class Address(BaseModel):
    street: str
    city: str
    country: str
    postal_code: str

class Person(BaseModel):
    name: str
    age: int
    email: str
    address: Optional[Address] = None
    interests: list[str]

agent = (
    Agent("extractor", model="gpt-4o")
    .output_schema(Person)
)

response = await agent.run(
    "Extract: John Doe, 30, lives in London, UK. Loves coding and hiking."
)

person: Person = response.parsed
print(person.name)
print(person.address.city if person.address else "No address")
```

### List Extraction

```python
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    category: str

class ItemList(BaseModel):
    items: list[Item]

agent = (
    Agent("extractor", model="gpt-4o")
    .output_schema(ItemList)
)

response = await agent.run(
    "Extract products: Laptop $999 (electronics), Book $20 (education)"
)

items: ItemList = response.parsed
for item in items.items:
    print(f"{item.name}: ${item.price} [{item.category}]")
```

---

## Caching

### Basic Caching

```python
from logos_adk import Agent, InMemoryCacheBackend

backend = InMemoryCacheBackend()

agent = (
    Agent("cached_bot", model="claude-sonnet-4-20250514")
    .cache(backend, ttl=3600)  # 1 hour
)

# First call - hits API
response1 = await agent.run("What is Python?")

# Second call - returns cached response
response2 = await agent.run("What is Python?")
```

### SQLite Cache

```python
from logos_adk import Agent, SQLiteCacheBackend

backend = SQLiteCacheBackend("cache.db")

agent = (
    Agent("persistent_bot", model="gpt-4o")
    .cache(backend, ttl=7200)  # 2 hours
)
```

### Semantic Caching

```python
from logos_adk import Agent, InMemoryCacheBackend

async def embed_fn(text: str) -> list[float]:
    # Use embedding model
    return await model.encode(text)

backend = InMemoryCacheBackend()

agent = (
    Agent("semantic_bot", model="gpt-4o")
    .cache(
        backend,
        ttl=3600,
        semantic=True,
        embed_fn=embed_fn,
        similarity_threshold=0.95
    )
)

# These will match semantically:
await agent.run("How do I make coffee?")
await agent.run("What's the way to brew coffee?")  # Uses cache
```

---

## Observability

### Console Exporter

```python
from logos_adk import Agent, ConsoleExporter

agent = (
    Agent("observable_bot", model="claude-sonnet-4-20250514")
    .observe(ConsoleExporter())
)

response = await agent.run("Hello")
# Prints span and metric info to console
```

### OpenTelemetry Exporter

```python
from logos_adk import Agent, OpenTelemetryExporter

exporter = OpenTelemetryExporter(
    endpoint="http://localhost:4318/v1/traces",
    headers={"x-api-key": "your-key"},
    compression="gzip"
)

agent = (
    Agent("otel_bot", model="gpt-4o")
    .observe(exporter)
)
```

### In-Memory Exporter for Testing

```python
from logos_adk import Agent, InMemoryExporter

exporter = InMemoryExporter()

agent = (
    Agent("test_bot", model="...")
    .observe(exporter)
)

await agent.run("Test prompt")

# Inspect spans and metrics
print(f"Spans: {len(exporter.spans)}")
for span in exporter.spans:
    print(f"  {span.name}: {span.duration_ms}ms")
```

### Custom Exporter

```python
from logos_adk import Exporter, Span, Metric

class CustomExporter(Exporter):
    async def on_span(self, span: Span) -> None:
        # Send to your monitoring system
        await send_to_datadog(span)
    
    async def on_metric(self, metric: Metric) -> None:
        await send_to_datadog(metric)
    
    async def flush(self) -> None:
        pass

exporter = CustomExporter()
agent = Agent("bot", model="...").observe(exporter)
```

---

## Web Server

### Basic Server

```python
from logos_adk import Agent, serve

agent = Agent("api_bot", model="claude-sonnet-4-20250514")

serve(
    agents=[agent],
    host="0.0.0.0",
    port=8000
)
```

### Server with API Key

```python
from logos_adk import Agent, serve

agent = Agent("secure_bot", model="gpt-4o")

serve(
    agents=[agent],
    host="0.0.0.0",
    port=8000,
    api_key="your-secret-key"
)
```

### Multiple Agents

```python
from logos_adk import Agent, serve

agent1 = Agent("assistant", model="claude-sonnet-4-20250514")
agent2 = Agent("coder", model="gpt-4o")
agent3 = Agent("analyst", model="claude-sonnet-4-20250514")

serve(
    agents=[agent1, agent2, agent3],
    port=8000,
    reload=True
)
```

### Programmatic App

```python
from fastapi import FastAPI
from logos_adk import Agent, create_app

agent = Agent("bot", model="...")
app = create_app(agents=[agent], api_key="key")

# Add custom routes
@app.get("/custom")
async def custom():
    return {"status": "ok"}

# Run with uvicorn
# uvicorn module:app --host 0.0.0.0 --port 8000
```

### API Endpoints

```bash
# Run agent
curl -X POST http://localhost:8000/bot/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Hello!"}'

# Stream response
curl -X POST http://localhost:8000/bot/stream \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Tell me a story"}'

# Health check
curl http://localhost:8000/health

# Metrics
curl http://localhost:8000/metrics
```

---

## YAML Configuration

### Basic Config

```yaml
# agent.yaml
name: yaml_bot
model: claude-sonnet-4-20250514
system_prompt: |
  You are a friendly assistant.
  Keep responses concise and helpful.
```

```python
from logos_adk import Agent

agent = Agent.from_config("agent.yaml")
response = await agent.run("Hello!")
```

### Provider Config

```yaml
# agent.yaml
name: ollama_bot
provider:
  type: ollama
  base_url: http://localhost:11434/v1
  model: llama3
system_prompt: You run locally on Ollama.
```

### OpenAI-Compatible

```yaml
name: vllm_bot
provider:
  type: openai-compatible
  base_url: http://localhost:8080/v1
  model: mistral-7b
  api_key: not-needed
system_prompt: You are powered by vLLM.
```

### Anthropic

```yaml
name: claude_bot
provider:
  type: anthropic
  model: claude-sonnet-4-20250514
system_prompt: You use Claude.
```

### With Tools

```yaml
name: tool_bot
model: gpt-4o
system_prompt: Use tools to help users.
tools:
  - search
  - calculator
  - get_weather
```

### With Memory

```yaml
name: memory_bot
model: claude-sonnet-4-20250514
memory:
  session:
    strategy: sliding_window
    max_messages: 20
  long_term:
    backend: sqlite
    path: memory.db
    retrieval: hybrid
    limit: 5
```

### With Guardrails

```yaml
name: safe_bot
model: gpt-4o
guardrails:
  input:
    - type: content_filter
      blocked: [spam, abuse]
    - type: max_length
      max_chars: 1000
  output:
    - type: keyword_guard
      keywords: [I don't know]
      action: override
      override_with: Let me check that.
```

### Full Example

```yaml
# agent.yaml
name: full_bot
provider:
  type: openai-compatible
  base_url: http://localhost:11434/v1
  model: llama3
system_prompt: |
  You are a comprehensive assistant.
  Use tools, remember context, and stay safe.

tools:
  - search
  - calculator

memory:
  session:
    strategy: summarize
    max_messages: 10

guardrails:
  input:
    - type: content_filter
      blocked: [spam]
  output:
    - type: max_length
      max_chars: 500

observe:
  exporter: console

cache:
  backend: sqlite
  path: cache.db
  ttl: 3600
  semantic: true
```

---

## Testing

### Unit Testing

```python
import pytest
from logos_adk import mock_agent, assert_response, assert_tool_called

@pytest.mark.asyncio
async def test_agent_response():
    mock = mock_agent(responses=["Hello!", "How can I help?"])
    
    response = await mock.run("Hi")
    assert_response(response, contains="Hello")

@pytest.mark.asyncio
async def test_tool_usage():
    mock = mock_agent(
        responses=["Let me check the weather"],
        tools=["get_weather"]
    )
    
    await mock.run("What's the weather?")
    assert_tool_called(mock, "get_weather")

@pytest.mark.asyncio
async def test_guardrail_blocking():
    from logos_adk import Agent, ContentFilter
    
    agent = Agent("bot", model="...").input_guardrail(
        ContentFilter(blocked=["spam"])
    )
    
    assert_guardrail_blocked(agent, "spam message")
```

### YAML Testing

```yaml
# tests.yaml
tests:
  - name: greeting
    prompt: Hello!
    expect:
      contains: [hello, hi]
  
  - name: weather_tool
    prompt: What's the weather?
    expect:
      tool_called: get_weather
  
  - name: blocked_content
    prompt: spam message
    expect:
      blocked: true
```

```python
from logos_adk import run_yaml_tests

results = run_yaml_tests("tests.yaml")

for test in results.tests:
    print(f"{test.name}: {'PASS' if test.passed else 'FAIL'}")
```

### Integration Testing

```python
import pytest
from logos_adk import Agent, InMemoryExporter

@pytest.mark.asyncio
async def test_full_flow():
    exporter = InMemoryExporter()
    
    agent = (
        Agent("test_bot", model="...")
        .observe(exporter)
    )
    
    response = await agent.run("Test prompt")
    
    assert response.content
    assert len(exporter.spans) > 0
    assert exporter.spans[0].name == "Agent.run"
```

---

## Hot Reload

```python
from logos_adk import ConfigWatcher, Agent

async def on_reload(config):
    print(f"Config reloaded: {config.name}")
    print(f"New system prompt: {config.system_prompt}")

# Start with config
agent = Agent.from_config("agent.yaml")

# Watch for changes
watcher = ConfigWatcher("agent.yaml", on_reload=on_reload)
await watcher.start()

# Agent auto-reloads when config changes
```

---

## Cost Tracking

```python
from logos_adk import Agent, CostTracker

tracker = CostTracker()

agent = Agent("tracked_bot", model="gpt-4o")

# Track usage
response = await agent.run("Hello")
tracker.track(agent, response)

# Get report
report = tracker.get_report(agent)
print(f"Input tokens: {report.input_tokens}")
print(f"Output tokens: {report.output_tokens}")
print(f"Estimated cost: ${report.estimated_cost}")

# Reset
tracker.reset()
```
