Skip to content

Output Formatter

Pavel Buchnev edited this page Sep 18, 2024 · 1 revision

Feature Explanation

The Agents Output Formatter is a feature designed to format the output of AI agents in a structured manner. It provides a flexible way to define output schemas and ensure that the AI's responses conform to these schemas. The main components of this feature are:

  • FormatterInterface: An interface that defines the contract for output formatters.
  • JsonSchemaFormatter: A concrete implementation of FormatterInterface that formats output based on JSON schemas.
  • EnumFormatter: Another implementation of FormatterInterface that formats output based on PHP enums.
  • SelectFormatter: A formatter that ensures the output is one of a predefined set of options.

The feature allows developers to specify the desired output format, which can be a JSON schema, an enum, or a set of predefined options. The formatter then processes the AI's output to ensure it matches the specified format, throwing exceptions if the output doesn't conform.

Use Cases (5 Examples)

a) Formatting user profile data:

$formatter = new JsonSchemaFormatter($schemaMapper);
$formatter = $formatter->withJsonSchema(UserProfile::class);

$userProfileData = $formatter->format($aiOutput);

class UserProfile {
    public function __construct(
        public readonly string $name,
        public readonly int $age,
        public readonly string $email
    ) {}
}

// Usage
$profile = new UserProfile(
    name: $userProfileData->name,
    age: $userProfileData->age,
    email: $userProfileData->email
);

b) Ensuring a valid response type:

enum ResponseType {
    case Yes;
    case No;
    case Maybe;
}

$formatter = new EnumFormatter(ResponseType::class);
$response = $formatter->format($aiOutput);

// Usage
match ($response) {
    ResponseType::Yes => handleYesResponse(),
    ResponseType::No => handleNoResponse(),
    ResponseType::Maybe => handleMaybeResponse(),
};

c) Validating multiple-choice answers:

$formatter = new SelectFormatter('A', 'B', 'C', 'D');
$selectedAnswer = $formatter->format($aiOutput);

// Usage
$question = new MultipleChoiceQuestion(
    text: "What is the capital of France?",
    options: ['A' => 'London', 'B' => 'Paris', 'C' => 'Berlin', 'D' => 'Rome'],
    correctAnswer: $selectedAnswer
);

d) Formatting structured product data:

$formatter = new JsonSchemaFormatter($schemaMapper);
$formatter = $formatter->withJsonSchema(ProductData::class);

$productData = $formatter->format($aiOutput);

class ProductData {
    public function __construct(
        public readonly string $name,
        public readonly float $price,
        public readonly array $categories,
        public readonly ?string $description = null
    ) {}
}

// Usage
$product = new Product(
    name: $productData->name,
    price: $productData->price,
    categories: $productData->categories,
    description: $productData->description
);

e) Ensuring valid sentiment analysis output:

enum Sentiment {
    case Positive;
    case Neutral;
    case Negative;
}

$formatter = new EnumFormatter(Sentiment::class);
$sentiment = $formatter->format($aiOutput);

// Usage
$review = new ProductReview(
    text: $reviewText,
    rating: $userRating,
    sentiment: $sentiment
);

Configuration Options

The main configuration options are available through the JsonSchemaFormatter class:

a) JSON Schema:

  • Default: None (must be set)
  • Configuration:
$formatter = new JsonSchemaFormatter($schemaMapper);
$formatter = $formatter->withJsonSchema(UserProfile::class);

b) Schema Mapper:

  • Default: None (must be provided in the constructor)
  • Configuration:
$schemaMapper = new SchemaMapper();
$formatter = new JsonSchemaFormatter(schemaMapper: $schemaMapper);

For the EnumFormatter and SelectFormatter, the configuration is done through the constructor:

$enumFormatter = new EnumFormatter(enumClass: ResponseType::class);
$selectFormatter = new SelectFormatter('Option1', 'Option2', 'Option3');

Related Classes

a) SchemaMapperInterface:

  • Purpose: Defines the contract for mapping between JSON schemas and PHP objects.
  • Interaction: Used by JsonSchemaFormatter to convert between JSON schemas and PHP classes.

b) FormatterException:

  • Purpose: Custom exception class for formatting errors.
  • Interaction: Thrown by formatters when the output doesn't conform to the specified schema or options.

c) InvalidArgumentException:

  • Purpose: Standard PHP exception for invalid arguments.
  • Interaction: Thrown when invalid configuration options are provided to the formatters.

Mermaid Class Diagram

classDiagram
    class FormatterInterface {
        +format(output: string|Stringable): mixed
        +getInstruction(): string|null
    }
    class JsonSchemaFormatter {
        -schemaMapper: SchemaMapperInterface
        -jsonSchema: string|null
        -class: string|null
        +withJsonSchema(jsonSchema: string): self
        +format(output: string|Stringable): mixed
        +getInstruction(): string|null
    }
    class EnumFormatter {
        -enum: ReflectionEnum
        -formatter: FormatterInterface
        +format(output: string|Stringable): mixed
        +getInstruction(): string|null
    }
    class SelectFormatter {
        -options: array
        +format(output: string|Stringable): string
        +getInstruction(): string|null
    }
    class SchemaMapperInterface {
        +toJsonSchema(class: string): array
        +toObject(json: string, class: string|null): object
    }
    
    FormatterInterface <|-- JsonSchemaFormatter
    FormatterInterface <|-- EnumFormatter
    FormatterInterface <|-- SelectFormatter
    JsonSchemaFormatter --> SchemaMapperInterface
Loading

This class diagram illustrates the relationships between the main FormatterInterface and its implementations, as well as the dependency on SchemaMapperInterface for the JsonSchemaFormatter.


Here's an expanded example that demonstrates how to use the formatter with an agent:

<?php

declare(strict_types=1);

namespace App\Agents\WebScraper;

use LLM\Agents\AgentExecutor\ExecutorInterface;
use LLM\Agents\LLM\Options;
use LLM\Agents\LLM\Output\JsonSchemaFormatter;
use LLM\Agents\Tool\SchemaMapperInterface;
use LLM\Agents\LLM\Prompt\Chat\Prompt;
use LLM\Agents\LLM\Prompt\Chat\MessagePrompt;
use Spiral\JsonSchemaGenerator\Attribute\Field;

final class FoundLinks implements \JsonSerializable
{
    public function __construct(
        /**
         * @var array<string>
         */
        #[Field(title: 'Links', description: 'Links found on the page')]
        public array $links,
    ) {}

    public function jsonSerialize(): array
    {
        return [
            'links' => $this->links,
        ];
    }
}

class WebScraperAgent
{
    public function __construct(
        private ExecutorInterface $executor,
        private SchemaMapperInterface $schemaMapper
    ) {}

    public function scrapeLinks(string $url): FoundLinks
    {
        $formatter = new JsonSchemaFormatter($this->schemaMapper);
        $formatter = $formatter->withJsonSchema(FoundLinks::class);

        $options = new Options([
            'output_formatter' => $formatter,
        ]);

        $prompt = new Prompt([
            MessagePrompt::system("You are a web scraping assistant. Your task is to extract all links from the given URL."),
            MessagePrompt::user("Extract all links from the following URL: $url"),
        ]);

        $result = $this->executor->execute(
            agent: 'web-scraper',
            prompt: $prompt,
            options: $options
        );

        // The result will be automatically formatted as a FoundLinks object
        return $result->content;
    }
}

// Usage
$webScraperAgent = new WebScraperAgent($executor, $schemaMapper);
$foundLinks = $webScraperAgent->scrapeLinks('https://spiral.dev');

// Access the formatted results
foreach ($foundLinks->links as $link) {
    echo "Found link: $link\n";
}

Let's break down how this example works with the formatting feature:

  1. We define the FoundLinks class, which represents the structured output we expect from the web scraper agent. This class uses attributes to define the JSON schema.

  2. In the WebScraperAgent class, we create a method scrapeLinks that encapsulates the logic for using the agent with the formatter.

  3. We create a JsonSchemaFormatter instance and configure it with the FoundLinks class:

    $formatter = new JsonSchemaFormatter($this->schemaMapper);
    $formatter = $formatter->withJsonSchema(FoundLinks::class);
  4. We create an Options object and set the output_formatter to our configured formatter:

    $options = new Options([
        'output_formatter' => $formatter,
    ]);
  5. We create a Prompt object with system and user messages to instruct the agent:

    $prompt = new Prompt([
        MessagePrompt::system("You are a web scraping assistant. Your task is to extract all links from the given URL."),
        MessagePrompt::user("Extract all links from the following URL: $url"),
    ]);
  6. We execute the agent using the executor, passing the prompt and options:

    $result = $this->executor->execute(
        agent: 'web-scraper',
        prompt: $prompt,
        options: $options
    );
  7. The executor will use the specified formatter to process the agent's output. The JsonSchemaFormatter ensures that the output conforms to the FoundLinks schema.

  8. The formatted result is returned as a FoundLinks object, which we can then use to access the extracted links.

This example demonstrates how to integrate the formatting feature with an agent using the executor. The formatter ensures that the agent's output is structured according to the specified schema, making it easy to work with the results in a type-safe manner.