Skip to content

Commit aa9e657

Browse files
committed
Add optional translation to system prompts
So that system prompts can be written in any locale
1 parent d8dfb30 commit aa9e657

File tree

7 files changed

+110
-7
lines changed

7 files changed

+110
-7
lines changed

src/agent/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@ CHANGELOG
5454
* Add model capability detection before processing
5555
* Add comprehensive type safety with full PHP type hints
5656
* Add clear exception hierarchy for different error scenarios
57+
* Add translation support for system prompts

src/agent/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"symfony/property-access": "^6.4 || ^7.1",
3131
"symfony/property-info": "^6.4 || ^7.1",
3232
"symfony/serializer": "^6.4 || ^7.1",
33+
"symfony/translation-contracts": "^3.6",
3334
"symfony/type-info": "^7.2.3"
3435
},
3536
"require-dev": {

src/agent/src/InputProcessor/SystemPromptInputProcessor.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313

1414
use Psr\Log\LoggerInterface;
1515
use Psr\Log\NullLogger;
16+
use Symfony\AI\Agent\Exception\RuntimeException;
1617
use Symfony\AI\Agent\Input;
1718
use Symfony\AI\Agent\InputProcessorInterface;
1819
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
1920
use Symfony\AI\Platform\Message\Message;
2021
use Symfony\AI\Platform\Tool\Tool;
22+
use Symfony\Contracts\Translation\TranslatorInterface;
2123

2224
/**
2325
* @author Christopher Hertel <mail@christopher-hertel.de>
@@ -31,6 +33,9 @@
3133
public function __construct(
3234
private \Stringable|string $systemPrompt,
3335
private ?ToolboxInterface $toolbox = null,
36+
private ?TranslatorInterface $translator = null,
37+
private bool $enableTranslation = false,
38+
private ?string $translationDomain = null,
3439
private LoggerInterface $logger = new NullLogger(),
3540
) {
3641
}
@@ -45,7 +50,7 @@ public function processInput(Input $input): void
4550
return;
4651
}
4752

48-
$message = (string) $this->systemPrompt;
53+
$message = $this->translateIfEnabled();
4954

5055
if ($this->toolbox instanceof ToolboxInterface
5156
&& [] !== $this->toolbox->getTools()
@@ -61,7 +66,7 @@ public function processInput(Input $input): void
6166
));
6267

6368
$message = <<<PROMPT
64-
{$this->systemPrompt}
69+
{$message}
6570
6671
# Tools
6772
@@ -73,4 +78,15 @@ public function processInput(Input $input): void
7378

7479
$input->messages = $messages->prepend(Message::forSystem($message));
7580
}
81+
82+
private function translateIfEnabled(): string
83+
{
84+
if ($this->enableTranslation && !$this->translator) {
85+
throw new RuntimeException('Prompt translation is enabled but no translator was provided.');
86+
}
87+
88+
return $this->enableTranslation
89+
? $this->translator->trans((string) $this->systemPrompt, [], $this->translationDomain)
90+
: (string) $this->systemPrompt;
91+
}
7692
}

src/agent/tests/InputProcessor/SystemPromptInputProcessorTest.php

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Symfony\AI\Platform\Result\ToolCall;
3030
use Symfony\AI\Platform\Tool\ExecutionReference;
3131
use Symfony\AI\Platform\Tool\Tool;
32+
use Symfony\Contracts\Translation\TranslatorInterface;
3233

3334
#[CoversClass(SystemPromptInputProcessor::class)]
3435
#[UsesClass(Gpt::class)]
@@ -89,7 +90,7 @@ public function execute(ToolCall $toolCall): mixed
8990
{
9091
return null;
9192
}
92-
}
93+
},
9394
);
9495

9596
$input = new Input(new Gpt(), new MessageBag(Message::ofUser('This is a user message')));
@@ -105,7 +106,7 @@ public function execute(ToolCall $toolCall): mixed
105106
public function testIncludeToolDefinitions()
106107
{
107108
$processor = new SystemPromptInputProcessor(
108-
'This is a system prompt',
109+
'This is a',
109110
new class implements ToolboxInterface {
110111
public function getTools(): array
111112
{
@@ -127,7 +128,9 @@ public function execute(ToolCall $toolCall): mixed
127128
{
128129
return null;
129130
}
130-
}
131+
},
132+
$this->getTranslator(),
133+
true,
131134
);
132135

133136
$input = new Input(new Gpt(), new MessageBag(Message::ofUser('This is a user message')));
@@ -138,7 +141,7 @@ public function execute(ToolCall $toolCall): mixed
138141
$this->assertInstanceOf(SystemMessage::class, $messages[0]);
139142
$this->assertInstanceOf(UserMessage::class, $messages[1]);
140143
$this->assertSame(<<<PROMPT
141-
This is a system prompt
144+
This is a cool translated system prompt
142145
143146
# Tools
144147
@@ -169,7 +172,7 @@ public function execute(ToolCall $toolCall): mixed
169172
{
170173
return null;
171174
}
172-
}
175+
},
173176
);
174177

175178
$input = new Input(new Gpt(), new MessageBag(Message::ofUser('This is a user message')));
@@ -190,4 +193,69 @@ public function execute(ToolCall $toolCall): mixed
190193
A tool without parameters
191194
PROMPT, $messages[0]->content);
192195
}
196+
197+
public function testWithTranslatedSystemPrompt()
198+
{
199+
$processor = new SystemPromptInputProcessor('This is a', null, $this->getTranslator(), true);
200+
201+
$input = new Input(new Gpt(), new MessageBag(Message::ofUser('This is a user message')), []);
202+
$processor->processInput($input);
203+
204+
$messages = $input->messages->getMessages();
205+
$this->assertCount(2, $messages);
206+
$this->assertInstanceOf(SystemMessage::class, $messages[0]);
207+
$this->assertInstanceOf(UserMessage::class, $messages[1]);
208+
$this->assertSame('This is a cool translated system prompt', $messages[0]->content);
209+
}
210+
211+
public function testWithTranslationDomainSystemPrompt()
212+
{
213+
$processor = new SystemPromptInputProcessor(
214+
'This is a',
215+
null,
216+
$this->getTranslator(),
217+
true,
218+
'prompts'
219+
);
220+
221+
$input = new Input(new Gpt(), new MessageBag(), []);
222+
$processor->processInput($input);
223+
224+
$messages = $input->messages->getMessages();
225+
$this->assertCount(1, $messages);
226+
$this->assertInstanceOf(SystemMessage::class, $messages[0]);
227+
$this->assertSame('This is a cool translated system prompt with a translation domain', $messages[0]->content);
228+
}
229+
230+
public function testWithMissingTranslator()
231+
{
232+
$processor = new SystemPromptInputProcessor(
233+
'This is a',
234+
null,
235+
null,
236+
true,
237+
);
238+
239+
$input = new Input(new Gpt(), new MessageBag(), []);
240+
241+
$this->expectExceptionMessage('Prompt translation is enabled but no translator was provided');
242+
$processor->processInput($input);
243+
}
244+
245+
private function getTranslator(): TranslatorInterface
246+
{
247+
return new class implements TranslatorInterface {
248+
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
249+
{
250+
$translated = \sprintf('%s cool translated system prompt', $id);
251+
252+
return $domain ? $translated.' with a translation domain' : $translated;
253+
}
254+
255+
public function getLocale(): string
256+
{
257+
return 'en';
258+
}
259+
};
260+
}
193261
}

src/ai-bundle/config/options.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@
162162
->info('Include tool definitions at the end of the system prompt')
163163
->defaultFalse()
164164
->end()
165+
->booleanNode('enable_translation')
166+
->info('Enable translation for the system prompt')
167+
->defaultFalse()
168+
->end()
169+
->scalarNode('translation_domain')
170+
->info('The translation domain for the system prompt')
171+
->defaultNull()
172+
->end()
165173
->end()
166174
->end()
167175
->arrayNode('tools')

src/ai-bundle/src/AiBundle.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,9 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde
611611
->setArguments([
612612
$config['prompt']['text'],
613613
$includeTools ? new Reference('ai.toolbox.'.$name) : null,
614+
new Reference('translator', ContainerInterface::NULL_ON_INVALID_REFERENCE),
615+
$config['prompt']['enable_translation'],
616+
$config['prompt']['translation_domain'],
614617
new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
615618
])
616619
->addTag('ai.agent.input_processor', ['agent' => $agentId, 'priority' => -30]);

src/ai-bundle/tests/DependencyInjection/AiBundleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,11 +471,13 @@ public function testMultipleAgentsWithProcessors()
471471
$firstSystemPrompt = $container->getDefinition('ai.agent.first_agent.system_prompt_processor');
472472
$firstSystemTags = $firstSystemPrompt->getTag('ai.agent.input_processor');
473473
$this->assertSame($firstAgentId, $firstSystemTags[0]['agent']);
474+
$this->assertCount(3, array_filter($firstSystemPrompt->getArguments()));
474475

475476
// Second agent system prompt processor
476477
$secondSystemPrompt = $container->getDefinition('ai.agent.second_agent.system_prompt_processor');
477478
$secondSystemTags = $secondSystemPrompt->getTag('ai.agent.input_processor');
478479
$this->assertSame($secondAgentId, $secondSystemTags[0]['agent']);
480+
$this->assertCount(3, array_filter($secondSystemPrompt->getArguments()));
479481
}
480482

481483
#[TestDox('Processors work correctly when using the default toolbox')]
@@ -658,6 +660,8 @@ public function testSystemPromptWithArrayStructure()
658660
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'],
659661
'prompt' => [
660662
'text' => 'You are a helpful assistant.',
663+
'enable_translation' => true,
664+
'translation_domain' => 'prompts',
661665
],
662666
'tools' => [
663667
['service' => 'some_tool', 'description' => 'Test tool'],
@@ -673,6 +677,8 @@ public function testSystemPromptWithArrayStructure()
673677

674678
$this->assertSame('You are a helpful assistant.', $arguments[0]);
675679
$this->assertNull($arguments[1]); // include_tools is false, so null reference
680+
$this->assertTrue($arguments[3]);
681+
$this->assertSame('prompts', $arguments[4]);
676682
}
677683

678684
#[TestDox('System prompt with include_tools enabled works correctly')]

0 commit comments

Comments
 (0)