Getting Started with Vel

Complete guide to installing and using Vel for the first time.

Installation

Prerequisites

  • Python 3.10 or higher
  • pip

Install from Source

# Clone the repository
git clone <repo-url>
cd vel

# Install in development mode
pip install -e .

# Optional: Install dev dependencies
pip install -e ".[dev]"

Configuration

Environment Variables

Create a .env file in the project root:

# Copy the example
cp .env.example .env

# Edit with your API keys
vim .env

Required Variables

# For OpenAI
OPENAI_API_KEY=sk-...

# OR for Google Gemini
GOOGLE_API_KEY=...

# OR for Anthropic Claude
ANTHROPIC_API_KEY=sk-ant-...

Optional Variables

# Database (for persistent sessions)
POSTGRES_DSN=postgresql+psycopg://user:pass@localhost:5432/vel

# Redis (for caching)
REDIS_URL=redis://localhost:6379/0

# OpenAI Custom Endpoint
OPENAI_API_BASE=https://api.openai.com/v1

Note: If POSTGRES_DSN and REDIS_URL are not set, Vel will use in-memory storage (fine for development).

API Key Configuration Methods

Vel supports two ways to provide API keys, making it suitable for both applications and libraries:

Set environment variables as shown above. Agents will automatically use them:

agent = Agent(
    id='my-agent',
    model={'provider': 'openai', 'model': 'gpt-4o'}
)
# Uses OPENAI_API_KEY from environment

Pros:

  • ✓ Secure (not in code)
  • ✓ Easy for development
  • ✓ Standard practice

Cons:

  • ✗ Not suitable for libraries imported by others
  • ✗ Can’t use different keys for different agents

Pass API keys directly in the model config:

agent = Agent(
    id='my-agent',
    model={
        'provider': 'openai',
        'model': 'gpt-4o',
        'api_key': 'sk-...'  # Override environment variable
    }
)

Pros:

  • ✓ Works in any environment
  • ✓ Suitable for installable packages
  • ✓ Different agents can use different keys
  • ✓ Perfect for multi-tenant applications

Cons:

  • ✗ Must manage secrets carefully

Use Cases

Scenario Method
Building an application Environment variables
Building a library/package Explicit API keys
Multi-tenant SaaS Explicit API keys (per-tenant)
Development/testing Environment variables
CI/CD Environment variables

Quick Start

Basic Usage

import asyncio
from dotenv import load_dotenv
from vel import Agent

load_dotenv()

async def main():
    # Create an agent
    agent = Agent(
        id='my-agent',
        model={'provider': 'openai', 'model': 'gpt-4o'}
    )

    # Non-streaming mode
    answer = await agent.run({'message': 'Hello, how are you?'})
    print(answer)

if __name__ == '__main__':
    asyncio.run(main())

Streaming Mode

async def streaming_example():
    agent = Agent(
        id='my-agent',
        model={'provider': 'openai', 'model': 'gpt-4o'}
    )

    # Stream events as they arrive
    async for event in agent.run_stream({'message': 'Tell me a joke'}):
        print(event)

With Sessions (Multi-turn)

async def session_example():
    agent = Agent(
        id='my-agent',
        model={'provider': 'openai', 'model': 'gpt-4o'},
        session_storage='memory'  # or 'database'
    )

    session_id = 'user-123'

    # First turn
    answer1 = await agent.run(
        {'message': 'My name is Alice'},
        session_id=session_id
    )
    print(answer1)

    # Second turn - remembers Alice
    answer2 = await agent.run(
        {'message': 'What is my name?'},
        session_id=session_id
    )
    print(answer2)  # "Your name is Alice"

With Tools

from vel import Agent, ToolSpec, register_tool

# Define a custom tool
def get_weather_handler(input: dict, ctx: dict) -> dict:
    city = input['city']
    return {'temp_f': 72, 'condition': 'sunny', 'city': city}

weather_tool = ToolSpec(
    name='get_weather',
    input_schema={
        'type': 'object',
        'properties': {'city': {'type': 'string'}},
        'required': ['city']
    },
    output_schema={
        'type': 'object',
        'properties': {
            'temp_f': {'type': 'number'},
            'condition': {'type': 'string'},
            'city': {'type': 'string'}
        },
        'required': ['temp_f', 'condition', 'city']
    },
    handler=get_weather_handler
)

register_tool(weather_tool)

# Use the agent with tools
async def tool_example():
    agent = Agent(
        id='my-agent',
        model={'provider': 'openai', 'model': 'gpt-4o'},
        tools=['get_weather']
    )

    answer = await agent.run({'message': 'What is the weather in New York?'})
    print(answer)  # Agent will call the tool and respond

Storing Messages for Database

Use MessageReducer to aggregate streaming events into structured messages compatible with the Vercel AI SDK format:

from vel import Agent, MessageReducer

async def database_storage_example():
    """Aggregate streaming events for database storage"""
    # Create reducer
    reducer = MessageReducer()

    # Add user message
    user_msg = reducer.add_user_message(
        "What's the weather in San Francisco?",
        metadata={"user_id": "user-123", "timestamp": "2024-01-15T10:00:00Z"}
    )

    # Stream agent response
    agent = Agent(
        id='weather-agent',
        model={'provider': 'openai', 'model': 'gpt-4o'},
        tools=['get_weather']
    )

    async for event in agent.run_stream({'message': "What's the weather in SF?"}):
        reducer.process_event(event)

    # Get messages in Vercel AI SDK format
    messages = reducer.get_messages(
        assistant_metadata={"model": "gpt-4o"}
    )
    # [
    #   {user message},
    #   {assistant message with parts: [tool-call, tool-result, text]}
    # ]

    # Store in database
    for msg in messages:
        await db.insert_message(msg)

Key Features:

  • ✓ Compatible with Vercel AI SDK useChat hook
  • ✓ Aggregates text, tool calls, and tool results into parts array
  • ✓ Includes provider metadata (OpenAI message/call IDs)
  • ✓ Supports custom message IDs and metadata
  • ✓ Ready for database storage

See Stream Protocol - Message Aggregation for complete details.

With Generation Configuration

Control model behavior with fine-grained parameters:

from vel import Agent

async def generation_config_example():
    # Agent with default config
    agent = Agent(
        id='my-agent',
        model={'provider': 'openai', 'model': 'gpt-4o'},
        generation_config={
            'temperature': 0.7,  # Creativity
            'max_tokens': 500    # Output limit
        }
    )

    # Use default config
    creative = await agent.run({'message': 'Write a poem'})
    print(creative)

    # Override for specific run
    factual = await agent.run(
        {'message': 'What is 2+2?'},
        generation_config={'temperature': 0}  # Deterministic for this run
    )
    print(factual)

Common Parameters:

  • temperature - Creativity (0-2)
  • max_tokens - Output length limit
  • top_p - Nucleus sampling (0-1)
  • seed - Reproducible outputs (OpenAI, Anthropic)

See Providers for all parameters.

REST API

Start the Service

# Start with uvicorn
uvicorn agents_service.main:app --reload

# Or with custom host/port
uvicorn agents_service.main:app --host 0.0.0.0 --port 8000

Streaming Endpoint

curl -X POST http://localhost:8000/runs \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "chat-general:v1",
    "provider": "openai",
    "model": "gpt-4o",
    "input": {"message": "hello"}
  }'

Non-Streaming Endpoint

curl -X POST http://localhost:8000/runs/sync \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "chat-general:v1",
    "provider": "google",
    "model": "gemini-1.5-pro",
    "input": {"message": "hello"}
  }'

Architecture Overview

Vel uses a two-layer architecture based on the Single Responsibility Principle:

Layer 1: Translators (Protocol Adapters)

Responsibility: Convert provider-specific events → standard stream protocol

from vel.providers.translators import OpenAIAPITranslator

translator = OpenAIAPITranslator()
# Converts OpenAI chunks → Vel protocol events
  • Job: Protocol translation only
  • Scope: Single LLM response stream
  • Stateful: Only tracks current response (text blocks, tool calls)
  • Reusable: Works with any orchestrator (Vel Agent, Mesh, LangGraph)

Layer 2: Agent (Orchestrator)

Responsibility: Multi-step execution, tool calling, context management

from vel import Agent

agent = Agent(id='my-agent', model={...}, tools=[...])
# Handles orchestration, tool execution, sessions
  • Job: Full agentic workflow
  • Scope: Multi-step execution with tools
  • Stateful: Sessions, context, run history
  • Opinionated: Implements specific orchestration pattern

Why Two Layers?

This separation enables composability:

  1. Use Agent for turnkey agentic workflows (most common)
  2. Use Translator when integrating with external frameworks or building custom orchestrators

The translator layer can be reused across different orchestration strategies without modification.

Learn more: Event Translators - Complete architecture details and usage guide

Next Steps

Troubleshooting

“Illegal header value b’Bearer ‘”

Your OPENAI_API_KEY, GOOGLE_API_KEY, or ANTHROPIC_API_KEY is not set. Check your .env file.

“Connection refused” (Postgres/Redis)

If you see connection errors for Postgres or Redis, comment out POSTGRES_DSN and REDIS_URL in your .env file to use in-memory storage.

Import Errors

Make sure you installed the package:

pip install -e .

And that you’re loading environment variables:

from dotenv import load_dotenv
load_dotenv()