Skip to content

Commit 9ed9428

Browse files
committed
[AI Bundle][Agent] Add support for prompt as file
1 parent f42af63 commit 9ed9428

File tree

12 files changed

+259
-27
lines changed

12 files changed

+259
-27
lines changed

demo/config/packages/ai.yaml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@ ai:
1313
method: 'now'
1414
stream:
1515
model: 'gpt-4o-mini'
16-
prompt: |
17-
You are an example chat application where messages from the LLM are streamed to the user using
18-
Server-Sent Events via `symfony/ux-turbo` / Turbo Streams. This example does not use any custom
19-
javascript and solely relies on the built-in `live` & `turbo_stream` Stimulus controllers.
20-
Whatever the user asks, tell them about the application & used technologies.
16+
prompt:
17+
file: '%kernel.project_dir%/prompts/stream-chat.txt'
2118
tools: false
2219
youtube:
2320
model: 'gpt-4o-mini'

demo/prompts/stream-chat.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
You are an example chat application where messages from the LLM are streamed to the user using
2+
Server-Sent Events via `symfony/ux-turbo` / Turbo Streams. This example does not use any custom
3+
javascript and solely relies on the built-in `live` & `turbo_stream` Stimulus controllers.
4+
Whatever the user asks, tell them about the application & used technologies.

examples/misc/prompt-json-file.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Agent\InputProcessor\SystemPromptInputProcessor;
14+
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
15+
use Symfony\AI\Platform\Message\Content\File;
16+
use Symfony\AI\Platform\Message\Message;
17+
use Symfony\AI\Platform\Message\MessageBag;
18+
19+
require_once dirname(__DIR__).'/bootstrap.php';
20+
21+
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY'], http_client());
22+
23+
// Load system prompt from a JSON file
24+
$promptFile = File::fromFile(dirname(__DIR__, 2).'/fixtures/prompts/code-reviewer.json');
25+
$systemPromptProcessor = new SystemPromptInputProcessor($promptFile);
26+
27+
$agent = new Agent($platform, 'gpt-4o-mini', [$systemPromptProcessor], logger: logger());
28+
$messages = new MessageBag(Message::ofUser('Review this code: function add($a, $b) { return $a + $b; }'));
29+
$result = $agent->call($messages);
30+
31+
echo $result->getContent().\PHP_EOL;

examples/misc/prompt-text-file.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Agent\InputProcessor\SystemPromptInputProcessor;
14+
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
15+
use Symfony\AI\Platform\Message\Content\File;
16+
use Symfony\AI\Platform\Message\Message;
17+
use Symfony\AI\Platform\Message\MessageBag;
18+
19+
require_once dirname(__DIR__).'/bootstrap.php';
20+
21+
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY'], http_client());
22+
23+
// Load system prompt from a plain text file (.txt)
24+
$promptFile = File::fromFile(dirname(__DIR__, 2).'/fixtures/prompts/helpful-assistant.txt');
25+
$systemPromptProcessor = new SystemPromptInputProcessor($promptFile);
26+
27+
$agent = new Agent($platform, 'gpt-4o-mini', [$systemPromptProcessor], logger: logger());
28+
$messages = new MessageBag(Message::ofUser('Can you explain what dependency injection is?'));
29+
$result = $agent->call($messages);
30+
31+
echo $result->getContent().\PHP_EOL;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"role": "You are an expert code reviewer with deep knowledge of software engineering best practices, design patterns, and code quality.",
3+
"responsibilities": [
4+
"Review code for bugs and potential issues",
5+
"Suggest improvements for code quality and maintainability",
6+
"Identify security vulnerabilities",
7+
"Recommend better design patterns when appropriate",
8+
"Ensure code follows language-specific best practices"
9+
],
10+
"tone": "constructive and educational",
11+
"approach": "Provide thorough but concise feedback with specific suggestions and examples when helpful"
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
You are a helpful and knowledgeable assistant. Your goal is to provide accurate, concise, and useful responses to user queries.
2+
3+
Guidelines:
4+
- Be clear and direct in your responses
5+
- Provide examples when appropriate
6+
- If you're unsure about something, say so
7+
- Be respectful and professional at all times
8+
- Break down complex topics into understandable explanations

src/agent/src/InputProcessor/SystemPromptInputProcessor.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\AI\Agent\Input;
1818
use Symfony\AI\Agent\InputProcessorInterface;
1919
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
20+
use Symfony\AI\Platform\Message\Content\File;
2021
use Symfony\AI\Platform\Message\Message;
2122
use Symfony\AI\Platform\Tool\Tool;
2223
use Symfony\Contracts\Translation\TranslatableInterface;
@@ -28,11 +29,11 @@
2829
final readonly class SystemPromptInputProcessor implements InputProcessorInterface
2930
{
3031
/**
31-
* @param \Stringable|TranslatableInterface|string $systemPrompt the system prompt to prepend to the input messages
32-
* @param ToolboxInterface|null $toolbox the tool box to be used to append the tool definitions to the system prompt
32+
* @param \Stringable|TranslatableInterface|string|File $systemPrompt the system prompt to prepend to the input messages, or a File object to read from
33+
* @param ToolboxInterface|null $toolbox the tool box to be used to append the tool definitions to the system prompt
3334
*/
3435
public function __construct(
35-
private \Stringable|TranslatableInterface|string $systemPrompt,
36+
private \Stringable|TranslatableInterface|string|File $systemPrompt,
3637
private ?ToolboxInterface $toolbox = null,
3738
private ?TranslatorInterface $translator = null,
3839
private LoggerInterface $logger = new NullLogger(),
@@ -52,9 +53,13 @@ public function processInput(Input $input): void
5253
return;
5354
}
5455

55-
$message = $this->systemPrompt instanceof TranslatableInterface
56-
? $this->systemPrompt->trans($this->translator)
57-
: (string) $this->systemPrompt;
56+
if ($this->systemPrompt instanceof File) {
57+
$message = $this->systemPrompt->asBinary();
58+
} elseif ($this->systemPrompt instanceof TranslatableInterface) {
59+
$message = $this->systemPrompt->trans($this->translator);
60+
} else {
61+
$message = (string) $this->systemPrompt;
62+
}
5863

5964
if ($this->toolbox instanceof ToolboxInterface
6065
&& [] !== $this->toolbox->getTools()

src/agent/tests/InputProcessor/SystemPromptInputProcessorTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\AI\Fixtures\Tool\ToolNoParams;
1919
use Symfony\AI\Fixtures\Tool\ToolRequiredParams;
2020
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
21+
use Symfony\AI\Platform\Message\Content\File;
2122
use Symfony\AI\Platform\Message\Message;
2223
use Symfony\AI\Platform\Message\MessageBag;
2324
use Symfony\AI\Platform\Message\SystemMessage;
@@ -221,6 +222,49 @@ public function testWithMissingTranslator()
221222
);
222223
}
223224

225+
public function testProcessInputWithFile()
226+
{
227+
$tempFile = tempnam(sys_get_temp_dir(), 'prompt_');
228+
file_put_contents($tempFile, 'This is a system prompt from a file');
229+
230+
try {
231+
$file = File::fromFile($tempFile);
232+
$processor = new SystemPromptInputProcessor($file);
233+
234+
$input = new Input(new Gpt('gpt-4o'), new MessageBag(Message::ofUser('This is a user message')));
235+
$processor->processInput($input);
236+
237+
$messages = $input->messages->getMessages();
238+
$this->assertCount(2, $messages);
239+
$this->assertInstanceOf(SystemMessage::class, $messages[0]);
240+
$this->assertInstanceOf(UserMessage::class, $messages[1]);
241+
$this->assertSame('This is a system prompt from a file', $messages[0]->content);
242+
} finally {
243+
unlink($tempFile);
244+
}
245+
}
246+
247+
public function testProcessInputWithMultilineFile()
248+
{
249+
$tempFile = tempnam(sys_get_temp_dir(), 'prompt_');
250+
file_put_contents($tempFile, "Line 1\nLine 2\nLine 3");
251+
252+
try {
253+
$file = File::fromFile($tempFile);
254+
$processor = new SystemPromptInputProcessor($file);
255+
256+
$input = new Input(new Gpt('gpt-4o'), new MessageBag(Message::ofUser('This is a user message')));
257+
$processor->processInput($input);
258+
259+
$messages = $input->messages->getMessages();
260+
$this->assertCount(2, $messages);
261+
$this->assertInstanceOf(SystemMessage::class, $messages[0]);
262+
$this->assertSame("Line 1\nLine 2\nLine 3", $messages[0]->content);
263+
} finally {
264+
unlink($tempFile);
265+
}
266+
}
267+
224268
private function getTranslator(): TranslatorInterface
225269
{
226270
return new class implements TranslatorInterface {

src/ai-bundle/config/options.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -306,22 +306,35 @@
306306
return ['text' => $v];
307307
})
308308
->end()
309-
->beforeNormalization()
310-
->ifArray()
311-
->then(function (array $v) {
312-
if (!isset($v['text']) && !isset($v['include_tools'])) {
313-
throw new \InvalidArgumentException('Either "text" or "include_tools" must be configured for prompt.');
309+
->validate()
310+
->ifTrue(function ($v) {
311+
if (!\is_array($v)) {
312+
return false;
314313
}
314+
$hasTextOrFile = isset($v['text']) || isset($v['file']);
315315

316-
return $v;
316+
return !$hasTextOrFile;
317+
})
318+
->thenInvalid('Either "text" or "file" must be configured for prompt.')
319+
->end()
320+
->validate()
321+
->ifTrue(function ($v) {
322+
return \is_array($v) && isset($v['text']) && isset($v['file']);
317323
})
324+
->thenInvalid('Cannot use both "text" and "file" for prompt. Choose one.')
318325
->end()
319326
->validate()
320327
->ifTrue(function ($v) {
321-
return \is_array($v) && '' === trim($v['text'] ?? '');
328+
return \is_array($v) && isset($v['text']) && '' === trim($v['text']);
322329
})
323330
->thenInvalid('The "text" cannot be empty.')
324331
->end()
332+
->validate()
333+
->ifTrue(function ($v) {
334+
return \is_array($v) && isset($v['file']) && '' === trim($v['file']);
335+
})
336+
->thenInvalid('The "file" cannot be empty.')
337+
->end()
325338
->validate()
326339
->ifTrue(function ($v) {
327340
return \is_array($v) && ($v['enabled'] ?? false) && !interface_exists(TranslatorInterface::class);
@@ -332,6 +345,9 @@
332345
->stringNode('text')
333346
->info('The system prompt text')
334347
->end()
348+
->stringNode('file')
349+
->info('Path to file containing the system prompt')
350+
->end()
335351
->booleanNode('include_tools')
336352
->info('Include tool definitions at the end of the system prompt')
337353
->defaultFalse()

src/ai-bundle/doc/index.rst

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,62 @@ For more control, such as including tool definitions in the system prompt, use t
275275
276276
The array format supports these options:
277277

278-
* ``text`` (string, required): The system prompt text that will be sent to the AI model
278+
* ``text`` (string): The system prompt text that will be sent to the AI model (either ``text`` or ``file`` is required)
279+
* ``file`` (string): Path to a file containing the system prompt (either ``text`` or ``file`` is required)
279280
* ``include_tools`` (boolean, optional): When set to ``true``, tool definitions will be appended to the system prompt
280281
* ``enable_translation`` (boolean, optional): When set to ``true``, enables translation for the system prompt text (requires symfony/translation)
281282
* ``translation_domain`` (string, optional): The translation domain to use for the system prompt translation
282283

284+
.. note::
285+
286+
You cannot use both ``text`` and ``file`` simultaneously. Choose one option based on your needs.
287+
288+
**File-Based Prompts**
289+
290+
For better organization and reusability, you can store system prompts in external files. This is particularly useful for:
291+
292+
* Long, complex prompts with multiple sections
293+
* Prompts shared across multiple agents or projects
294+
* Version-controlled prompt templates
295+
* JSON-structured prompts with specific formatting
296+
297+
Configure the prompt with a file path:
298+
299+
.. code-block:: yaml
300+
301+
ai:
302+
agent:
303+
my_agent:
304+
model: 'gpt-4o-mini'
305+
prompt:
306+
file: '%kernel.project_dir%/prompts/assistant.txt'
307+
308+
The file can be in any text format (.txt, .json, .md, etc.). The entire content of the file will be used as the system prompt text.
309+
310+
**Example Text File** (``prompts/assistant.txt``):
311+
312+
.. code-block:: text
313+
314+
You are a helpful and knowledgeable assistant.
315+
316+
Guidelines:
317+
- Be clear and direct in your responses
318+
- Provide examples when appropriate
319+
- Be respectful and professional at all times
320+
321+
**Example JSON File** (``prompts/code-reviewer.json``):
322+
323+
.. code-block:: json
324+
325+
{
326+
"role": "You are an expert code reviewer",
327+
"responsibilities": [
328+
"Review code for bugs and potential issues",
329+
"Suggest improvements for code quality"
330+
],
331+
"tone": "constructive and educational"
332+
}
333+
283334
**Translation Support**
284335

285336
To use translated system prompts, you need to have the Symfony Translation component installed:

0 commit comments

Comments
 (0)