refactor: integration llm-chain-bundle as ai-bundle into Symfony AI

* llm-bundle/restructure: (81 commits)
  refactor: restructure
  fix: support for 0.22 (#86)
  chore: support LLM Chain 0.22 (#85)
  chore: support LLM Chain 0.21 (#84)
  fix: profiler when using files as platform input (#81)
  fix: model name argument on bundle extension/injection (#80)
  chore(dependencies): require `php-llm/llm-chain` 0.20 (#78)
  fix: remove unwanted tools in system prompt by autowire (#77)
  feat: add inline tool definition and chain in chain 🤯 (#75)
  chore: 0.19 compatibility (#74)
  chore: lib update (#73)
  feat: support include tools feature to system prompt (#68)
  ci: streamline with lib (#71)
  style: add rector (#70)
  chore: prepare with release 0.17 (#69)
  feat: update for 0.16 - gemini & fault tolerant toolbox (#67)
  chore: lib update (#66)
  Fix: Embedder wiring (#64)
  chore: require `php-llm/llm-chain` 0.14 (#63)
  fix: require symfony/string explicitly (#62)
  ...
This commit is contained in:
Christopher Hertel
2025-06-13 14:16:50 +02:00
parent d2d30f9fa4
commit b8b40ba075
22 changed files with 358 additions and 399 deletions

6
.gitattributes vendored Normal file
View File

@@ -0,0 +1,6 @@
/.github export-ignore
/tests export-ignore
.gitattributes export-ignore
.gitignore export-ignore
phpstan.dist.neon export-ignore
phpunit.xml.dist export-ignore

8
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,8 @@
Please do not submit any Pull Requests here. They will be closed.
---
Please submit your PR here instead:
https://github.com/symfony/ai
This repository is what we call a "subtree split": a read-only subset of that main repository.
We're looking forward to your PR there!

View File

@@ -0,0 +1,20 @@
name: Close Pull Request
on:
pull_request_target:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: superbrothers/close-pull-request@v3
with:
comment: |
Thanks for your Pull Request! We love contributions.
However, you should instead open your PR on the main repository:
https://github.com/symfony/ai
This repository is what we call a "subtree split": a read-only subset of that main repository.
We're looking forward to your PR there!

View File

@@ -1,72 +0,0 @@
name: pipeline
on: pull_request
permissions:
contents: read
pull-requests: write
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.2', '8.3', '8.4']
dependencies: ['lowest', 'highest']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- name: Install Composer
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "${{ matrix.dependencies }}"
- name: Composer Validation
run: composer validate --strict
- name: Install PHP Dependencies
run: composer install --no-scripts
- name: Tests
run: vendor/bin/phpunit
qa:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Conventional Commit
uses: ytanikin/pr-conventional-commits@1.4.1
with:
task_types: '["feat", "fix", "docs", "test", "ci", "style", "refactor", "perf", "chore", "revert"]'
add_label: 'true'
custom_labels: '{"feat": "feature", "fix": "bug", "docs": "documentation", "test": "test", "ci": "CI/CD", "style": "codestyle", "refactor": "refactor", "perf": "performance", "chore": "chore", "revert": "revert"}'
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Install Composer
uses: "ramsey/composer-install@v3"
- name: Composer Validation
run: composer validate --strict
- name: Install PHP Dependencies
run: composer install --no-scripts
- name: Code Style PHP
run: vendor/bin/php-cs-fixer fix --dry-run
- name: Rector
run: vendor/bin/rector --dry-run
- name: PHPStan
run: vendor/bin/phpstan analyse

View File

@@ -1,12 +0,0 @@
<?php
$finder = (new PhpCsFixer\Finder())
->in(__DIR__)
;
return (new PhpCsFixer\Config())
->setRules([
'@Symfony' => true,
])
->setFinder($finder)
;

View File

@@ -1,4 +1,4 @@
Copyright (c) 2024 Christopher Hertel
Copyright (c) 2025-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,37 +0,0 @@
.PHONY: deps-stable deps-low cs rector phpstan tests coverage run-examples ci ci-stable ci-lowest
deps-stable:
composer update --prefer-stable --ignore-platform-req=ext-mongodb
deps-low:
composer update --prefer-lowest --ignore-platform-req=ext-mongodb
deps-dev:
composer require php-llm/llm-chain:dev-main
cs:
PHP_CS_FIXER_IGNORE_ENV=true vendor/bin/php-cs-fixer fix --diff --verbose
rector:
vendor/bin/rector
phpstan:
vendor/bin/phpstan --memory-limit=-1
tests:
vendor/bin/phpunit
coverage:
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage
run-examples:
./example
ci: ci-stable
ci-stable: deps-stable rector cs phpstan tests
ci-lowest: deps-low rector cs phpstan tests
ci-dev: deps-dev rector cs phpstan tests
git restore composer.json

100
README.md
View File

@@ -1,11 +1,11 @@
# LLM Chain Bundle
# Symfony AI Bundle
Symfony integration bundle for [php-llm/llm-chain](https://github.com/php-llm/llm-chain) library.
Integration bundle for Symfony AI components.
## Installation
```bash
composer require php-llm/llm-chain-bundle
composer require symfony/ai-bundle
```
## Configuration
@@ -13,21 +13,21 @@ composer require php-llm/llm-chain-bundle
### Simple Example with OpenAI
```yaml
# config/packages/llm_chain.yaml
llm_chain:
# config/packages/ai.yaml
ai:
platform:
openai:
api_key: '%env(OPENAI_API_KEY)%'
chain:
agent:
default:
model:
name: 'GPT'
```
### Advanced Example with Anthropic, Azure, Google and multiple chains
### Advanced Example with Anthropic, Azure, Google and multiple agents
```yaml
# config/packages/llm_chain.yaml
llm_chain:
# config/packages/ai.yaml
ai:
platform:
anthropic:
api_key: '%env(ANTHROPIC_API_KEY)%'
@@ -40,36 +40,36 @@ llm_chain:
api_version: '%env(AZURE_GPT_VERSION)%'
google:
api_key: '%env(GOOGLE_API_KEY)%'
chain:
agent:
rag:
platform: 'llm_chain.platform.azure.gpt_deployment'
platform: 'symfony_ai.platform.azure.gpt_deployment'
structured_output: false # Disables support for "output_structure" option, default is true
model:
name: 'GPT'
version: 'gpt-4o-mini'
system_prompt: 'You are a helpful assistant that can answer questions.' # The default system prompt of the chain
include_tools: true # Include tool definitions at the end of the system prompt
system_prompt: 'You are a helpful assistant that can answer questions.' # The default system prompt of the agent
include_tools: true # Include tool definitions at the end of the system prompt
tools:
# Referencing a service with #[AsTool] attribute
- 'PhpLlm\LlmChain\Chain\Toolbox\Tool\SimilaritySearch'
- 'Symfony\AI\Agent\Toolbox\Tool\SimilaritySearch'
# Referencing a service without #[AsTool] attribute
- service: 'App\Chain\Tool\CompanyName'
- service: 'App\Agent\Tool\CompanyName'
name: 'company_name'
description: 'Provides the name of your company'
method: 'foo' # Optional with default value '__invoke'
# Referencing a chain => chain in chain 🤯
- service: 'llm_chain.chain.research'
# Referencing a agent => agent in agent 🤯
- service: 'symfony_ai.agent.research'
name: 'wikipedia_research'
description: 'Can research on Wikipedia'
is_chain: true
is_agent: true
research:
platform: 'llm_chain.platform.anthropic'
platform: 'symfony_ai.platform.anthropic'
model:
name: 'Claude'
tools: # If undefined, all tools are injected into the chain, use "tools: false" to disable tools.
- 'PhpLlm\LlmChain\Chain\Toolbox\Tool\Wikipedia'
tools: # If undefined, all tools are injected into the agent, use "tools: false" to disable tools.
- 'Symfony\AI\Agent\Toolbox\Tool\Wikipedia'
fault_tolerant_toolbox: false # Disables fault tolerant toolbox, default is true
store:
# also azure_search, mongodb and pinecone are supported as store type
@@ -79,8 +79,8 @@ llm_chain:
collection: 'my_collection'
embedder:
default:
# platform: 'llm_chain.platform.anthropic'
# store: 'llm_chain.store.chroma_db.default'
# platform: 'symfony_ai.platform.anthropic'
# store: 'symfony_ai.store.chroma_db.default'
model:
name: 'Embeddings'
version: 'text-embedding-ada-002'
@@ -88,21 +88,21 @@ llm_chain:
## Usage
### Chain Service
### Agent Service
Use the `Chain` service to leverage GPT:
Use the `Agent` service to leverage models and tools:
```php
use PhpLlm\LlmChain\ChainInterface;
use PhpLlm\LlmChain\Model\Message\Message;
use PhpLlm\LlmChain\Model\Message\MessageBag;
use Symfony\AI\Agent\AgentInterface;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
final readonly class MyService
{
public function __construct(
private ChainInterface $chain,
private AgentInterface $agent,
) {
}
public function submit(string $message): string
{
$messages = new MessageBag(
@@ -110,7 +110,7 @@ final readonly class MyService
Message::ofUser($message),
);
return $this->chain->call($messages);
return $this->agent->call($messages);
}
}
```
@@ -124,21 +124,21 @@ services:
autowire: true
autoconfigure: true
PhpLlm\LlmChain\Chain\Toolbox\Tool\Clock: ~
PhpLlm\LlmChain\Chain\Toolbox\Tool\OpenMeteo: ~
PhpLlm\LlmChain\Chain\Toolbox\Tool\SerpApi:
Symfony\AI\Agent\Toolbox\Tool\Clock: ~
Symfony\AI\Agent\Toolbox\Tool\OpenMeteo: ~
Symfony\AI\Agent\Toolbox\Tool\SerpApi:
$apiKey: '%env(SERP_API_KEY)%'
PhpLlm\LlmChain\Chain\Toolbox\Tool\SimilaritySearch: ~
PhpLlm\LlmChain\Chain\Toolbox\Tool\Tavily:
Symfony\AI\Agent\Toolbox\Tool\SimilaritySearch: ~
Symfony\AI\Agent\Toolbox\Tool\Tavily:
$apiKey: '%env(TAVILY_API_KEY)%'
PhpLlm\LlmChain\Chain\Toolbox\Tool\Wikipedia: ~
PhpLlm\LlmChain\Chain\Toolbox\Tool\YouTubeTranscriber: ~
Symfony\AI\Agent\Toolbox\Tool\Wikipedia: ~
Symfony\AI\Agent\Toolbox\Tool\YouTubeTranscriber: ~
```
Custom tools can be registered by using the `#[AsTool]` attribute:
```php
use PhpLlm\LlmChain\Chain\Toolbox\Attribute\AsTool;
use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
#[AsTool('company_name', 'Provides the name of your company')]
final class CompanyName
@@ -150,27 +150,27 @@ final class CompanyName
}
```
The chain configuration by default will inject all known tools into the chain.
The agent configuration by default will inject all known tools into the agent.
To disable this behavior, set the `tools` option to `false`:
```yaml
llm_chain:
chain:
my_chain:
ai:
agent:
my_agent:
tools: false
```
To inject only specific tools, list them in the configuration:
```yaml
llm_chain:
chain:
my_chain:
ai:
agent:
my_agent:
tools:
- 'PhpLlm\LlmChain\Chain\Toolbox\Tool\SimilaritySearch'
- 'Symfony\AI\Agent\Toolbox\Tool\SimilaritySearch'
```
### Profiler
The profiler panel provides insights into the chain's execution:
The profiler panel provides insights into the agent's execution:
![Profiler](./profiler.png)

View File

@@ -1,7 +1,7 @@
{
"name": "php-llm/llm-chain-bundle",
"name": "symfony/ai-bundle",
"type": "symfony-bundle",
"description": "Symfony integration bundle for php-llm/llm-chain",
"description": "Integration bundle for Symfony AI components",
"license": "MIT",
"authors": [
{
@@ -15,29 +15,34 @@
],
"require": {
"php": ">=8.2",
"php-llm/llm-chain": "^0.22",
"symfony/ai-agent": "@dev",
"symfony/ai-platform": "@dev",
"symfony/ai-store": "@dev",
"symfony/config": "^6.4 || ^7.0",
"symfony/dependency-injection": "^6.4 || ^7.0",
"symfony/framework-bundle": "^6.4 || ^7.0",
"symfony/string": "^6.4 || ^7.0"
},
"require-dev": {
"php-cs-fixer/shim": "^3.69",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5",
"rector/rector": "^2.0"
"phpunit/phpunit": "^11.5"
},
"config": {
"sort-packages": true
},
"autoload": {
"psr-4": {
"PhpLlm\\LlmChainBundle\\": "src/"
"Symfony\\AI\\AIBundle\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"PhpLlm\\LlmChainBundle\\Tests\\": "tests/"
"Symfony\\AI\\AIBundle\\Tests\\": "tests/"
}
}
},
"repositories": [
{"type": "path", "url": "../agent"},
{"type": "path", "url": "../platform"},
{"type": "path", "url": "../store"}
]
}

View File

@@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitSelfCallRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector;
use Rector\PHPUnit\Set\PHPUnitSetList;
return RectorConfig::configure()
->withPaths([
__DIR__.'/src',
__DIR__.'/tests',
])
->withPhpSets(php82: true)
->withSets([
PHPUnitSetList::PHPUNIT_110,
PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES,
PHPUnitSetList::PHPUNIT_CODE_QUALITY,
])
->withRules([
PreferPHPUnitSelfCallRector::class,
])
->withImportNames(importShortClasses: false)
->withSkip([
ClosureToArrowFunctionRector::class,
PreferPHPUnitThisCallRector::class,
])
->withTypeCoverageLevel(0);

21
src/AIBundle.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\AI\AIBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class AIBundle extends Bundle
{
}

View File

@@ -1,46 +1,53 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\LlmChainBundle\DependencyInjection;
namespace Symfony\AI\AIBundle\DependencyInjection;
use PhpLlm\LlmChain\Chain\Chain;
use PhpLlm\LlmChain\Chain\ChainInterface;
use PhpLlm\LlmChain\Chain\InputProcessor\SystemPromptInputProcessor;
use PhpLlm\LlmChain\Chain\InputProcessorInterface;
use PhpLlm\LlmChain\Chain\OutputProcessorInterface;
use PhpLlm\LlmChain\Chain\StructuredOutput\ChainProcessor as StructureOutputProcessor;
use PhpLlm\LlmChain\Chain\Toolbox\Attribute\AsTool;
use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor as ToolProcessor;
use PhpLlm\LlmChain\Chain\Toolbox\FaultTolerantToolbox;
use PhpLlm\LlmChain\Chain\Toolbox\Tool\Chain as ChainTool;
use PhpLlm\LlmChain\Chain\Toolbox\ToolFactory\ChainFactory;
use PhpLlm\LlmChain\Chain\Toolbox\ToolFactory\MemoryToolFactory;
use PhpLlm\LlmChain\Chain\Toolbox\ToolFactory\ReflectionToolFactory;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
use PhpLlm\LlmChain\Platform\Bridge\Azure\OpenAI\PlatformFactory as AzureOpenAIPlatformFactory;
use PhpLlm\LlmChain\Platform\Bridge\Google\Gemini;
use PhpLlm\LlmChain\Platform\Bridge\Google\PlatformFactory as GooglePlatformFactory;
use PhpLlm\LlmChain\Platform\Bridge\Meta\Llama;
use PhpLlm\LlmChain\Platform\Bridge\OpenAI\Embeddings;
use PhpLlm\LlmChain\Platform\Bridge\OpenAI\GPT;
use PhpLlm\LlmChain\Platform\Bridge\OpenAI\PlatformFactory as OpenAIPlatformFactory;
use PhpLlm\LlmChain\Platform\Bridge\Voyage\Voyage;
use PhpLlm\LlmChain\Platform\ModelClientInterface;
use PhpLlm\LlmChain\Platform\Platform;
use PhpLlm\LlmChain\Platform\PlatformInterface;
use PhpLlm\LlmChain\Platform\ResponseConverterInterface;
use PhpLlm\LlmChain\Store\Bridge\Azure\SearchStore as AzureSearchStore;
use PhpLlm\LlmChain\Store\Bridge\ChromaDB\Store as ChromaDBStore;
use PhpLlm\LlmChain\Store\Bridge\MongoDB\Store as MongoDBStore;
use PhpLlm\LlmChain\Store\Bridge\Pinecone\Store as PineconeStore;
use PhpLlm\LlmChain\Store\Embedder;
use PhpLlm\LlmChain\Store\StoreInterface;
use PhpLlm\LlmChain\Store\VectorStoreInterface;
use PhpLlm\LlmChainBundle\Profiler\DataCollector;
use PhpLlm\LlmChainBundle\Profiler\TraceablePlatform;
use PhpLlm\LlmChainBundle\Profiler\TraceableToolbox;
use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\AgentInterface;
use Symfony\AI\Agent\InputProcessor\SystemPromptInputProcessor;
use Symfony\AI\Agent\InputProcessorInterface;
use Symfony\AI\Agent\OutputProcessorInterface;
use Symfony\AI\Agent\StructuredOutput\AgentProcessor as StructureOutputProcessor;
use Symfony\AI\Agent\Toolbox\AgentProcessor as ToolProcessor;
use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
use Symfony\AI\Agent\Toolbox\FaultTolerantToolbox;
use Symfony\AI\Agent\Toolbox\Tool\Agent as AgentTool;
use Symfony\AI\Agent\Toolbox\ToolFactory\ChainFactory;
use Symfony\AI\Agent\Toolbox\ToolFactory\MemoryToolFactory;
use Symfony\AI\Agent\Toolbox\ToolFactory\ReflectionToolFactory;
use Symfony\AI\AIBundle\Profiler\DataCollector;
use Symfony\AI\AIBundle\Profiler\TraceablePlatform;
use Symfony\AI\AIBundle\Profiler\TraceableToolbox;
use Symfony\AI\Platform\Bridge\Anthropic\Claude;
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
use Symfony\AI\Platform\Bridge\Azure\OpenAI\PlatformFactory as AzureOpenAIPlatformFactory;
use Symfony\AI\Platform\Bridge\Google\Gemini;
use Symfony\AI\Platform\Bridge\Google\PlatformFactory as GooglePlatformFactory;
use Symfony\AI\Platform\Bridge\Meta\Llama;
use Symfony\AI\Platform\Bridge\OpenAI\Embeddings;
use Symfony\AI\Platform\Bridge\OpenAI\GPT;
use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory as OpenAIPlatformFactory;
use Symfony\AI\Platform\Bridge\Voyage\Voyage;
use Symfony\AI\Platform\ModelClientInterface;
use Symfony\AI\Platform\Platform;
use Symfony\AI\Platform\PlatformInterface;
use Symfony\AI\Platform\ResponseConverterInterface;
use Symfony\AI\Store\Bridge\Azure\SearchStore as AzureSearchStore;
use Symfony\AI\Store\Bridge\ChromaDB\Store as ChromaDBStore;
use Symfony\AI\Store\Bridge\MongoDB\Store as MongoDBStore;
use Symfony\AI\Store\Bridge\Pinecone\Store as PineconeStore;
use Symfony\AI\Store\Embedder;
use Symfony\AI\Store\StoreInterface;
use Symfony\AI\Store\VectorStoreInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -51,11 +58,14 @@ use Symfony\Component\DependencyInjection\Reference;
use function Symfony\Component\String\u;
final class LlmChainExtension extends Extension
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class AIExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new PhpFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config'));
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config'));
$loader->load('services.php');
$configuration = new Configuration();
@@ -63,8 +73,8 @@ final class LlmChainExtension extends Extension
foreach ($config['platform'] ?? [] as $type => $platform) {
$this->processPlatformConfig($type, $platform, $container);
}
$platforms = array_keys($container->findTaggedServiceIds('llm_chain.platform'));
if (1 === count($platforms)) {
$platforms = array_keys($container->findTaggedServiceIds('symfony_ai.platform'));
if (1 === \count($platforms)) {
$container->setAlias(PlatformInterface::class, reset($platforms));
}
if ($container->getParameter('kernel.debug')) {
@@ -72,24 +82,24 @@ final class LlmChainExtension extends Extension
$traceablePlatformDefinition = (new Definition(TraceablePlatform::class))
->setDecoratedService($platform)
->setAutowired(true)
->addTag('llm_chain.traceable_platform');
->addTag('symfony_ai.traceable_platform');
$suffix = u($platform)->afterLast('.')->toString();
$container->setDefinition('llm_chain.traceable_platform.'.$suffix, $traceablePlatformDefinition);
$container->setDefinition('symfony_ai.traceable_platform.'.$suffix, $traceablePlatformDefinition);
}
}
foreach ($config['chain'] as $chainName => $chain) {
$this->processChainConfig($chainName, $chain, $container);
foreach ($config['agent'] as $agentName => $agent) {
$this->processAgentConfig($agentName, $agent, $container);
}
if (1 === count($config['chain']) && isset($chainName)) {
$container->setAlias(ChainInterface::class, 'llm_chain.chain.'.$chainName);
if (1 === \count($config['agent']) && isset($agentName)) {
$container->setAlias(AgentInterface::class, 'symfony_ai.agent.'.$agentName);
}
foreach ($config['store'] ?? [] as $type => $store) {
$this->processStoreConfig($type, $store, $container);
}
$stores = array_keys($container->findTaggedServiceIds('llm_chain.store'));
if (1 === count($stores)) {
$stores = array_keys($container->findTaggedServiceIds('symfony_ai.store'));
if (1 === \count($stores)) {
$container->setAlias(VectorStoreInterface::class, reset($stores));
$container->setAlias(StoreInterface::class, reset($stores));
}
@@ -97,12 +107,12 @@ final class LlmChainExtension extends Extension
foreach ($config['embedder'] as $embedderName => $embedder) {
$this->processEmbedderConfig($embedderName, $embedder, $container);
}
if (1 === count($config['embedder']) && isset($embedderName)) {
$container->setAlias(Embedder::class, 'llm_chain.embedder.'.$embedderName);
if (1 === \count($config['embedder']) && isset($embedderName)) {
$container->setAlias(Embedder::class, 'symfony_ai.embedder.'.$embedderName);
}
$container->registerAttributeForAutoconfiguration(AsTool::class, static function (ChildDefinition $definition, AsTool $attribute): void {
$definition->addTag('llm_chain.tool', [
$definition->addTag('symfony_ai.tool', [
'name' => $attribute->name,
'description' => $attribute->description,
'method' => $attribute->method,
@@ -110,13 +120,13 @@ final class LlmChainExtension extends Extension
});
$container->registerForAutoconfiguration(InputProcessorInterface::class)
->addTag('llm_chain.chain.input_processor');
->addTag('symfony_ai.agent.input_processor');
$container->registerForAutoconfiguration(OutputProcessorInterface::class)
->addTag('llm_chain.chain.output_processor');
->addTag('symfony_ai.agent.output_processor');
$container->registerForAutoconfiguration(ModelClientInterface::class)
->addTag('llm_chain.platform.model_client');
->addTag('symfony_ai.platform.model_client');
$container->registerForAutoconfiguration(ResponseConverterInterface::class)
->addTag('llm_chain.platform.response_converter');
->addTag('symfony_ai.platform.response_converter');
if (false === $container->getParameter('kernel.debug')) {
$container->removeDefinition(DataCollector::class);
@@ -130,7 +140,7 @@ final class LlmChainExtension extends Extension
private function processPlatformConfig(string $type, array $platform, ContainerBuilder $container): void
{
if ('anthropic' === $type) {
$platformId = 'llm_chain.platform.anthropic';
$platformId = 'symfony_ai.platform.anthropic';
$definition = (new Definition(Platform::class))
->setFactory(AnthropicPlatformFactory::class.'::create')
->setAutowired(true)
@@ -139,7 +149,7 @@ final class LlmChainExtension extends Extension
->setArguments([
'$apiKey' => $platform['api_key'],
])
->addTag('llm_chain.platform');
->addTag('symfony_ai.platform');
if (isset($platform['version'])) {
$definition->replaceArgument('$version', $platform['version']);
@@ -152,7 +162,7 @@ final class LlmChainExtension extends Extension
if ('azure' === $type) {
foreach ($platform as $name => $config) {
$platformId = 'llm_chain.platform.azure.'.$name;
$platformId = 'symfony_ai.platform.azure.'.$name;
$definition = (new Definition(Platform::class))
->setFactory(AzureOpenAIPlatformFactory::class.'::create')
->setAutowired(true)
@@ -164,7 +174,7 @@ final class LlmChainExtension extends Extension
'$apiVersion' => $config['api_version'],
'$apiKey' => $config['api_key'],
])
->addTag('llm_chain.platform');
->addTag('symfony_ai.platform');
$container->setDefinition($platformId, $definition);
}
@@ -173,14 +183,14 @@ final class LlmChainExtension extends Extension
}
if ('google' === $type) {
$platformId = 'llm_chain.platform.google';
$platformId = 'symfony_ai.platform.google';
$definition = (new Definition(Platform::class))
->setFactory(GooglePlatformFactory::class.'::create')
->setAutowired(true)
->setLazy(true)
->addTag('proxy', ['interface' => PlatformInterface::class])
->setArguments(['$apiKey' => $platform['api_key']])
->addTag('llm_chain.platform');
->addTag('symfony_ai.platform');
$container->setDefinition($platformId, $definition);
@@ -188,27 +198,27 @@ final class LlmChainExtension extends Extension
}
if ('openai' === $type) {
$platformId = 'llm_chain.platform.openai';
$platformId = 'symfony_ai.platform.openai';
$definition = (new Definition(Platform::class))
->setFactory(OpenAIPlatformFactory::class.'::create')
->setAutowired(true)
->setLazy(true)
->addTag('proxy', ['interface' => PlatformInterface::class])
->setArguments(['$apiKey' => $platform['api_key']])
->addTag('llm_chain.platform');
->addTag('symfony_ai.platform');
$container->setDefinition($platformId, $definition);
return;
}
throw new \InvalidArgumentException(sprintf('Platform "%s" is not supported for configuration via bundle at this point.', $type));
throw new \InvalidArgumentException(\sprintf('Platform "%s" is not supported for configuration via bundle at this point.', $type));
}
/**
* @param array<string, mixed> $config
*/
private function processChainConfig(string $name, array $config, ContainerBuilder $container): void
private function processAgentConfig(string $name, array $config, ContainerBuilder $container): void
{
// MODEL
['name' => $modelName, 'version' => $version, 'options' => $options] = $config['model'];
@@ -218,23 +228,23 @@ final class LlmChainExtension extends Extension
'claude' => Claude::class,
'llama' => Llama::class,
'gemini' => Gemini::class,
default => throw new \InvalidArgumentException(sprintf('Model "%s" is not supported.', $modelName)),
default => throw new \InvalidArgumentException(\sprintf('Model "%s" is not supported.', $modelName)),
};
$modelDefinition = new Definition($modelClass);
if (null !== $version) {
$modelDefinition->setArgument('$name', $version);
}
if (0 !== count($options)) {
if (0 !== \count($options)) {
$modelDefinition->setArgument('$options', $options);
}
$modelDefinition->addTag('llm_chain.model.language_model');
$container->setDefinition('llm_chain.chain.'.$name.'.model', $modelDefinition);
$modelDefinition->addTag('symfony_ai.model.language_model');
$container->setDefinition('symfony_ai.agent.'.$name.'.model', $modelDefinition);
// CHAIN
$chainDefinition = (new Definition(Chain::class))
// AGENT
$agentDefinition = (new Definition(Agent::class))
->setAutowired(true)
->setArgument('$platform', new Reference($config['platform']))
->setArgument('$model', new Reference('llm_chain.chain.'.$name.'.model'));
->setArgument('$model', new Reference('symfony_ai.agent.'.$name.'.model'));
$inputProcessors = [];
$outputProcessors = [];
@@ -242,57 +252,57 @@ final class LlmChainExtension extends Extension
// TOOL & PROCESSOR
if ($config['tools']['enabled']) {
// Create specific toolbox and process if tools are explicitly defined
if (0 !== count($config['tools']['services'])) {
if (0 !== \count($config['tools']['services'])) {
$memoryFactoryDefinition = new Definition(MemoryToolFactory::class);
$container->setDefinition('llm_chain.toolbox.'.$name.'.memory_factory', $memoryFactoryDefinition);
$container->setDefinition('symfony_ai.toolbox.'.$name.'.memory_factory', $memoryFactoryDefinition);
$chainFactoryDefinition = new Definition(ChainFactory::class, [
'$factories' => [new Reference('llm_chain.toolbox.'.$name.'.memory_factory'), new Reference(ReflectionToolFactory::class)],
'$factories' => [new Reference('symfony_ai.toolbox.'.$name.'.memory_factory'), new Reference(ReflectionToolFactory::class)],
]);
$container->setDefinition('llm_chain.toolbox.'.$name.'.chain_factory', $chainFactoryDefinition);
$container->setDefinition('symfony_ai.toolbox.'.$name.'.chain_factory', $chainFactoryDefinition);
$tools = [];
foreach ($config['tools']['services'] as $tool) {
$reference = new Reference($tool['service']);
// We use the memory factory in case method, description and name are set
if (isset($tool['name'], $tool['description'])) {
if ($tool['is_chain']) {
$chainWrapperDefinition = new Definition(ChainTool::class, ['$chain' => $reference]);
$container->setDefinition('llm_chain.toolbox.'.$name.'.chain_wrapper.'.$tool['name'], $chainWrapperDefinition);
$reference = new Reference('llm_chain.toolbox.'.$name.'.chain_wrapper.'.$tool['name']);
if ($tool['is_agent']) {
$chainWrapperDefinition = new Definition(AgentTool::class, ['$agent' => $reference]);
$container->setDefinition('symfony_ai.toolbox.'.$name.'.agent_wrapper.'.$tool['name'], $chainWrapperDefinition);
$reference = new Reference('symfony_ai.toolbox.'.$name.'.agent_wrapper.'.$tool['name']);
}
$memoryFactoryDefinition->addMethodCall('addTool', [$reference, $tool['name'], $tool['description'], $tool['method'] ?? '__invoke']);
}
$tools[] = $reference;
}
$toolboxDefinition = (new ChildDefinition('llm_chain.toolbox.abstract'))
->replaceArgument('$toolFactory', new Reference('llm_chain.toolbox.'.$name.'.chain_factory'))
$toolboxDefinition = (new ChildDefinition('symfony_ai.toolbox.abstract'))
->replaceArgument('$toolFactory', new Reference('symfony_ai.toolbox.'.$name.'.chain_factory'))
->replaceArgument('$tools', $tools);
$container->setDefinition('llm_chain.toolbox.'.$name, $toolboxDefinition);
$container->setDefinition('symfony_ai.toolbox.'.$name, $toolboxDefinition);
if ($config['fault_tolerant_toolbox']) {
$faultTolerantToolboxDefinition = (new Definition('llm_chain.fault_tolerant_toolbox.'.$name))
$faultTolerantToolboxDefinition = (new Definition('symfony_ai.fault_tolerant_toolbox.'.$name))
->setClass(FaultTolerantToolbox::class)
->setAutowired(true)
->setDecoratedService('llm_chain.toolbox.'.$name);
$container->setDefinition('llm_chain.fault_tolerant_toolbox.'.$name, $faultTolerantToolboxDefinition);
->setDecoratedService('symfony_ai.toolbox.'.$name);
$container->setDefinition('symfony_ai.fault_tolerant_toolbox.'.$name, $faultTolerantToolboxDefinition);
}
if ($container->getParameter('kernel.debug')) {
$traceableToolboxDefinition = (new Definition('llm_chain.traceable_toolbox.'.$name))
$traceableToolboxDefinition = (new Definition('symfony_ai.traceable_toolbox.'.$name))
->setClass(TraceableToolbox::class)
->setAutowired(true)
->setDecoratedService('llm_chain.toolbox.'.$name)
->addTag('llm_chain.traceable_toolbox');
$container->setDefinition('llm_chain.traceable_toolbox.'.$name, $traceableToolboxDefinition);
->setDecoratedService('symfony_ai.toolbox.'.$name)
->addTag('symfony_ai.traceable_toolbox');
$container->setDefinition('symfony_ai.traceable_toolbox.'.$name, $traceableToolboxDefinition);
}
$toolProcessorDefinition = (new ChildDefinition('llm_chain.tool.chain_processor.abstract'))
->replaceArgument('$toolbox', new Reference('llm_chain.toolbox.'.$name));
$container->setDefinition('llm_chain.tool.chain_processor.'.$name, $toolProcessorDefinition);
$toolProcessorDefinition = (new ChildDefinition('symfony_ai.tool.agent_processor.abstract'))
->replaceArgument('$toolbox', new Reference('symfony_ai.toolbox.'.$name));
$container->setDefinition('symfony_ai.tool.agent_processor.'.$name, $toolProcessorDefinition);
$inputProcessors[] = new Reference('llm_chain.tool.chain_processor.'.$name);
$outputProcessors[] = new Reference('llm_chain.tool.chain_processor.'.$name);
$inputProcessors[] = new Reference('symfony_ai.tool.agent_processor.'.$name);
$outputProcessors[] = new Reference('symfony_ai.tool.agent_processor.'.$name);
} else {
$inputProcessors[] = new Reference(ToolProcessor::class);
$outputProcessors[] = new Reference(ToolProcessor::class);
@@ -306,23 +316,23 @@ final class LlmChainExtension extends Extension
}
// SYSTEM PROMPT
if (is_string($config['system_prompt'])) {
if (\is_string($config['system_prompt'])) {
$systemPromptInputProcessorDefinition = new Definition(SystemPromptInputProcessor::class);
$systemPromptInputProcessorDefinition
->setAutowired(true)
->setArguments([
'$systemPrompt' => $config['system_prompt'],
'$toolbox' => $config['include_tools'] ? new Reference('llm_chain.toolbox.'.$name) : null,
'$toolbox' => $config['include_tools'] ? new Reference('symfony_ai.toolbox.'.$name) : null,
]);
$inputProcessors[] = $systemPromptInputProcessorDefinition;
}
$chainDefinition
$agentDefinition
->setArgument('$inputProcessors', $inputProcessors)
->setArgument('$outputProcessors', $outputProcessors);
$container->setDefinition('llm_chain.chain.'.$name, $chainDefinition);
$container->setDefinition('symfony_ai.agent.'.$name, $agentDefinition);
}
/**
@@ -339,17 +349,17 @@ final class LlmChainExtension extends Extension
'$apiVersion' => $store['api_version'],
];
if (array_key_exists('vector_field', $store)) {
if (\array_key_exists('vector_field', $store)) {
$arguments['$vectorFieldName'] = $store['vector_field'];
}
$definition = new Definition(AzureSearchStore::class);
$definition
->setAutowired(true)
->addTag('llm_chain.store')
->addTag('symfony_ai.store')
->setArguments($arguments);
$container->setDefinition('llm_chain.store.'.$type.'.'.$name, $definition);
$container->setDefinition('symfony_ai.store.'.$type.'.'.$name, $definition);
}
}
@@ -359,9 +369,9 @@ final class LlmChainExtension extends Extension
$definition
->setAutowired(true)
->setArgument('$collectionName', $store['collection'])
->addTag('llm_chain.store');
->addTag('symfony_ai.store');
$container->setDefinition('llm_chain.store.'.$type.'.'.$name, $definition);
$container->setDefinition('symfony_ai.store.'.$type.'.'.$name, $definition);
}
}
@@ -373,21 +383,21 @@ final class LlmChainExtension extends Extension
'$indexName' => $store['index_name'],
];
if (array_key_exists('vector_field', $store)) {
if (\array_key_exists('vector_field', $store)) {
$arguments['$vectorFieldName'] = $store['vector_field'];
}
if (array_key_exists('bulk_write', $store)) {
if (\array_key_exists('bulk_write', $store)) {
$arguments['$bulkWrite'] = $store['bulk_write'];
}
$definition = new Definition(MongoDBStore::class);
$definition
->setAutowired(true)
->addTag('llm_chain.store')
->addTag('symfony_ai.store')
->setArguments($arguments);
$container->setDefinition('llm_chain.store.'.$type.'.'.$name, $definition);
$container->setDefinition('symfony_ai.store.'.$type.'.'.$name, $definition);
}
}
@@ -397,21 +407,21 @@ final class LlmChainExtension extends Extension
'$namespace' => $store['namespace'],
];
if (array_key_exists('filter', $store)) {
if (\array_key_exists('filter', $store)) {
$arguments['$filter'] = $store['filter'];
}
if (array_key_exists('top_k', $store)) {
if (\array_key_exists('top_k', $store)) {
$arguments['$topK'] = $store['top_k'];
}
$definition = new Definition(PineconeStore::class);
$definition
->setAutowired(true)
->addTag('llm_chain.store')
->addTag('symfony_ai.store')
->setArguments($arguments);
$container->setDefinition('llm_chain.store.'.$type.'.'.$name, $definition);
$container->setDefinition('symfony_ai.store.'.$type.'.'.$name, $definition);
}
}
}
@@ -426,24 +436,24 @@ final class LlmChainExtension extends Extension
$modelClass = match (strtolower((string) $modelName)) {
'embeddings' => Embeddings::class,
'voyage' => Voyage::class,
default => throw new \InvalidArgumentException(sprintf('Model "%s" is not supported.', $modelName)),
default => throw new \InvalidArgumentException(\sprintf('Model "%s" is not supported.', $modelName)),
};
$modelDefinition = (new Definition($modelClass));
if (null !== $version) {
$modelDefinition->setArgument('$name', $version);
}
if (0 !== count($options)) {
if (0 !== \count($options)) {
$modelDefinition->setArgument('$options', $options);
}
$modelDefinition->addTag('llm_chain.model.embeddings_model');
$container->setDefinition('llm_chain.embedder.'.$name.'.model', $modelDefinition);
$modelDefinition->addTag('symfony_ai.model.embeddings_model');
$container->setDefinition('symfony_ai.embedder.'.$name.'.model', $modelDefinition);
$definition = new Definition(Embedder::class, [
'$model' => new Reference('llm_chain.embedder.'.$name.'.model'),
'$model' => new Reference('symfony_ai.embedder.'.$name.'.model'),
'$platform' => new Reference($config['platform']),
'$store' => new Reference($config['store']),
]);
$container->setDefinition('llm_chain.embedder.'.$name, $definition);
$container->setDefinition('symfony_ai.embedder.'.$name, $definition);
}
}

View File

@@ -1,19 +1,29 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\LlmChainBundle\DependencyInjection;
namespace Symfony\AI\AIBundle\DependencyInjection;
use PhpLlm\LlmChain\Platform\PlatformInterface;
use PhpLlm\LlmChain\Store\StoreInterface;
use Symfony\AI\Platform\PlatformInterface;
use Symfony\AI\Store\StoreInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('llm_chain');
$treeBuilder = new TreeBuilder('ai');
$rootNode = $treeBuilder->getRootNode();
$rootNode
@@ -50,7 +60,7 @@ final class Configuration implements ConfigurationInterface
->end()
->end()
->end()
->arrayNode('chain')
->arrayNode('agent')
->normalizeKeys(false)
->useAttributeAsKey('name')
->arrayPrototype()
@@ -75,7 +85,7 @@ final class Configuration implements ConfigurationInterface
->thenInvalid('The default system prompt must not be an empty string')
->end()
->defaultNull()
->info('The default system prompt of the chain')
->info('The default system prompt of the agent')
->end()
->booleanNode('include_tools')
->info('Include tool definitions at the end of the system prompt')
@@ -104,7 +114,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('name')->end()
->scalarNode('description')->end()
->scalarNode('method')->end()
->booleanNode('is_chain')->defaultFalse()->end()
->booleanNode('is_agent')->defaultFalse()->end()
->end()
->beforeNormalization()
->ifString()

View File

@@ -1,11 +0,0 @@
<?php
declare(strict_types=1);
namespace PhpLlm\LlmChainBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
final class LlmChainBundle extends Bundle
{
}

View File

@@ -1,17 +1,26 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\LlmChainBundle\Profiler;
namespace Symfony\AI\AIBundle\Profiler;
use PhpLlm\LlmChain\Chain\Toolbox\ToolboxInterface;
use PhpLlm\LlmChain\Platform\Tool\Tool;
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
use Symfony\AI\Platform\Tool\Tool;
use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector;
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*
* @phpstan-import-type PlatformCallData from TraceablePlatform
* @phpstan-import-type ToolCallData from TraceableToolbox
*/

View File

@@ -1,15 +1,24 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\LlmChainBundle\Profiler;
namespace Symfony\AI\AIBundle\Profiler;
use PhpLlm\LlmChain\Platform\Message\Content\File;
use PhpLlm\LlmChain\Platform\Model;
use PhpLlm\LlmChain\Platform\PlatformInterface;
use PhpLlm\LlmChain\Platform\Response\ResponseInterface;
use Symfony\AI\Platform\Message\Content\File;
use Symfony\AI\Platform\Model;
use Symfony\AI\Platform\PlatformInterface;
use Symfony\AI\Platform\Response\ResponseInterface;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*
* @phpstan-type PlatformCallData array{
* model: Model,
* input: array<mixed>|string|object,
@@ -39,7 +48,7 @@ final class TraceablePlatform implements PlatformInterface
$this->calls[] = [
'model' => $model,
'input' => is_object($input) ? clone $input : $input,
'input' => \is_object($input) ? clone $input : $input,
'options' => $options,
'response' => $response->getContent(),
];

View File

@@ -1,13 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\LlmChainBundle\Profiler;
namespace Symfony\AI\AIBundle\Profiler;
use PhpLlm\LlmChain\Chain\Toolbox\ToolboxInterface;
use PhpLlm\LlmChain\Platform\Response\ToolCall;
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
use Symfony\AI\Platform\Response\ToolCall;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*
* @phpstan-type ToolCallData array{
* call: ToolCall,
* result: string,

View File

@@ -1,19 +1,26 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use PhpLlm\LlmChain\Chain\StructuredOutput\ChainProcessor as StructureOutputProcessor;
use PhpLlm\LlmChain\Chain\StructuredOutput\ResponseFormatFactory;
use PhpLlm\LlmChain\Chain\StructuredOutput\ResponseFormatFactoryInterface;
use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor as ToolProcessor;
use PhpLlm\LlmChain\Chain\Toolbox\Toolbox;
use PhpLlm\LlmChain\Chain\Toolbox\ToolboxInterface;
use PhpLlm\LlmChain\Chain\Toolbox\ToolFactory\ReflectionToolFactory;
use PhpLlm\LlmChain\Chain\Toolbox\ToolFactoryInterface;
use PhpLlm\LlmChainBundle\Profiler\DataCollector;
use PhpLlm\LlmChainBundle\Profiler\TraceableToolbox;
use Symfony\AI\Agent\StructuredOutput\AgentProcessor as StructureOutputProcessor;
use Symfony\AI\Agent\StructuredOutput\ResponseFormatFactory;
use Symfony\AI\Agent\StructuredOutput\ResponseFormatFactoryInterface;
use Symfony\AI\Agent\Toolbox\AgentProcessor as ToolProcessor;
use Symfony\AI\Agent\Toolbox\Toolbox;
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
use Symfony\AI\Agent\Toolbox\ToolFactory\ReflectionToolFactory;
use Symfony\AI\Agent\Toolbox\ToolFactoryInterface;
use Symfony\AI\AIBundle\Profiler\DataCollector;
use Symfony\AI\AIBundle\Profiler\TraceableToolbox;
return static function (ContainerConfigurator $container): void {
$container->services()
@@ -24,11 +31,11 @@ return static function (ContainerConfigurator $container): void {
->set(ResponseFormatFactory::class)
->alias(ResponseFormatFactoryInterface::class, ResponseFormatFactory::class)
->set(StructureOutputProcessor::class)
->tag('llm_chain.chain.input_processor')
->tag('llm_chain.chain.output_processor')
->tag('symfony_ai.agent.input_processor')
->tag('symfony_ai.agent.output_processor')
// tools
->set('llm_chain.toolbox.abstract')
->set('symfony_ai.toolbox.abstract')
->class(Toolbox::class)
->autowire()
->abstract()
@@ -37,23 +44,23 @@ return static function (ContainerConfigurator $container): void {
'$tools' => abstract_arg('Collection of tools'),
])
->set(Toolbox::class)
->parent('llm_chain.toolbox.abstract')
->parent('symfony_ai.toolbox.abstract')
->args([
'$tools' => tagged_iterator('llm_chain.tool'),
'$tools' => tagged_iterator('symfony_ai.tool'),
])
->alias(ToolboxInterface::class, Toolbox::class)
->set(ReflectionToolFactory::class)
->alias(ToolFactoryInterface::class, ReflectionToolFactory::class)
->set('llm_chain.tool.chain_processor.abstract')
->set('symfony_ai.tool.chain_processor.abstract')
->class(ToolProcessor::class)
->abstract()
->args([
'$toolbox' => abstract_arg('Toolbox'),
])
->set(ToolProcessor::class)
->parent('llm_chain.tool.chain_processor.abstract')
->tag('llm_chain.chain.input_processor')
->tag('llm_chain.chain.output_processor')
->parent('symfony_ai.tool.chain_processor.abstract')
->tag('symfony_ai.agent.input_processor')
->tag('symfony_ai.agent.output_processor')
->args([
'$toolbox' => service(ToolboxInterface::class),
'$eventDispatcher' => service('event_dispatcher')->nullOnInvalid(),
@@ -64,6 +71,6 @@ return static function (ContainerConfigurator $container): void {
->tag('data_collector')
->set(TraceableToolbox::class)
->decorate(ToolboxInterface::class)
->tag('llm_chain.traceable_toolbox')
->tag('symfony_ai.traceable_toolbox')
;
};

View File

@@ -3,7 +3,7 @@
{% block toolbar %}
{% if collector.platformCalls|length > 0 %}
{% set icon %}
{{ include('@LlmChain/icon.svg', { y: 18 }) }}
{{ include('@AI/icon.svg', { y: 18 }) }}
<span class="sf-toolbar-value">{{ collector.platformCalls|length }}</span>
<span class="sf-toolbar-info-piece-additional-detail">
<span class="sf-toolbar-label">calls</span>
@@ -37,8 +37,8 @@
{% block menu %}
<span class="label">
<span class="icon">{{ include('@LlmChain/icon.svg', { y: 16 }) }}</span>
<strong>LLM Chain</strong>
<span class="icon">{{ include('@AI/icon.svg', { y: 16 }) }}</span>
<strong>Symfony AI</strong>
<span class="count">{{ collector.platformCalls|length }}</span>
</span>
{% endblock %}
@@ -56,7 +56,7 @@
{% endmacro %}
{% block panel %}
<h2>LLM Chain</h2>
<h2>Symfony AI</h2>
<section class="metrics">
<div class="metric-group">
<div class="metric">

View File

@@ -12,5 +12,5 @@
}
</style>
</defs>
<text x="12" y="{{ y }}" font-family="Lilita One, Segoe UI Emoji" font-size="14px" fill="currentColor" text-anchor="middle">LLM</text>
<text x="12" y="{{ y }}" font-family="Lilita One, Segoe UI Emoji" font-size="14px" fill="currentColor" text-anchor="middle">AI</text>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,18 +1,25 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\LlmChainBundle\Tests\Profiler;
namespace Symfony\AI\AIBundle\Tests\Profiler;
use PhpLlm\LlmChain\Chain\Toolbox\ToolboxInterface;
use PhpLlm\LlmChain\Platform\Response\ToolCall;
use PhpLlm\LlmChain\Platform\Tool\ExecutionReference;
use PhpLlm\LlmChain\Platform\Tool\Tool;
use PhpLlm\LlmChainBundle\Profiler\TraceableToolbox;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Small;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
use Symfony\AI\AIBundle\Profiler\TraceableToolbox;
use Symfony\AI\Platform\Response\ToolCall;
use Symfony\AI\Platform\Tool\ExecutionReference;
use Symfony\AI\Platform\Tool\Tool;
#[CoversClass(TraceableToolbox::class)]
#[Small]