forked from cleveragents/cleveragents-core
600 lines
18 KiB
ReStructuredText
600 lines
18 KiB
ReStructuredText
==============
|
|
Advanced Usage
|
|
==============
|
|
|
|
Agent Configuration
|
|
===================
|
|
|
|
Agents are defined in the ``agents`` section of your configuration file. Here's an overview of common configuration for the built-in ``llm`` agent type.
|
|
|
|
LLMAgent
|
|
~~~~~~~~
|
|
|
|
The ``LLMAgent`` is powered by a large language model. Its configuration includes:
|
|
|
|
- ``provider``: The LLM provider to use (e.g., ``openai``, ``anthropic``).
|
|
- ``model``: The specific model to use (e.g., ``gpt-4``, ``claude-3-opus-20240229``).
|
|
- ``role``: An inline string defining the agent's role or system message.
|
|
- ``role_reference``: The name of a template from the top-level ``prompts`` section to use as the role. Cannot be used with ``role``.
|
|
- ``prompt``: An inline template for formatting the user's input message.
|
|
- ``prompt_reference``: The name of a template from ``prompts`` to format the user's input. Cannot be used with ``prompt``.
|
|
- ``response``: An inline template for formatting the LLM's output.
|
|
- ``response_reference``: The name of a template from ``prompts`` to format the LLM's output. Cannot be used with ``response``.
|
|
- ``api_key``: (Optional) The API key for the provider. If not provided, it will be read from the corresponding environment variable (e.g., ``OPENAI_API_KEY``).
|
|
|
|
Example:
|
|
|
|
.. code-block:: yaml
|
|
|
|
agents:
|
|
researcher:
|
|
type: llm
|
|
config:
|
|
provider: openai
|
|
model: gpt-4
|
|
role: "You are a research assistant."
|
|
prompt_reference: research_task
|
|
temperature: 0.7
|
|
|
|
prompts:
|
|
research_task:
|
|
content: "Research the topic: {{ message }}"
|
|
|
|
Custom Agent Implementation
|
|
==========================
|
|
|
|
This section covers advanced usage scenarios for CleverAgents.
|
|
|
|
Custom Agent Implementation
|
|
==========================
|
|
|
|
Creating custom agents allows you to extend CleverAgents with specialized functionality.
|
|
|
|
Basic Custom Agent
|
|
----------------
|
|
|
|
Here's how to create a basic custom agent:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.agents.base import Agent
|
|
|
|
class MyCustomAgent(Agent):
|
|
def __init__(self, name, config, template_renderer):
|
|
super().__init__(name, config, template_renderer)
|
|
# Initialize your custom agent
|
|
self.custom_parameter = config.get("custom_parameter", "default")
|
|
|
|
async def process(self, message, context=None):
|
|
# Process the message
|
|
return f"Custom agent processed: {message}"
|
|
|
|
def get_capabilities(self):
|
|
return ["custom_processing"]
|
|
|
|
# Register the custom agent with the factory
|
|
from cleveragents.agents.factory import AgentFactory
|
|
|
|
factory = AgentFactory(config, template_renderer)
|
|
factory.register_agent_type("custom", MyCustomAgent)
|
|
|
|
# Create an instance of the custom agent
|
|
custom_agent = factory.create_agent("my_custom_agent")
|
|
|
|
Stateful Custom Agent
|
|
-------------------
|
|
|
|
For agents that need to maintain state:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.agents.base import AgentWithMemory
|
|
|
|
class MyStatefulAgent(AgentWithMemory):
|
|
def __init__(self, name, config, template_renderer):
|
|
super().__init__(name, config, template_renderer)
|
|
# Initialize memory
|
|
self.memory = {
|
|
"conversation_count": 0,
|
|
"last_message": None
|
|
}
|
|
|
|
async def process(self, message, context=None):
|
|
# Update memory
|
|
self.memory["conversation_count"] += 1
|
|
self.memory["last_message"] = message
|
|
|
|
# Process the message
|
|
return f"Message {self.memory['conversation_count']}: {message}"
|
|
|
|
def get_capabilities(self):
|
|
return ["stateful_processing"]
|
|
|
|
def save_memory(self):
|
|
# Return the memory for persistence
|
|
return self.memory
|
|
|
|
def load_memory(self, memory):
|
|
# Load the memory from persistence
|
|
self.memory = memory
|
|
|
|
Custom Tools
|
|
===========
|
|
|
|
You can create custom tools to extend the functionality of tool agents.
|
|
|
|
Basic Custom Tool
|
|
--------------
|
|
|
|
Here's how to create a basic custom tool:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.agents.tool import Tool
|
|
|
|
class WeatherTool(Tool):
|
|
def __init__(self, config=None):
|
|
super().__init__(
|
|
name="weather",
|
|
description="Gets weather information for a location",
|
|
config=config or {}
|
|
)
|
|
self.api_key = self.config.get("api_key", "")
|
|
|
|
async def execute(self, input_data, context=None):
|
|
# Parse the location from the input
|
|
location = input_data.strip()
|
|
|
|
# In a real implementation, you would call a weather API here
|
|
# For this example, we'll return a mock response
|
|
return f"The weather in {location} is sunny with a temperature of 25°C."
|
|
|
|
# Create an instance of the custom tool
|
|
weather_tool = WeatherTool({"api_key": "your-api-key"})
|
|
|
|
# Use the tool
|
|
result = await weather_tool.execute("New York")
|
|
# Result: "The weather in New York is sunny with a temperature of 25°C."
|
|
|
|
Integrating Custom Tools
|
|
---------------------
|
|
|
|
To use custom tools in a tool agent:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.agents.tool import ToolAgent
|
|
|
|
# Create a tool agent with custom tools
|
|
tool_agent = ToolAgent(
|
|
name="tools",
|
|
config={
|
|
"tools": [
|
|
{
|
|
"name": "weather",
|
|
"description": "Gets weather information for a location",
|
|
"class": "path.to.WeatherTool",
|
|
"config": {
|
|
"api_key": "your-api-key"
|
|
}
|
|
}
|
|
]
|
|
},
|
|
template_renderer=template_renderer
|
|
)
|
|
|
|
# Process a message
|
|
response = await tool_agent.process("What's the weather in New York?")
|
|
|
|
Advanced Routing
|
|
==============
|
|
|
|
CleverAgents supports advanced routing scenarios for complex agent networks.
|
|
|
|
Conditional Routing and Transformation
|
|
--------------------------------------
|
|
|
|
Route messages based on conditions and transform the message payload using Jinja2 templates.
|
|
|
|
.. code-block:: yaml
|
|
|
|
routes:
|
|
main_workflow:
|
|
- source: input
|
|
destination: classifier
|
|
transform: "{{ context.get('initial_message', message) }}"
|
|
|
|
- source: classifier
|
|
destination: calculator
|
|
condition: "'CALCULATION' in message"
|
|
transform: "{{ context.get('initial_message', message) }}"
|
|
|
|
- source: calculator
|
|
destination: responder
|
|
transform: |-
|
|
The result of {{ context.history[-1].message }} is {{ message }}.
|
|
|
|
Dynamic Routing
|
|
------------
|
|
|
|
Implement dynamic routing based on message content:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.routing.router import Router
|
|
|
|
class DynamicRouter(Router):
|
|
async def route_message(self, message, source="input", context=None):
|
|
# Analyze the message to determine the destination
|
|
if "weather" in message.lower():
|
|
destination = "weather_agent"
|
|
elif "news" in message.lower():
|
|
destination = "news_agent"
|
|
elif "calculate" in message.lower():
|
|
destination = "calculator_agent"
|
|
else:
|
|
destination = "general_agent"
|
|
|
|
# Route to the determined destination
|
|
if destination in self.agents:
|
|
return await self.agents[destination].process(message, context)
|
|
else:
|
|
return f"No agent found for: {destination}"
|
|
|
|
Advanced Templates
|
|
================
|
|
|
|
CleverAgents supports advanced template features for complex prompt engineering.
|
|
|
|
Template Inheritance
|
|
-----------------
|
|
|
|
Create template hierarchies:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.templates.renderer import TemplateRenderer, TemplateEngine
|
|
|
|
# Create a template renderer
|
|
renderer = TemplateRenderer(engine_type=TemplateEngine.JINJA2)
|
|
|
|
# Register a base template
|
|
renderer.register_template(
|
|
"base_prompt",
|
|
"""
|
|
You are a helpful assistant.
|
|
|
|
User: {message}
|
|
|
|
Assistant:
|
|
"""
|
|
)
|
|
|
|
# Register a specialized template that extends the base
|
|
renderer.register_template(
|
|
"expert_prompt",
|
|
"""
|
|
{% extends "base_prompt" %}
|
|
{% block preamble %}
|
|
You are an expert in {domain}.
|
|
{% endblock %}
|
|
"""
|
|
)
|
|
|
|
# Render the specialized template
|
|
prompt = renderer.render(
|
|
"expert_prompt",
|
|
{"message": "How does quantum computing work?", "domain": "quantum physics"}
|
|
)
|
|
|
|
Template Includes
|
|
--------------
|
|
|
|
Include templates within other templates:
|
|
|
|
.. code-block:: python
|
|
|
|
# Register component templates
|
|
renderer.register_template(
|
|
"header",
|
|
"# {title}\n\n"
|
|
)
|
|
|
|
renderer.register_template(
|
|
"footer",
|
|
"\n\nRespond in a {tone} tone."
|
|
)
|
|
|
|
# Register a template that includes components
|
|
renderer.register_template(
|
|
"complete_prompt",
|
|
"""
|
|
{% include "header" with {"title": "Expert Consultation"} %}
|
|
|
|
You are an expert in {domain}.
|
|
|
|
User: {message}
|
|
|
|
Assistant:
|
|
|
|
{% include "footer" with {"tone": "professional"} %}
|
|
"""
|
|
)
|
|
|
|
# Render the complete template
|
|
prompt = renderer.render(
|
|
"complete_prompt",
|
|
{"message": "How does quantum computing work?", "domain": "quantum physics"}
|
|
)
|
|
|
|
Conditional Templates
|
|
-----------------
|
|
|
|
Use conditions in templates:
|
|
|
|
.. code-block:: python
|
|
|
|
# Register a template with conditions
|
|
renderer.register_template(
|
|
"conditional_prompt",
|
|
"""
|
|
You are a helpful assistant.
|
|
|
|
{% if context.user_level == "beginner" %}
|
|
Please provide a simple explanation suitable for beginners.
|
|
{% elif context.user_level == "intermediate" %}
|
|
Please provide a detailed explanation with some technical terms.
|
|
{% else %}
|
|
Please provide an advanced technical explanation.
|
|
{% endif %}
|
|
|
|
User: {message}
|
|
|
|
Assistant:
|
|
"""
|
|
)
|
|
|
|
# Render with different contexts
|
|
beginner_prompt = renderer.render(
|
|
"conditional_prompt",
|
|
{"message": "How does quantum computing work?", "user_level": "beginner"}
|
|
)
|
|
|
|
expert_prompt = renderer.render(
|
|
"conditional_prompt",
|
|
{"message": "How does quantum computing work?", "user_level": "expert"}
|
|
)
|
|
|
|
Distributed Agent Networks
|
|
========================
|
|
|
|
CleverAgents can be used to create distributed agent networks across multiple machines.
|
|
|
|
Agent Network Distribution
|
|
-----------------------
|
|
|
|
Distribute agents across multiple machines:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.network import AgentNetwork
|
|
from cleveragents.distribution import RemoteAgent
|
|
|
|
# Create a network
|
|
network = AgentNetwork()
|
|
|
|
# Add a local agent
|
|
network.add_agent(local_agent)
|
|
|
|
# Add a remote agent
|
|
remote_agent = RemoteAgent(
|
|
name="remote_assistant",
|
|
endpoint="http://remote-server:8000/agent",
|
|
api_key="your-api-key"
|
|
)
|
|
network.add_agent(remote_agent)
|
|
|
|
# Define connections
|
|
router = network.get_router()
|
|
router.add_route({
|
|
"from": "input",
|
|
"to": "local_agent",
|
|
"condition": "true"
|
|
})
|
|
router.add_route({
|
|
"from": "local_agent",
|
|
"to": "remote_assistant",
|
|
"condition": "true"
|
|
})
|
|
router.add_route({
|
|
"from": "remote_assistant",
|
|
"to": "output",
|
|
"condition": "true"
|
|
})
|
|
|
|
Remote Agent Server
|
|
----------------
|
|
|
|
Create a server for remote agents:
|
|
|
|
.. code-block:: python
|
|
|
|
from fastapi import FastAPI, HTTPException, Depends
|
|
from pydantic import BaseModel
|
|
from cleveragents.network import AgentNetwork
|
|
|
|
app = FastAPI()
|
|
|
|
# Create a network with agents
|
|
network = AgentNetwork()
|
|
|
|
class MessageRequest(BaseModel):
|
|
message: str
|
|
context: dict = None
|
|
agent: str
|
|
|
|
@app.post("/agent")
|
|
async def process_message(request: MessageRequest):
|
|
try:
|
|
# Get the specified agent
|
|
agent = network.get_agent(request.agent)
|
|
if not agent:
|
|
raise HTTPException(status_code=404, detail=f"Agent {request.agent} not found")
|
|
|
|
# Process the message
|
|
response = await agent.process(request.message, request.context)
|
|
|
|
return {"response": response}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
|
Performance Optimization
|
|
======================
|
|
|
|
Optimize CleverAgents for better performance.
|
|
|
|
Caching
|
|
------
|
|
|
|
Implement caching to reduce redundant processing:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.agents.base import Agent
|
|
from functools import lru_cache
|
|
|
|
class CachedAgent(Agent):
|
|
def __init__(self, name, config, template_renderer):
|
|
super().__init__(name, config, template_renderer)
|
|
self.cache_size = config.get("cache_size", 100)
|
|
self.process_with_cache = lru_cache(maxsize=self.cache_size)(self._process_uncached)
|
|
|
|
async def process(self, message, context=None):
|
|
# Convert context to a hashable form for caching
|
|
hashable_context = None
|
|
if context:
|
|
hashable_context = tuple(sorted((k, str(v)) for k, v in context.items()))
|
|
|
|
return await self.process_with_cache(message, hashable_context)
|
|
|
|
async def _process_uncached(self, message, hashable_context):
|
|
# Convert hashable context back to a dict
|
|
context = None
|
|
if hashable_context:
|
|
context = {k: v for k, v in hashable_context}
|
|
|
|
# Actual processing logic
|
|
return f"Processed: {message}"
|
|
|
|
def get_capabilities(self):
|
|
return ["cached_processing"]
|
|
|
|
Batching
|
|
-------
|
|
|
|
Batch process messages for better throughput:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.agents.base import Agent
|
|
import asyncio
|
|
|
|
class BatchAgent(Agent):
|
|
def __init__(self, name, config, template_renderer):
|
|
super().__init__(name, config, template_renderer)
|
|
self.batch_size = config.get("batch_size", 10)
|
|
self.batch_timeout = config.get("batch_timeout", 1.0)
|
|
self.batch = []
|
|
self.batch_lock = asyncio.Lock()
|
|
self.batch_event = asyncio.Event()
|
|
self.results = {}
|
|
self.next_id = 0
|
|
|
|
# Start the batch processor
|
|
asyncio.create_task(self._process_batches())
|
|
|
|
async def process(self, message, context=None):
|
|
async with self.batch_lock:
|
|
# Assign an ID to this request
|
|
request_id = self.next_id
|
|
self.next_id += 1
|
|
|
|
# Add to the batch
|
|
self.batch.append((request_id, message, context))
|
|
|
|
# Signal that a new item is in the batch
|
|
self.batch_event.set()
|
|
|
|
# Wait for the result
|
|
while request_id not in self.results:
|
|
await asyncio.sleep(0.1)
|
|
|
|
# Get and remove the result
|
|
result = self.results.pop(request_id)
|
|
return result
|
|
|
|
async def _process_batches(self):
|
|
while True:
|
|
# Wait for items in the batch
|
|
await self.batch_event.wait()
|
|
|
|
# Wait for more items or timeout
|
|
await asyncio.sleep(self.batch_timeout)
|
|
|
|
# Get the current batch
|
|
async with self.batch_lock:
|
|
current_batch = self.batch[:self.batch_size]
|
|
self.batch = self.batch[self.batch_size:]
|
|
|
|
# Reset the event if the batch is empty
|
|
if not self.batch:
|
|
self.batch_event.clear()
|
|
|
|
# Process the batch
|
|
if current_batch:
|
|
batch_results = await self._process_batch(current_batch)
|
|
|
|
# Store the results
|
|
for request_id, result in batch_results:
|
|
self.results[request_id] = result
|
|
|
|
async def _process_batch(self, batch):
|
|
# Process the batch and return results
|
|
# This is where you would implement your batch processing logic
|
|
return [(request_id, f"Batch processed: {message}") for request_id, message, _ in batch]
|
|
|
|
def get_capabilities(self):
|
|
return ["batch_processing"]
|
|
|
|
Parallel Processing
|
|
----------------
|
|
|
|
Process messages in parallel:
|
|
|
|
.. code-block:: python
|
|
|
|
from cleveragents.agents.base import Agent
|
|
import asyncio
|
|
|
|
class ParallelAgent(Agent):
|
|
def __init__(self, name, config, template_renderer):
|
|
super().__init__(name, config, template_renderer)
|
|
self.max_parallel = config.get("max_parallel", 10)
|
|
self.semaphore = asyncio.Semaphore(self.max_parallel)
|
|
|
|
async def process(self, message, context=None):
|
|
async with self.semaphore:
|
|
# Process the message with a limit on parallel executions
|
|
return await self._process_message(message, context)
|
|
|
|
async def _process_message(self, message, context=None):
|
|
# Actual processing logic
|
|
return f"Processed: {message}"
|
|
|
|
def get_capabilities(self):
|
|
return ["parallel_processing"]
|