1515use PhpLlm \LlmChain \Model \Response \TextResponse ;
1616use PhpLlm \LlmChain \Tests \Double \ConfigurableResponseFormatFactory ;
1717use PhpLlm \LlmChain \Tests \Fixture \SomeStructure ;
18+ use PhpLlm \LlmChain \Tests \Fixture \StructuredOutput \MathReasoning ;
19+ use PhpLlm \LlmChain \Tests \Fixture \StructuredOutput \Step ;
1820use PHPUnit \Framework \Attributes \CoversClass ;
1921use PHPUnit \Framework \Attributes \Test ;
2022use PHPUnit \Framework \Attributes \UsesClass ;
2123use PHPUnit \Framework \TestCase ;
22- use Symfony \Component \Serializer \Encoder \JsonEncoder ;
23- use Symfony \Component \Serializer \Normalizer \ObjectNormalizer ;
24- use Symfony \Component \Serializer \Serializer ;
2524use Symfony \Component \Serializer \SerializerInterface ;
2625
2726#[CoversClass(ChainProcessor::class)]
@@ -37,9 +36,7 @@ final class ChainProcessorTest extends TestCase
3736 #[Test]
3837 public function processInputWithOutputStructure (): void
3938 {
40- $ responseFormatFactory = new ConfigurableResponseFormatFactory (['some ' => 'format ' ]);
41- $ serializer = new Serializer ([new ObjectNormalizer ()], [new JsonEncoder ()]);
42- $ chainProcessor = new ChainProcessor ($ responseFormatFactory , $ serializer );
39+ $ chainProcessor = new ChainProcessor (new ConfigurableResponseFormatFactory (['some ' => 'format ' ]));
4340
4441 $ llm = self ::createMock (LanguageModel::class);
4542 $ llm ->method ('supportsStructuredOutput ' )->willReturn (true );
@@ -54,9 +51,7 @@ public function processInputWithOutputStructure(): void
5451 #[Test]
5552 public function processInputWithoutOutputStructure (): void
5653 {
57- $ responseFormatFactory = new ConfigurableResponseFormatFactory ();
58- $ serializer = new Serializer ([new ObjectNormalizer ()], [new JsonEncoder ()]);
59- $ chainProcessor = new ChainProcessor ($ responseFormatFactory , $ serializer );
54+ $ chainProcessor = new ChainProcessor (new ConfigurableResponseFormatFactory ());
6055
6156 $ llm = self ::createMock (LanguageModel::class);
6257 $ input = new Input ($ llm , new MessageBag (), []);
@@ -71,9 +66,7 @@ public function processInputThrowsExceptionWhenLlmDoesNotSupportStructuredOutput
7166 {
7267 self ::expectException (MissingModelSupport::class);
7368
74- $ responseFormatFactory = new ConfigurableResponseFormatFactory ();
75- $ serializer = new Serializer ([new ObjectNormalizer ()], [new JsonEncoder ()]);
76- $ chainProcessor = new ChainProcessor ($ responseFormatFactory , $ serializer );
69+ $ chainProcessor = new ChainProcessor (new ConfigurableResponseFormatFactory ());
7770
7871 $ llm = self ::createMock (LanguageModel::class);
7972 $ llm ->method ('supportsStructuredOutput ' )->willReturn (false );
@@ -86,9 +79,7 @@ public function processInputThrowsExceptionWhenLlmDoesNotSupportStructuredOutput
8679 #[Test]
8780 public function processOutputWithResponseFormat (): void
8881 {
89- $ responseFormatFactory = new ConfigurableResponseFormatFactory (['some ' => 'format ' ]);
90- $ serializer = new Serializer ([new ObjectNormalizer ()], [new JsonEncoder ()]);
91- $ chainProcessor = new ChainProcessor ($ responseFormatFactory , $ serializer );
82+ $ chainProcessor = new ChainProcessor (new ConfigurableResponseFormatFactory (['some ' => 'format ' ]));
9283
9384 $ llm = self ::createMock (LanguageModel::class);
9485 $ llm ->method ('supportsStructuredOutput ' )->willReturn (true );
@@ -108,6 +99,61 @@ public function processOutputWithResponseFormat(): void
10899 self ::assertSame ('data ' , $ output ->response ->getContent ()->some );
109100 }
110101
102+ #[Test]
103+ public function processOutputWithComplexResponseFormat (): void
104+ {
105+ $ chainProcessor = new ChainProcessor (new ConfigurableResponseFormatFactory (['some ' => 'format ' ]));
106+
107+ $ llm = self ::createMock (LanguageModel::class);
108+ $ llm ->method ('supportsStructuredOutput ' )->willReturn (true );
109+
110+ $ options = ['output_structure ' => MathReasoning::class];
111+ $ input = new Input ($ llm , new MessageBag (), $ options );
112+ $ chainProcessor ->processInput ($ input );
113+
114+ $ response = new TextResponse (<<<JSON
115+ {
116+ "steps": [
117+ {
118+ "explanation": "We want to isolate the term with x. First, let's subtract 7 from both sides of the equation.",
119+ "output": "8x + 7 - 7 = -23 - 7"
120+ },
121+ {
122+ "explanation": "This simplifies to 8x = -30.",
123+ "output": "8x = -30"
124+ },
125+ {
126+ "explanation": "Next, to solve for x, we need to divide both sides of the equation by 8.",
127+ "output": "x = -30 / 8"
128+ },
129+ {
130+ "explanation": "Now we simplify -30 / 8 to its simplest form.",
131+ "output": "x = -15 / 4"
132+ },
133+ {
134+ "explanation": "Dividing both the numerator and the denominator by their greatest common divisor, we finalize our solution.",
135+ "output": "x = -3.75"
136+ }
137+ ],
138+ "finalAnswer": "x = -3.75"
139+ }
140+ JSON );
141+
142+ $ output = new Output ($ llm , $ response , new MessageBag (), $ input ->getOptions ());
143+
144+ $ chainProcessor ->processOutput ($ output );
145+
146+ self ::assertInstanceOf (StructuredResponse::class, $ output ->response );
147+ self ::assertInstanceOf (MathReasoning::class, $ structure = $ output ->response ->getContent ());
148+ self ::assertCount (5 , $ structure ->steps );
149+ self ::assertInstanceOf (Step::class, $ structure ->steps [0 ]);
150+ self ::assertInstanceOf (Step::class, $ structure ->steps [1 ]);
151+ self ::assertInstanceOf (Step::class, $ structure ->steps [2 ]);
152+ self ::assertInstanceOf (Step::class, $ structure ->steps [3 ]);
153+ self ::assertInstanceOf (Step::class, $ structure ->steps [4 ]);
154+ self ::assertSame ('x = -3.75 ' , $ structure ->finalAnswer );
155+ }
156+
111157 #[Test]
112158 public function processOutputWithoutResponseFormat (): void
113159 {
0 commit comments