From 8330f355361606eb83e2f20c9e5e54c63040f911 Mon Sep 17 00:00:00 2001 From: D3n3bX Date: Tue, 5 May 2026 16:36:16 +0200 Subject: [PATCH] Doc add agents --- app/src/agents/README.md | 1 + app/src/agents/docs/agents/add-agents.md | 91 ++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 app/src/agents/docs/agents/add-agents.md diff --git a/app/src/agents/README.md b/app/src/agents/README.md index a1e0717..25412c2 100644 --- a/app/src/agents/README.md +++ b/app/src/agents/README.md @@ -17,6 +17,7 @@ Each specialized agent inherits from `BaseAgent`, which defines the shared execu ### Agent-specific documentation +- [Add more agents](docs/agents/add-agents-md) - [Chat Agent](docs/agents/chat-agent.md) - [CRM Agent](docs/agents/crm-agent.md) - [Finance Agent](docs/agents/finance-agent.md) diff --git a/app/src/agents/docs/agents/add-agents.md b/app/src/agents/docs/agents/add-agents.md new file mode 100644 index 0000000..1eccccf --- /dev/null +++ b/app/src/agents/docs/agents/add-agents.md @@ -0,0 +1,91 @@ +This guide explains how to expand **Perry's** capabilities by adding new specialized agents. The architecture is designed to be modular, relying on a base class, a centralized factory and a semantic router. + +## 1: Create the agent class + +Every new agent must reside in `ingest-api/app/src/agents/` and inherit from **`BaseAgent`**. You must implement the `act()` method as an **`AsyncGenerator`** to support the "Thinking" (reasoning) steps and the final response. + +**Example: `ingest-api/app/src/agents/marketing_agent.py`** +```python +import json +import re +import ast +import logfire +from contextlib import asynccontextmanager +from typing import Optional, AsyncGenerator, Union +from pydantic import BaseModel, ValidationError, Field +from pydantic_ai import Agent +from src.agents.base_agent import BaseAgent, Message, Failed, AgentState, PerryThought + +class MarketingAgent(BaseAgent): + async def act(self) -> AsyncGenerator[Union[PerryThought, Message, Failed], None]: + # 1. Emit reasoning steps + yield PerryThought(content="Analyzing brand guidelines...", internal_step="check_brand") + + # 2. Logic... + + # 3. Return final result + yield Message( + sender=self.state.agent_name, + receiver="user", + content="Marketing campaign drafted successfully." + ) +``` + +--- + +## 2: Register in the agent factory + +The **`AgentFactory`** handles the instantiation of agents and manages their state to avoid circular imports and unnecessary memory overhead. Update `ingest-api/app/src/services/agent_factory.py`: + +1. Add a new `elif` block for your agent. +2. Import the class **inside** the method to prevent circular dependencies. + +```python +def get_or_create_agent(self, agent_name: str) -> BaseAgent: + if agent_name == "finance_agent": + from src.agents.finance_agent import FinanceAgent + agent_class = FinanceAgent + # ... other agents ... + elif agent_name == "marketing_agent": # Add your new agent here + from src.agents.marketing_agent import MarketingAgent + agent_class = MarketingAgent + else: + raise ValueError(f"Agent '{agent_name}' is not registered.") + + state = AgentState(agent_name=agent_name) + self.instances[agent_name] = agent_class(state=state) + return self.instances[agent_name] +``` + +## 3: Configure semantic routing + +To ensure Perry knows when to trigger your new agent, you must configure the **Semantic Router**. + +### A. Update `utterances.json` +Add a list of example phrases (utterances) that describe the user's intent when they want to talk to your new agent. + +```json +{ + "marketing_agent": [ + "Create a marketing campaign", + "Draft a social media post", + "What is our current brand strategy?", + "Help me with marketing materials" + ] +} +``` + +### B. Update `semantic_router_service.py` +Define the new route and add it to the router's active layers. + +```python +# 1. Define the route using the JSON data +marketing_route = Route( + name="marketing_agent", + utterances=utterances_data["marketing_agent"] +) + +# 2. Add it to the routes list +routes = [finance_route, crm_route, calendar_route, chat_route, marketing_route] +route_layer = SemanticRouter(encoder=encoder, routes=routes, auto_sync="local") +``` \ No newline at end of file