15
15
use PhpLlm \LlmChain \Model \Response \TextResponse ;
16
16
use PhpLlm \LlmChain \Tests \Double \ConfigurableResponseFormatFactory ;
17
17
use PhpLlm \LlmChain \Tests \Fixture \SomeStructure ;
18
+ use PhpLlm \LlmChain \Tests \Fixture \StructuredOutput \MathReasoning ;
19
+ use PhpLlm \LlmChain \Tests \Fixture \StructuredOutput \Step ;
18
20
use PHPUnit \Framework \Attributes \CoversClass ;
19
21
use PHPUnit \Framework \Attributes \Test ;
20
22
use PHPUnit \Framework \Attributes \UsesClass ;
21
23
use PHPUnit \Framework \TestCase ;
22
- use Symfony \Component \Serializer \Encoder \JsonEncoder ;
23
- use Symfony \Component \Serializer \Normalizer \ObjectNormalizer ;
24
- use Symfony \Component \Serializer \Serializer ;
25
24
use Symfony \Component \Serializer \SerializerInterface ;
26
25
27
26
#[CoversClass(ChainProcessor::class)]
@@ -37,9 +36,7 @@ final class ChainProcessorTest extends TestCase
37
36
#[Test]
38
37
public function processInputWithOutputStructure (): void
39
38
{
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 ' ]));
43
40
44
41
$ llm = self ::createMock (LanguageModel::class);
45
42
$ llm ->method ('supportsStructuredOutput ' )->willReturn (true );
@@ -54,9 +51,7 @@ public function processInputWithOutputStructure(): void
54
51
#[Test]
55
52
public function processInputWithoutOutputStructure (): void
56
53
{
57
- $ responseFormatFactory = new ConfigurableResponseFormatFactory ();
58
- $ serializer = new Serializer ([new ObjectNormalizer ()], [new JsonEncoder ()]);
59
- $ chainProcessor = new ChainProcessor ($ responseFormatFactory , $ serializer );
54
+ $ chainProcessor = new ChainProcessor (new ConfigurableResponseFormatFactory ());
60
55
61
56
$ llm = self ::createMock (LanguageModel::class);
62
57
$ input = new Input ($ llm , new MessageBag (), []);
@@ -71,9 +66,7 @@ public function processInputThrowsExceptionWhenLlmDoesNotSupportStructuredOutput
71
66
{
72
67
self ::expectException (MissingModelSupport::class);
73
68
74
- $ responseFormatFactory = new ConfigurableResponseFormatFactory ();
75
- $ serializer = new Serializer ([new ObjectNormalizer ()], [new JsonEncoder ()]);
76
- $ chainProcessor = new ChainProcessor ($ responseFormatFactory , $ serializer );
69
+ $ chainProcessor = new ChainProcessor (new ConfigurableResponseFormatFactory ());
77
70
78
71
$ llm = self ::createMock (LanguageModel::class);
79
72
$ llm ->method ('supportsStructuredOutput ' )->willReturn (false );
@@ -86,9 +79,7 @@ public function processInputThrowsExceptionWhenLlmDoesNotSupportStructuredOutput
86
79
#[Test]
87
80
public function processOutputWithResponseFormat (): void
88
81
{
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 ' ]));
92
83
93
84
$ llm = self ::createMock (LanguageModel::class);
94
85
$ llm ->method ('supportsStructuredOutput ' )->willReturn (true );
@@ -108,6 +99,61 @@ public function processOutputWithResponseFormat(): void
108
99
self ::assertSame ('data ' , $ output ->response ->getContent ()->some );
109
100
}
110
101
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
+
111
157
#[Test]
112
158
public function processOutputWithoutResponseFormat (): void
113
159
{
0 commit comments