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 strands.tools.mcp.mcp_client import StreamableHTTPTransport

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

    # Connect to the SerpAPI MCP server for web search
    transport = StreamableHTTPTransport(
        url="https://mcp.api.universalapi.co/mcp/mcp-xxx",
        headers={
            "Authorization": f"Bearer {os.environ.get('UNIVERSALAPI_BEARER_TOKEN', '')}"
        }
    )
    mcp_client = MCPClient(transport=transport)

    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_transport = StreamableHTTPTransport(
        url="https://mcp.api.universalapi.co/mcp/mcp-search-id",
        headers={"Authorization": f"Bearer {token}"}
    )
    search_client = MCPClient(transport=search_transport)

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

    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

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.

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
    transport = StreamableHTTPTransport(
        url="https://mcp.api.universalapi.co/mcp/your-mcp-server-id",
        headers={"Authorization": f"Bearer {os.environ.get('UNIVERSALAPI_BEARER_TOKEN', '')}"}
    )
    mcp_client = MCPClient(transport=transport)

    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