99use Cortex \Support \Utils ;
1010use Cortex \LLM \Data \Usage ;
1111use Cortex \Prompts \Prompt ;
12+ use Cortex \Contracts \ToolKit ;
1213use Cortex \Memory \ChatMemory ;
1314use Cortex \Contracts \Pipeable ;
15+ use Cortex \LLM \Data \ChatResult ;
1416use Cortex \LLM \Enums \ToolChoice ;
17+ use Cortex \Memory \Contracts \Store ;
1518use Cortex \Agents \Stages \AppendUsage ;
1619use Cortex \LLM \Data \ChatStreamResult ;
1720use Cortex \Memory \Stores \InMemoryStore ;
2528use Cortex \Prompts \Builders \ChatPromptBuilder ;
2629use Cortex \LLM \Data \Messages \MessagePlaceholder ;
2730use Cortex \Prompts \Templates \ChatPromptTemplate ;
31+ use Cortex \Contracts \ChatMemory as ChatMemoryContract ;
2832
2933class Agent implements Pipeable
3034{
3135 protected LLMContract $ llm ;
3236
3337 protected ChatPromptTemplate $ prompt ;
3438
35- protected ChatMemory $ memory ;
39+ protected ChatMemoryContract $ memory ;
3640
3741 protected Usage $ usage ;
3842
3943 /**
4044 * @param class-string|\Cortex\JsonSchema\Types\ObjectSchema $output
41- * @param array<int, \Cortex\LLM\Contracts\Tool|\Closure|string> $tools
45+ * @param array<int, \Cortex\LLM\Contracts\Tool|\Closure|string>|\Cortex\Contracts\ToolKit $tools
4246 * @param array<string, mixed> $initialPromptVariables
4347 */
4448 public function __construct (
4549 protected string $ name ,
4650 ChatPromptTemplate |ChatPromptBuilder |string |null $ prompt = null ,
4751 LLMContract |string |null $ llm = null ,
4852 protected ?string $ description = null ,
49- protected array $ tools = [],
53+ protected array | ToolKit $ tools = [],
5054 protected ToolChoice |string $ toolChoice = ToolChoice::Auto,
5155 protected ObjectSchema |string |null $ output = null ,
56+ protected ?Store $ memoryStore = null ,
5257 protected array $ initialPromptVariables = [],
5358 protected int $ maxSteps = 5 ,
5459 protected bool $ strict = true ,
5560 ) {
56- if ($ prompt !== null ) {
57- $ this ->prompt = match (true ) {
58- is_string ($ prompt ) => Prompt::builder ('chat ' )
59- ->messages ([new SystemMessage ($ prompt )])
60- ->strict ($ this ->strict )
61- ->initialVariables ($ this ->initialPromptVariables )
62- ->build (),
63- $ prompt instanceof ChatPromptBuilder => $ prompt ->build (),
64- default => $ prompt ,
65- };
66-
67- $ this ->prompt ->addMessage (new MessagePlaceholder ('messages ' ));
68- } else {
69- $ this ->prompt = new ChatPromptTemplate ([
70- new MessagePlaceholder ('messages ' ),
71- ], $ this ->initialPromptVariables );
72- }
73-
74- $ this ->memory = new ChatMemory (new InMemoryStore ($ this ->prompt ->messages ->withoutPlaceholders ()));
61+ $ this ->prompt = self ::buildPromptTemplate ($ prompt , $ strict , $ initialPromptVariables );
62+ $ this ->memory = self ::buildMemory ($ this ->prompt , $ this ->memoryStore );
63+ $ this ->llm = self ::buildLLM (
64+ $ this ->prompt ,
65+ $ this ->name ,
66+ $ llm ,
67+ $ this ->tools ,
68+ $ this ->toolChoice ,
69+ $ this ->output ,
70+ $ this ->strict ,
71+ );
7572 $ this ->usage = Usage::empty ();
76-
77- $ this ->llm = $ llm !== null
78- ? Utils::toLLM ($ llm )
79- : $ this ->prompt ->metadata ?->toLLM() ?? Utils::toLLM ($ llm );
80-
81- if ($ this ->tools !== []) {
82- $ this ->llm ->withTools ($ this ->tools , $ this ->toolChoice );
83- }
84-
85- if ($ this ->output !== null ) {
86- $ this ->llm ->withStructuredOutput (
87- output: $ this ->output ,
88- name: $ this ->name ,
89- strict: $ this ->strict ,
90- );
91- }
9273 }
9374
9475 public function pipeline (bool $ shouldParseOutput = true ): Pipeline
@@ -124,7 +105,7 @@ public function executionPipeline(bool $shouldParseOutput = true): Pipeline
124105 * @param array<int, \Cortex\LLM\Contracts\Message> $messages
125106 * @param array<string, mixed> $input
126107 */
127- public function invoke (array $ messages = [], array $ input = []): mixed
108+ public function invoke (array $ messages = [], array $ input = []): ChatResult
128109 {
129110 // $this->id ??= $this->generateId();
130111 $ this ->memory ->setVariables ([
@@ -135,10 +116,13 @@ public function invoke(array $messages = [], array $input = []): mixed
135116 $ messages = $ this ->memory ->getMessages ()->merge ($ messages );
136117 $ this ->memory ->setMessages ($ messages );
137118
138- return $ this ->pipeline ()->invoke ([
119+ /** @var \Cortex\LLM\Data\ChatResult $result */
120+ $ result = $ this ->pipeline ()->invoke ([
139121 ...$ input ,
140122 'messages ' => $ this ->memory ->getMessages (),
141123 ]);
124+
125+ return $ result ;
142126 }
143127
144128 /**
@@ -185,7 +169,7 @@ public function handlePipeable(mixed $payload, Closure $next): mixed
185169 default => throw new PipelineException ('Invalid input for agent. ' ),
186170 };
187171
188- return $ next ($ this ->invoke ($ payload ));
172+ return $ next ($ this ->invoke (input: $ payload ));
189173 }
190174
191175 public function getName (): string
@@ -208,7 +192,9 @@ public function getPrompt(): ChatPromptTemplate
208192 */
209193 public function getTools (): array
210194 {
211- return $ this ->tools ;
195+ return $ this ->tools instanceof ToolKit
196+ ? $ this ->tools ->getTools ()
197+ : $ this ->tools ;
212198 }
213199
214200 public function getLLM (): LLMContract
@@ -225,4 +211,74 @@ public function getUsage(): Usage
225211 {
226212 return $ this ->usage ;
227213 }
214+
215+ /**
216+ * Build the prompt template for the agent.
217+ */
218+ protected static function buildPromptTemplate (
219+ ChatPromptTemplate |ChatPromptBuilder |string |null $ prompt = null ,
220+ bool $ strict = true ,
221+ array $ initialPromptVariables = [],
222+ ): ChatPromptTemplate {
223+ $ promptTemplate = match (true ) {
224+ $ prompt === null => new ChatPromptTemplate ([], $ initialPromptVariables ),
225+ is_string ($ prompt ) => Prompt::builder ('chat ' )
226+ ->messages ([new SystemMessage ($ prompt )])
227+ ->strict ($ strict )
228+ ->initialVariables ($ initialPromptVariables )
229+ ->build (),
230+ $ prompt instanceof ChatPromptBuilder => $ prompt ->build (),
231+ default => $ prompt ,
232+ };
233+
234+ $ promptTemplate ->addMessage (new MessagePlaceholder ('messages ' ));
235+
236+ return $ promptTemplate ;
237+ }
238+
239+ /**
240+ * Build the memory instance for the agent.
241+ */
242+ protected static function buildMemory (ChatPromptTemplate $ prompt , ?Store $ memoryStore = null ): ChatMemoryContract
243+ {
244+ $ store = $ memoryStore ?? new InMemoryStore ();
245+ $ store ->setMessages ($ prompt ->messages ->withoutPlaceholders ());
246+
247+ return new ChatMemory ($ store );
248+ }
249+
250+ /**
251+ * Build the LLM instance for the agent.
252+ */
253+ protected static function buildLLM (
254+ ChatPromptTemplate $ prompt ,
255+ string $ name ,
256+ ?LLMContract $ llm = null ,
257+ array $ tools = [],
258+ ToolChoice |string $ toolChoice = ToolChoice::Auto,
259+ ObjectSchema |string |null $ output = null ,
260+ bool $ strict = true ,
261+ ): LLMContract {
262+ $ llm = $ llm !== null
263+ ? Utils::llm ($ llm )
264+ : $ prompt ->metadata ?->llm() ?? Utils::llm ($ llm );
265+
266+ // The LLM instance will already contain the configuration from
267+ // the prompt metadata if it was provided.
268+ // Below those can be overridden.
269+
270+ if ($ tools !== []) {
271+ $ llm ->withTools ($ tools , $ toolChoice );
272+ }
273+
274+ if ($ output !== null ) {
275+ $ llm ->withStructuredOutput (
276+ output: $ output ,
277+ name: $ name ,
278+ strict: $ strict ,
279+ );
280+ }
281+
282+ return $ llm ;
283+ }
228284}
0 commit comments