helgesverre/pagent

A Pest-inspired LLM Agent Framework for PHP with multi-provider support, automatic tool calling, safety guards, and multi-agent orchestration

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

pkg:composer/helgesverre/pagent

dev-main 2025-11-20 13:55 UTC

This package is auto-updated.

Last update: 2025-11-20 14:01:23 UTC


README

A Pest-inspired LLM Agent Framework for PHP

Build intelligent agents with automatic tool calling, multi-provider support, safety guards, and multi-agent orchestrationβ€”all with a clean, fluent API.

Latest Version Tests Total Downloads PHP Version License

Why Pagent?

  • πŸ§ͺ Pest-Inspired API - Fluent, expressive syntax that feels natural
  • 🌊 Real-Time Streaming - SSE streaming for ChatGPT-like experiences
  • πŸ’Ύ Memory & Persistence - SQLite, File, and custom storage adapters
  • πŸ”§ Automatic Tool Calling - JSON schema generation from PHP functions
  • πŸ€– Multi-Provider - Anthropic Claude, OpenAI GPT, Ollama (local), Mock (for testing)
  • πŸ›‘οΈ Safety Guards - PII detection, content filtering, prompt injection prevention
  • πŸ“Š Evaluation Framework - Test datasets with automated metrics and reports
  • πŸ”„ Multi-Agent Orchestration - Pipeline, handoff, and delegation patterns
  • πŸ“‘ Observability & Tracing - OpenTelemetry instrumentation with Jaeger, Zipkin, OTLP support
  • ⚑ Production Ready - 630+ tests, PHPStan level 9, PHP 8.3+ type safety

Installation

composer require helgesverre/pagent

Requirements:

  • PHP 8.3 or higher
  • Composer 2.x

Quick Start

// Configure an agent
agent('assistant')
    ->provider('anthropic')
    ->system('You are a helpful assistant')
    ->temperature(0.7);

// Use the agent
$response = agent('assistant')->prompt('Hello!');
echo $response->content;

// Or stream responses in real-time
agent('assistant')->streamTo('Tell me a story', function ($chunk) {
    if ($chunk->isText()) {
        echo $chunk->content;
        flush();
    }
});

// Persist conversations across sessions
agent('support')
    ->memory('sqlite', ['path' => 'storage/conversations.db'])
    ->sessionId('user-123')
    ->contextWindow(100000)
    ->prompt('Hello');

πŸ“– Explore: Streaming Guide | Memory & Persistence

Providers

Mock Provider (for testing)

$mock = mock([
    'Hello' => 'Hi there!',
    'How are you?' => 'I am doing great!'
]);

$response = $mock->prompt('Hello');
echo $response->content; // "Hi there!"

Anthropic (Claude)

export ANTHROPIC_API_KEY="your-key"
$claude = anthropic();
$response = $claude->prompt('Hello!', [
    'model' => 'claude-3-sonnet-20240229',
    'max_tokens' => 100
]);

OpenAI (GPT)

export OPENAI_API_KEY="your-key"
$gpt = openai();
$response = $gpt->prompt('Hello!', [
    'model' => 'gpt-4',
    'temperature' => 0.8
]);

Ollama (Local LLMs)

Run models locally with complete privacy and zero API costs:

# Install Ollama and pull models
ollama pull qwen3:8b
ollama pull gpt-oss:20b
ollama serve
$ollama = ollama();
$response = $ollama->prompt('Hello!', [
    'model' => 'qwen3:8b',
    'temperature' => 0.7
]);

Benefits:

  • πŸ”’ Complete privacy - all data stays local
  • πŸ’° Zero API costs
  • ⚑ Low latency
  • πŸ› οΈ Full tool calling support (qwen3, llama3.1, mistral)
  • πŸ“‘ NDJSON streaming

πŸ“– Full Guide: Ollama Integration

Agent Pattern

Agents provide a higher-level abstraction with conversation history:

// Define an agent
agent('support')
    ->provider('anthropic')
    ->system('You are a customer support agent')
    ->model('claude-3-haiku-20240307')
    ->temperature(0.3);

// Have a conversation
$agent = agent('support');
$agent->prompt('I need help with my order');
$agent->prompt('Order number is 12345');

// Access conversation history
foreach ($agent->messages as $message) {
    echo "[{$message['role']}]: {$message['content']}\n";
}

Provider Configuration

Pagent supports two ways to configure providers:

String-Based (Simple)

Use provider names for quick setup with default configuration:

agent('assistant')
    ->provider('anthropic')  // String name
    ->system('You are helpful');

// With config options
agent('custom')
    ->provider('ollama', ['base_url' => 'http://custom:11434', 'timeout' => 180])
    ->model('qwen3:8b');

Instance-Based (Advanced)

Use helper functions or direct instantiation for custom configuration:

// Using helper functions
agent('assistant')
    ->provider(anthropic(['api_key' => 'custom-key']))
    ->prompt('Hello');

agent('local')
    ->provider(ollama(['timeout' => 300, 'base_url' => 'http://10.0.0.5:11434']))
    ->model('llama3.1');

// Direct instantiation
use Pagent\Providers\OpenAI;

agent('custom')
    ->provider(new OpenAI(['api_key' => getenv('CUSTOM_KEY')]))
    ->prompt('Hello');

When to use each:

  • String-based: Quick setup, standard configuration
  • Instance-based: Custom config, multiple providers with same name, testing

Provider-Specific Features

The library is intentionally "leaky" - you can use provider-specific features:

// Anthropic-specific models
$response = anthropic()->prompt('Complex analysis task', [
    'model' => 'claude-3-opus-20240229',
    'max_tokens' => 4096
]);

// OpenAI-specific features
$response = openai()->prompt('Generate JSON data', [
    'model' => 'gpt-3.5-turbo-1106',
    'response_format' => ['type' => 'json_object']
]);

Tool Calling

Define tools using PHP closures with automatic JSON schema generation:

use Pagent\Tool\Tool;

// Create a tool from a closure
$weatherTool = Tool::fromClosure(
    'get_weather',
    'Get the current weather for a location',
    fn(string $location, bool $include_forecast = false) => "Weather data..."
);

// Add tools to an agent
$agent = agent('assistant')
    ->provider('anthropic')
    ->tool('calculate', 'Perform calculations', fn(int $a, int $b) => $a + $b)
    ->tool('get_time', 'Get current time', fn(string $tz = 'UTC') => date('H:i:s'));

// Execute tools
$result = $agent->executeTool('calculate', [10, 5]); // 15

// Generate provider-specific schemas
$anthropicSchema = $weatherTool->toAnthropicSchema();
$openaiSchema = $weatherTool->toOpenAISchema();

Type hints are automatically converted to JSON schema types:

  • string β†’ "string"
  • int β†’ "integer"
  • float β†’ "number"
  • bool β†’ "boolean"
  • array β†’ "array"

Class-Based Tools

Pagent includes 9 production-ready class-based tools in the Pagent\Tools namespace:

use Pagent\Tools\FileRead;
use Pagent\Tools\FileWrite;
use Pagent\Tools\WebFetch;
use Pagent\Tools\Bash;
use Pagent\Tools\Grep;
use Pagent\Tools\Glob;
use Pagent\Tools\PdfReader;
use Pagent\Tools\DataExtract;
use Pagent\Tools\SearchTool;

// Use class-based tools with agents
$agent = agent('assistant')
    ->provider('anthropic')
    ->tool(new FileRead())
    ->tool(new WebFetch())
    ->prompt('Read the file data.json and fetch https://api.example.com/data');

// Add multiple tools at once
$agent = agent('file-assistant')
    ->provider('anthropic')
    ->tools([
        new FileRead(baseDir: '/project'),
        new FileWrite(baseDir: '/project'),
        new Glob(baseDir: '/project'),
        new Grep(baseDir: '/project'),
    ])
    ->prompt('List all PHP files and show me the config');

// Create custom class-based tools
use Pagent\Tools\Tool;

class DatabaseQuery extends Tool
{
    public function name(): string
    {
        return 'query_database';
    }

    public function description(): string
    {
        return 'Execute a database query and return results';
    }

    public function parameters(): array
    {
        return [
            'type' => 'object',
            'properties' => [
                'query' => ['type' => 'string', 'description' => 'SQL query to execute'],
                'limit' => ['type' => 'integer', 'description' => 'Maximum rows to return'],
            ],
            'required' => ['query'],
        ];
    }

    public function execute(array $params): mixed
    {
        // Your implementation here
        return ['results' => []];
    }
}

// Use your custom tool
agent('assistant')->tool(new DatabaseQuery());

Both closure-based and class-based tools implement ToolInterface and work seamlessly with all providers.

SearchTool - Full-Text Search

The SearchTool provides powerful full-text search capabilities powered by TNTSearch, enabling agents to search through documents, files, and databases:

use Pagent\Tools\SearchTool;

// Search through an array of documents (RAG pattern)
$documents = [
    ['id' => 1, 'title' => 'PHP Guide', 'content' => 'Learn PHP programming...'],
    ['id' => 2, 'title' => 'Laravel Tutorial', 'content' => 'Build web apps with Laravel...'],
];

agent('docs-assistant')
    ->tools([searchDocuments($documents)])
    ->prompt('Find information about PHP');

// Search through files in a directory
agent('codebase-helper')
    ->tools([new SearchTool(paths: ['docs/', 'src/'])])
    ->prompt('Find all documentation about API endpoints');

// Use a pre-built search index
agent('knowledge-bot')
    ->tools([searchIndex('knowledge/docs.index')])
    ->prompt('Search the knowledge base for deployment guides');

// Database-backed search
agent('db-search')
    ->tools([
        new SearchTool(
            query: 'SELECT id, title, content FROM articles',
            connection: ['driver' => 'mysql', 'host' => 'localhost', 'database' => 'mydb']
        )
    ])
    ->prompt('Find articles about Laravel');

Key Features:

  • Multiple Document Sources: Arrays, files, directories, databases, or pre-built indexes
  • Fuzzy Matching: Handles typos and approximate matches
  • BM25 Ranking: Industry-standard relevance scoring
  • Flexible Returns: Get just IDs or full document content
  • Fast Performance: Sub-millisecond to millisecond search times
  • UTF-8 Support: Works with international characters

Configuration Options:

new SearchTool(
    documents: $docs,           // Array of documents to index
    returnContent: true,        // Return full documents vs just IDs
    fuzzy: true,                // Enable fuzzy search
    fuzziness: 2,               // Levenshtein distance (1-3)
    maxResults: 20,             // Max results to return
    storage: ':memory:',        // Index storage location
    stemmer: PorterStemmer::class  // Custom stemmer class
);

Search Results:

[
    'hits' => 3,
    'execution_time' => '1.5ms',
    'results' => [
        ['id' => 1, 'score' => 4.2, 'document' => [...]],
        ['id' => 2, 'score' => 3.8, 'document' => [...]],
    ]
]

Perfect for building:

  • RAG (Retrieval-Augmented Generation) systems
  • Documentation search agents
  • Knowledge base assistants
  • Semantic code search
  • Content discovery tools

Observability & Distributed Tracing

Pagent includes comprehensive OpenTelemetry instrumentation for monitoring and debugging your LLM agents in production.

Quick Start

use function Pagent\{agent, telemetry_console};

// Enable console telemetry for debugging
telemetry_console(verbose: true);

agent('assistant')
    ->provider('anthropic')
    ->telemetry(true)  // Enable tracing for this agent
    ->prompt('Hello!');

// Console shows:
// β”Œβ”€ Span: agent.prompt
// β”‚  Duration: 1.23s
// β”‚  Attributes:
// β”‚    - gen_ai.system: anthropic
// β”‚    - gen_ai.usage.total_tokens: 125
// └─

Production Monitoring

Connect to Jaeger, Zipkin, or any OpenTelemetry-compatible platform:

use function Pagent\{agent, telemetry_jaeger};

// Export to Jaeger
telemetry_jaeger('http://localhost:14268/api/traces');

// All operations automatically traced
agent('support')
    ->telemetry(true)
    ->tool('search', 'Search knowledge base', $searchFn)
    ->prompt('Help me find documentation');

// View traces at http://localhost:16686

What Gets Traced

  • Agent Operations - Every prompt, stream, and tool execution
  • LLM Requests - Provider calls with token usage
  • Tool Executions - Arguments, results, and duration
  • Guard Checks - Security validations
  • Memory Operations - Load/save operations
  • Workflows - Multi-agent pipeline orchestration

Supported Platforms

  • Jaeger - Open-source distributed tracing
  • Zipkin - Distributed tracing system
  • OTLP - Generic protocol (Datadog, New Relic, Honeycomb, etc.)
  • Console - Local debugging output

Multi-Agent Workflow Tracing

use function Pagent\{agent, pipeline, telemetry_jaeger};

telemetry_jaeger('http://localhost:14268/api/traces');

// Enable telemetry on agents
agent('researcher')->provider('anthropic')->telemetry(true);
agent('writer')->provider('anthropic')->telemetry(true);

// Run workflow - creates hierarchical trace
pipeline('content-creation')
    ->step('research', agent('researcher'))
    ->step('write', agent('writer'))
    ->run('Write article about PHP');

// Trace shows:
// workflow.pipeline
//   β”œβ”€ workflow.step (research)
//   β”‚  └─ agent.prompt β†’ llm.request β†’ tool.execute
//   └─ workflow.step (write)
//      └─ agent.prompt β†’ llm.request

Benefits

  • Debug Complex Workflows - Visualize multi-agent interactions
  • Performance Monitoring - Track latency and bottlenecks
  • Token Usage Tracking - Real-time token consumption
  • Cost Visibility - Understand API usage patterns
  • Compliance - Complete audit trail

πŸ“– Full Guide: Observability Documentation

Development

Quick Commands

# Setup project
just setup              # Install dependencies and git hooks

# Testing
just test               # Run all tests
just coverage           # Run tests with coverage report

# Code Quality
just format             # Fix code style (PHP + Markdown)
just analyse            # Run PHPStan static analysis
just pr                 # Prepare for PR (fix, analyse, test)

# Observability Stack
just obs-up             # Start observability tools (Jaeger, Phoenix, Langfuse, etc.)
just obs-down           # Stop and remove observability stack

Manual Testing

# Run unit tests (no API calls)
./vendor/bin/pest --exclude-group=api

# Run API integration tests (requires API keys)
cp .env.example .env    # Add your keys to .env
./vendor/bin/pest --group=api

Documentation

Learning Guides

We've created 5 different guide styles so you can learn in the way that works best for you:

  1. Getting Started (Conversational) - Friendly, interactive introduction with examples
  2. Recipes (Task-Oriented) - Step-by-step solutions for common tasks
  3. Quick Start (Minimal) - TL;DR reference for when you're in a hurry
  4. Concepts (Deep Dive) - Understand the architecture and design decisions
  5. API Reference (Technical) - Complete technical documentation

New to Pagent? Start with the Getting Started Guide.

Need something specific? Check the Recipes Guide.

In a hurry? The Quick Start has you covered.

Integration Guides

Learn how to integrate Pagent into your application:

Feature Guides

Deep-dive into specific features:

See the docs/ folder for all guides.

Contributing

We welcome contributions! Please see CONTRIBUTING.md for details on:

  • Development setup
  • Running tests
  • Code style guidelines
  • Pull request process

Read our Code of Conduct and Security Policy.

Changelog

See CHANGELOG.md for recent changes.

License

MIT License. See LICENSE for details.

Credits

Created by Helge Sverre.

Inspired by Pest's elegant API design.