Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions demo/config/packages/ai.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ ai:
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Gpt::GPT_4O_MINI
options:
temperature: 0.5
system_prompt: 'Please answer the users question based on Wikipedia and provide a link to the article.'
include_tools: true
system_prompt:
prompt: 'Please answer the users question based on Wikipedia and provide a link to the article.'
include_tools: true
tools:
- 'Symfony\AI\Agent\Toolbox\Tool\Wikipedia'
audio:
Expand Down
40 changes: 31 additions & 9 deletions src/ai-bundle/config/options.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,39 @@
->end()
->end()
->booleanNode('structured_output')->defaultTrue()->end()
->scalarNode('system_prompt')
->arrayNode('system_prompt')
->info('The system prompt configuration')
->beforeNormalization()
->ifString()
->then(function (string $v) {
return ['prompt' => $v];
})
->end()
->beforeNormalization()
->ifArray()
->then(function (array $v) {
if (!isset($v['prompt']) && !isset($v['include_tools'])) {
throw new \InvalidArgumentException('Either "prompt" must be configured for system_prompt.');
}

return $v;
})
->end()
->validate()
->ifTrue(fn ($v) => null !== $v && '' === trim($v))
->thenInvalid('The default system prompt must not be an empty string')
->ifTrue(function ($v) {
return \is_array($v) && '' === trim($v['prompt'] ?? '');
})
->thenInvalid('The "prompt" cannot be empty.')
->end()
->children()
->scalarNode('prompt')
->info('The system prompt text')
->end()
->booleanNode('include_tools')
->info('Include tool definitions at the end of the system prompt')
->defaultFalse()
->end()
->end()
->defaultNull()
->info('The default system prompt of the agent')
->end()
->booleanNode('include_tools')
->info('Include tool definitions at the end of the system prompt')
->defaultFalse()
->end()
->arrayNode('tools')
->addDefaultsIfNotSet()
Expand Down
42 changes: 40 additions & 2 deletions src/ai-bundle/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ Configuration
model:
class: 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Gpt::GPT_4O_MINI
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
system_prompt: # The system prompt configuration
prompt: 'You are a helpful assistant that can answer questions.' # The prompt text
include_tools: true # Include tool definitions at the end of the system prompt
tools:
# Referencing a service with #[AsTool] attribute
- 'Symfony\AI\Agent\Toolbox\Tool\SimilaritySearch'
Expand Down Expand Up @@ -144,6 +145,43 @@ Configuration
vectorizer: 'ai.vectorizer.mistral_embeddings'
store: 'ai.store.memory.research'

System Prompt Configuration
---------------------------

For basic usage, specify the system prompt as a simple string:

.. code-block:: yaml

ai:
agent:
my_agent:
model:
class: 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Gpt::GPT_4O_MINI
system_prompt: 'You are a helpful assistant.'

**Advanced Configuration**

For more control, such as including tool definitions in the system prompt, use the array format:

.. code-block:: yaml

ai:
agent:
my_agent:
model:
class: 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Gpt::GPT_4O_MINI
system_prompt:
prompt: 'You are a helpful assistant that can answer questions.'
include_tools: true # Include tool definitions at the end of the system prompt

The array format supports these options:

* ``prompt`` (string, required): The system prompt text that will be sent to the AI model
* ``include_tools`` (boolean, optional): When set to ``true``, tool definitions will be appended to the system prompt


Usage
-----

Expand Down
8 changes: 5 additions & 3 deletions src/ai-bundle/src/AiBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -604,11 +604,13 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde
}

// SYSTEM PROMPT
if (\is_string($config['system_prompt'])) {
if (isset($config['system_prompt'])) {
$includeTools = isset($config['system_prompt']['include_tools']) && $config['system_prompt']['include_tools'];

$systemPromptInputProcessorDefinition = (new Definition(SystemPromptInputProcessor::class))
->setArguments([
$config['system_prompt'],
$config['include_tools'] ? new Reference('ai.toolbox.'.$name) : null,
$config['system_prompt']['prompt'],
$includeTools ? new Reference('ai.toolbox.'.$name) : null,
new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
])
->addTag('ai.agent.input_processor', ['agent' => $agentId, 'priority' => -30]);
Expand Down
200 changes: 198 additions & 2 deletions src/ai-bundle/tests/DependencyInjection/AiBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,200 @@ public function testPerplexityPlatformConfiguration()
$this->assertSame('ai.platform.contract.perplexity', (string) $arguments[2]);
}

#[TestDox('System prompt with array structure works correctly')]
public function testSystemPromptWithArrayStructure()
{
$container = $this->buildContainer([
'ai' => [
'agent' => [
'test_agent' => [
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'],
'system_prompt' => [
'prompt' => 'You are a helpful assistant.',
],
'tools' => [
['service' => 'some_tool', 'description' => 'Test tool'],
],
],
],
],
]);

$this->assertTrue($container->hasDefinition('ai.agent.test_agent.system_prompt_processor'));
$definition = $container->getDefinition('ai.agent.test_agent.system_prompt_processor');
$arguments = $definition->getArguments();

$this->assertSame('You are a helpful assistant.', $arguments[0]);
$this->assertNull($arguments[1]); // include_tools is false, so null reference
}

#[TestDox('System prompt with include_tools enabled works correctly')]
public function testSystemPromptWithIncludeToolsEnabled()
{
$container = $this->buildContainer([
'ai' => [
'agent' => [
'test_agent' => [
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'],
'system_prompt' => [
'prompt' => 'You are a helpful assistant.',
'include_tools' => true,
],
'tools' => [
['service' => 'some_tool', 'description' => 'Test tool'],
],
],
],
],
]);

$this->assertTrue($container->hasDefinition('ai.agent.test_agent.system_prompt_processor'));
$definition = $container->getDefinition('ai.agent.test_agent.system_prompt_processor');
$arguments = $definition->getArguments();

$this->assertSame('You are a helpful assistant.', $arguments[0]);
$this->assertInstanceOf(Reference::class, $arguments[1]);
$this->assertSame('ai.toolbox.test_agent', (string) $arguments[1]);
}

#[TestDox('System prompt with only prompt key defaults include_tools to false')]
public function testSystemPromptWithOnlyPromptKey()
{
$container = $this->buildContainer([
'ai' => [
'agent' => [
'test_agent' => [
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'],
'system_prompt' => [
'prompt' => 'You are a helpful assistant.',
],
'tools' => [
['service' => 'some_tool', 'description' => 'Test tool'],
],
],
],
],
]);

$this->assertTrue($container->hasDefinition('ai.agent.test_agent.system_prompt_processor'));
$definition = $container->getDefinition('ai.agent.test_agent.system_prompt_processor');
$arguments = $definition->getArguments();

$this->assertSame('You are a helpful assistant.', $arguments[0]);
$this->assertNull($arguments[1]); // include_tools defaults to false
}

#[TestDox('Agent without system prompt does not create processor')]
public function testAgentWithoutSystemPrompt()
{
$container = $this->buildContainer([
'ai' => [
'agent' => [
'test_agent' => [
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'],
],
],
],
]);

$this->assertFalse($container->hasDefinition('ai.agent.test_agent.system_prompt_processor'));
}

#[TestDox('Valid system prompt creates processor correctly')]
public function testValidSystemPromptCreatesProcessor()
{
// This test verifies that valid system prompts work correctly with new structure
$container = $this->buildContainer([
'ai' => [
'agent' => [
'test_agent' => [
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'],
'system_prompt' => [
'prompt' => 'Valid prompt',
'include_tools' => true,
],
'tools' => [
['service' => 'some_tool', 'description' => 'Test tool'],
],
],
],
],
]);

$this->assertTrue($container->hasDefinition('ai.agent.test_agent.system_prompt_processor'));
$definition = $container->getDefinition('ai.agent.test_agent.system_prompt_processor');
$arguments = $definition->getArguments();

$this->assertSame('Valid prompt', $arguments[0]);
$this->assertInstanceOf(Reference::class, $arguments[1]);
$this->assertSame('ai.toolbox.test_agent', (string) $arguments[1]);
}

#[TestDox('Empty prompt in array structure throws configuration exception')]
public function testEmptyPromptInArrayThrowsException()
{
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('The "prompt" cannot be empty.');

$this->buildContainer([
'ai' => [
'agent' => [
'test_agent' => [
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'],
'system_prompt' => [
'prompt' => '',
],
],
],
],
]);
}

#[TestDox('System prompt array without prompt key throws configuration exception')]
public function testSystemPromptArrayWithoutPromptKeyThrowsException()
{
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('The "prompt" cannot be empty.');

$this->buildContainer([
'ai' => [
'agent' => [
'test_agent' => [
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'],
'system_prompt' => [
'include_tools' => true,
],
],
],
],
]);
}

#[TestDox('System prompt with string format works correctly')]
public function testSystemPromptWithStringFormat()
{
$container = $this->buildContainer([
'ai' => [
'agent' => [
'test_agent' => [
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'],
'system_prompt' => 'You are a helpful assistant.',
'tools' => [
['service' => 'some_tool', 'description' => 'Test tool'],
],
],
],
],
]);

$this->assertTrue($container->hasDefinition('ai.agent.test_agent.system_prompt_processor'));
$definition = $container->getDefinition('ai.agent.test_agent.system_prompt_processor');
$arguments = $definition->getArguments();

$this->assertSame('You are a helpful assistant.', $arguments[0]);
$this->assertNull($arguments[1]); // include_tools not enabled with string format
}

public function testVectorizerConfiguration()
{
$container = $this->buildContainer([
Expand Down Expand Up @@ -1076,8 +1270,10 @@ private function getFullConfig(): array
],
'structured_output' => false,
'track_token_usage' => true,
'system_prompt' => 'You are a helpful assistant.',
'include_tools' => true,
'system_prompt' => [
'prompt' => 'You are a helpful assistant.',
'include_tools' => true,
],
'tools' => [
'enabled' => true,
'services' => [
Expand Down