1818use Cortex \Contracts \OutputParser ;
1919use Cortex \Support \Traits \CanPipe ;
2020use Cortex \Exceptions \LLMException ;
21+ use Cortex \LLM \Data \ChatGeneration ;
2122use Cortex \JsonSchema \SchemaFactory ;
2223use Cortex \ModelInfo \Data \ModelInfo ;
2324use Cortex \LLM \Data \ChatStreamResult ;
2425use Cortex \Exceptions \PipelineException ;
26+ use Cortex \LLM \Data \ChatGenerationChunk ;
2527use Cortex \ModelInfo \Enums \ModelFeature ;
2628use Cortex \JsonSchema \Types \ObjectSchema ;
2729use Cortex \LLM \Data \Messages \UserMessage ;
@@ -50,6 +52,8 @@ abstract class AbstractLLM implements LLM
5052
5153 protected ?StructuredOutputConfig $ structuredOutputConfig = null ;
5254
55+ protected StructuredOutputMode $ structuredOutputMode = StructuredOutputMode::Auto;
56+
5357 protected ?OutputParser $ outputParser = null ;
5458
5559 protected ?string $ outputParserError = null ;
@@ -64,6 +68,8 @@ abstract class AbstractLLM implements LLM
6468
6569 protected bool $ shouldApplyFormatInstructions = false ;
6670
71+ protected bool $ shouldParseOutput = true ;
72+
6773 /**
6874 * @var array<\Cortex\ModelInfo\Enums\ModelFeature>
6975 */
@@ -116,15 +122,19 @@ public function output(OutputParser $parser): Pipeline
116122 /**
117123 * @param array<int, \Cortex\LLM\Contracts\Tool|\Cortex\JsonSchema\Contracts\Schema|\Closure|string> $tools
118124 */
119- public function withTools (array $ tools , ToolChoice |string $ toolChoice = ToolChoice::Auto): static
120- {
125+ public function withTools (
126+ array $ tools ,
127+ ToolChoice |string $ toolChoice = ToolChoice::Auto,
128+ bool $ allowParallelToolCalls = true ,
129+ ): static {
121130 $ this ->supportsFeatureOrFail (ModelFeature::ToolCalling);
122131
123132 $ this ->toolConfig = $ tools === []
124133 ? null
125134 : new ToolConfig (
126135 Utils::toToolCollection ($ tools )->all (),
127136 $ toolChoice ,
137+ $ allowParallelToolCalls ,
128138 );
129139
130140 return $ this ;
@@ -133,9 +143,15 @@ public function withTools(array $tools, ToolChoice|string $toolChoice = ToolChoi
133143 /**
134144 * Add a tool to the LLM.
135145 */
136- public function addTool (Tool |Closure |string $ tool , ToolChoice |string $ toolChoice = ToolChoice::Auto): static
137- {
138- return $ this ->withTools ([...($ this ->toolConfig ->tools ?? []), $ tool ], $ toolChoice );
146+ public function addTool (
147+ Tool |Closure |string $ tool ,
148+ ToolChoice |string $ toolChoice = ToolChoice::Auto,
149+ bool $ allowParallelToolCalls = true ,
150+ ): static {
151+ return $ this ->withTools ([
152+ ...($ this ->toolConfig ->tools ?? []),
153+ $ tool ,
154+ ], $ toolChoice , $ allowParallelToolCalls );
139155 }
140156
141157 /**
@@ -166,6 +182,7 @@ public function withStructuredOutput(
166182 bool $ strict = true ,
167183 StructuredOutputMode $ outputMode = StructuredOutputMode::Auto,
168184 ): static {
185+ $ this ->structuredOutputMode = $ outputMode ;
169186 [$ schema , $ outputParser ] = $ this ->resolveSchemaAndOutputParser ($ output , $ strict );
170187
171188 $ this ->withOutputParser ($ outputParser );
@@ -386,6 +403,64 @@ protected static function applyFormatInstructions(
386403 return $ messages ;
387404 }
388405
406+ public function shouldParseOutput (bool $ shouldParseOutput = true ): static
407+ {
408+ $ this ->shouldParseOutput = $ shouldParseOutput ;
409+
410+ return $ this ;
411+ }
412+
413+ protected function applyOutputParserIfApplicable (
414+ ChatGeneration |ChatGenerationChunk $ generationOrChunk ,
415+ ): ChatGeneration |ChatGenerationChunk {
416+ if ($ this ->shouldParseOutput && $ this ->outputParser !== null ) {
417+ try {
418+ $ parsedOutput = $ this ->outputParser ->parse ($ generationOrChunk );
419+ $ generationOrChunk = $ generationOrChunk ->cloneWithParsedOutput ($ parsedOutput );
420+ } catch (OutputParserException $ e ) {
421+ $ this ->outputParserError = $ e ->getMessage ();
422+ }
423+ }
424+
425+ return $ generationOrChunk ;
426+ }
427+
428+ /**
429+ * Apply the format instructions to the messages if applicable.
430+ *
431+ * @return \Cortex\LLM\Data\Messages\MessageCollection<int, \Cortex\LLM\Contracts\Message>
432+ */
433+ protected function applyFormatInstructionsIfApplicable (
434+ MessageCollection $ messages ,
435+ ): MessageCollection {
436+ if ($ this ->shouldApplyFormatInstructions && $ formatInstructions = $ this ->outputParser ?->formatInstructions()) {
437+ return static ::applyFormatInstructions ($ messages , $ formatInstructions );
438+ }
439+
440+ return $ messages ;
441+ }
442+
443+ /**
444+ * Resolve the messages to be used for the LLM.
445+ *
446+ * @param \Cortex\LLM\Data\Messages\MessageCollection|\Cortex\LLM\Contracts\Message|non-empty-array<int, \Cortex\LLM\Contracts\Message|\Cortex\LLM\Data\Messages\MessagePlaceholder>|string $messages
447+ *
448+ * @throws \Cortex\Exceptions\LLMException If no messages are provided
449+ *
450+ * @return \Cortex\LLM\Data\Messages\MessageCollection<int, \Cortex\LLM\Contracts\Message>
451+ */
452+ protected function resolveMessages (
453+ MessageCollection |Message |array |string $ messages ,
454+ ): MessageCollection {
455+ $ messages = Utils::toMessageCollection ($ messages )->withoutPlaceholders ();
456+
457+ if ($ messages ->isEmpty ()) {
458+ throw new LLMException ('You must provide at least one message to the LLM. ' );
459+ }
460+
461+ return $ this ->applyFormatInstructionsIfApplicable ($ messages );
462+ }
463+
389464 /**
390465 * Resolve the schema and output parser from the given output type.
391466 *
0 commit comments