Skip to content

Creating Agents ​

Complete guide to building AI agents on Universal API.

Source Code Structure ​

Every agent must define a create_agent() function that returns a tuple of (agent, mcp_clients):

python
import os
from strands import Agent
from strands.models.bedrock import BedrockModel

def create_agent():
    model = BedrockModel(
        model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
        region_name="us-east-1"
    )
    agent = Agent(
        model=model,
        system_prompt="You are a helpful assistant.",
        session_manager=session_manager  # Required for multi-turn conversations
    )
    return agent, []  # (agent, list_of_mcp_clients)

WARNING

The function must be named create_agent and must return a tuple of (agent, mcp_clients). The mcp_clients list is used for cleanup — return [] if you don't use MCP tools.

Choosing a Model ​

Use any AWS Bedrock model via cross-region inference profiles:

python
# Claude Sonnet 4 (recommended — best balance of quality and cost)
model = BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0", region_name="us-east-1")

# Claude 3.5 Haiku (fast and cheap)
model = BedrockModel(model_id="us.anthropic.claude-3-5-haiku-20241022-v1:0", region_name="us-east-1")

# Nova Pro (good value)
model = BedrockModel(model_id="us.amazon.nova-pro-v1:0", region_name="us-east-1")

# Nova Micro (ultra-cheap for simple tasks)
model = BedrockModel(model_id="us.amazon.nova-micro-v1:0", region_name="us-east-1")

Platform Bedrock vs. BYO Keys ​

  • Platform Bedrock (default): If you don't provide AWS keys, the platform uses its own Bedrock access. Cost is billed as credits. No AWS account needed.
  • BYO Keys: Store your AWS credentials at universalapi.co/keys to use your own Bedrock access. You pay AWS directly.

System Prompts ​

Define your agent's personality and behavior:

python
agent = Agent(
    model=model,
    system_prompt="""You are a senior software engineer assistant.

Rules:
- Always explain your reasoning
- Provide code examples when helpful
- Ask clarifying questions if the request is ambiguous
- Be concise but thorough"""
)

Adding Tools ​

MCP Server Tools ​

Connect your agent to any MCP server's tools:

python
import os
from strands import Agent
from strands.models.bedrock import BedrockModel
from strands.tools.mcp import MCPClient
from mcp.client.streamable_http import streamablehttp_client

def create_agent():
    bearer_token = os.environ.get('UNIVERSALAPI_BEARER_TOKEN', '')

    model = BedrockModel(
        model_id="us.anthropic.claude-sonnet-4-6",
        region_name="us-east-1"
    )

    # Connect to the SerpAPI MCP server for web search
    mcp_client = MCPClient(
        lambda: streamablehttp_client(
            "https://mcp.api.universalapi.co/mcp/mcp-xxx",
            headers={"Authorization": f"Bearer {bearer_token}"}
        )
    )

    agent = Agent(
        model=model,
        system_prompt="You are a research assistant with web search capabilities.",
        tools=[mcp_client],
        session_manager=session_manager  # Required for multi-turn conversations
    )
    # Return mcp_client in the list so it gets cleaned up properly
    return agent, [mcp_client]

Multiple MCP Servers ​

Connect to multiple servers for more capabilities:

python
def create_agent():
    model = BedrockModel(
        model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
        region_name="us-east-1"
    )

    token = os.environ.get('UNIVERSALAPI_BEARER_TOKEN', '')

    # Web search
    search_client = MCPClient(
        lambda: streamablehttp_client(
            "https://mcp.api.universalapi.co/mcp/mcp-search-id",
            headers={"Authorization": f"Bearer {token}"}
        )
    )

    # Google Suite (Gmail, Calendar, Drive)
    google_client = MCPClient(
        lambda: streamablehttp_client(
            "https://mcp.api.universalapi.co/mcp/mcp-google-id",
            headers={"Authorization": f"Bearer {token}"}
        )
    )

    agent = Agent(
        model=model,
        system_prompt="You are a personal assistant with search and Google Suite access.",
        tools=[search_client, google_client],
        session_manager=session_manager  # Required for multi-turn conversations
    )
    return agent, [search_client, google_client]

Custom Python Tools ​

Define tools directly in your agent code using the @tool decorator:

python
from strands import Agent, tool
from strands.models.bedrock import BedrockModel

@tool
def calculate(expression: str) -> str:
    """Evaluate a mathematical expression.

    Args:
        expression: A math expression like '2 + 2' or 'sqrt(16)'
    """
    import math
    result = eval(expression, {"__builtins__": {}}, vars(math))
    return str(result)

@tool
def get_current_time() -> str:
    """Get the current UTC time."""
    from datetime import datetime, timezone
    return datetime.now(timezone.utc).isoformat()

def create_agent():
    model = BedrockModel(
        model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
        region_name="us-east-1"
    )
    agent = Agent(
        model=model,
        system_prompt="You are a helpful assistant with math and time tools.",
        tools=[calculate, get_current_time],
        session_manager=session_manager  # Required for multi-turn conversations
    )
    return agent, []

Tool Best Practices

  • Always include a docstring — the LLM uses it to decide when to call the tool
  • Use type hints for parameters
  • Return strings — the LLM reads the return value
  • Handle errors gracefully — return error messages instead of raising exceptions

Attaching Skills ​

Skills are reusable instruction sets that agents load on demand. Use the AgentSkills plugin to attach them:

python
from strands.vended_plugins.skills import AgentSkills

def create_agent():
    model = BedrockModel(model_id="us.amazon.nova-pro-v1:0", region_name="us-east-1")
    agent = Agent(
        model=model,
        session_manager=session_manager,
        plugins=[AgentSkills(skills=[
            "https://api.universalapi.co/skills/s/snowtimber/concise-answers/SKILL.md",
        ])],
    )
    return agent, []

The plugin injects available skill metadata into the system prompt and registers a skills tool. The agent activates skills on demand — only loaded skills consume tokens.

Browse available skills at universalapi.co/skills or search via GET /search?q=concise&type=skill. See the Skills guide for creating your own.

Accessing User API Keys ​

When users store API keys on the platform, they're available to your agent:

python
import os
import json

def create_agent():
    # All user keys are available as a JSON dict
    keys_json = os.environ.get('UAPI_KEYS_JSON', '{}')
    keys = json.loads(keys_json)

    serpapi_key = keys.get('serpapi', '')
    openai_key = keys.get('openai', '')

    # Use keys in your agent logic...

Session Management ​

CRITICAL — Always include session_manager=session_manager

Without this parameter, your agent cannot maintain multi-turn conversations. Each message will start fresh with no memory of previous turns.

This is conversation memory — not request lineage

session_manager persists LLM chat history (messages in/out) within a single agent conversation. It has nothing to do with cross-agent tracing or MCP log correlation. For request lineage (tracking voice → text → MCP chains), see Request Lineage & Tracing.

The UAPI runtime pre-injects a session_manager variable into your agent's execution environment. You don't need to create it — just pass it through to the Agent() constructor:

python
def create_agent():
    model = BedrockModel(
        model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
        region_name="us-east-1"
    )
    agent = Agent(
        model=model,
        system_prompt="You are a helpful assistant.",
        session_manager=session_manager  # ← This is pre-injected by the runtime
    )
    return agent, []

How it works under the hood:

  1. The platform creates an S3SessionManager instance for each conversation
  2. When a user sends a conversationId, the session manager loads the full conversation history
  3. After each response, the updated history is saved back to S3
  4. The session_manager variable is available in your create_agent() scope automatically — no import needed

See Session Management for details on the API consumer side (conversationId, listing conversations, etc.).

Deploying via API ​

bash
curl -s -X POST https://api.universalapi.co/agent/create \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agentName": "my-agent",
    "description": "Description of what the agent does",
    "sourceCode": "...",
    "visibility": "public"
  }' | jq

Deploying via Python Script ​

For complex agents, use a Python script:

python
import requests
import json

TOKEN = "uapi_ut_xxxx_your_token"
API = "https://api.universalapi.co"

source_code = '''
import os
from strands import Agent
from strands.models.bedrock import BedrockModel

def create_agent():
    model = BedrockModel(
        model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
        region_name="us-east-1"
    )
    agent = Agent(
        model=model,
        system_prompt="You are a helpful assistant.",
        session_manager=session_manager
    )
    return agent, []
'''

response = requests.post(
    f"{API}/agent/create",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json"
    },
    json={
        "agentName": "my-agent",
        "description": "A helpful assistant",
        "sourceCode": source_code,
        "visibility": "private"
    }
)
print(json.dumps(response.json(), indent=2))

Updating an Agent ​

bash
curl -s -X PUT https://api.universalapi.co/agent/update \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "agent-xxx",
    "description": "Updated description",
    "sourceCode": "..."
  }' | jq

Available Imports ​

Agent source code runs in a Python 3.12 environment with these packages available:

  • strands — Strands Agent SDK
  • boto3 / botocore — AWS SDK
  • requests — HTTP library
  • json, os, datetime, math, re, uuid — Python stdlib
  • All packages from the Strands Lambda layer

Security Restrictions ​

  • Agents run in isolated Lambda containers
  • No filesystem write access
  • No network access except to allowed endpoints
  • Environment variables are sandboxed per-invocation
  • Author credentials are never injected into agent code — only into MCP servers
  • Platform AWS credentials (Bedrock, DynamoDB) are hidden from agent code

Key Isolation Model ​

Agents can only access user keys (the caller's own stored API keys). Author keys from authorRoleToken are never injected into the agent runtime.

Environment VariableAvailable to Agents?Description
UAPI_KEYS_JSONYesCaller's own API keys (JSON)
UNIVERSALAPI_BEARER_TOKENYesCaller's token for MCP server connections
BEDROCK_MODEL_IDYesModel override (if specified)
UAPI_AUTHOR_KEYS_JSONNoAuthor keys — MCP servers only

If your agent needs to use author-provided API credentials (e.g., a SerpAPI key you own), the pattern is:

  1. Create an MCP server with your authorRoleToken set
  2. Have your agent connect to that MCP server via MCPClient
  3. The agent invokes tools and gets results — without ever seeing your API keys
python
def create_agent():
    # Agent connects to your MCP server — author keys are resolved
    # by the MCP runtime, never visible to this code
    mcp_client = MCPClient(
        lambda: streamablehttp_client(
            "https://mcp.api.universalapi.co/mcp/your-mcp-server-id",
            headers={"Authorization": f"Bearer {os.environ.get('UNIVERSALAPI_BEARER_TOKEN', '')}"}
        )
    )

    agent = Agent(model=model, tools=[mcp_client], session_manager=session_manager)
    return agent, [mcp_client]

Universal API - The agentic entry point to the universe of APIs