Skip to content

Commit 81b39d8

Browse files
reworked data collector, use profiler_dump for display
1 parent 95d76eb commit 81b39d8

File tree

4 files changed

+75
-162
lines changed

4 files changed

+75
-162
lines changed

src/ai-bundle/config/services.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,13 @@
129129
])
130130
->tag('kernel.event_listener')
131131

132-
->set('ai.traceable_agent', TraceableAgent::class)
133-
->decorate(AgentInterface::class, priority: 5)
134-
->args([
135-
service('.inner'),
136-
service('ai.data_collector'),
137-
])
138-
139132
// profiler
133+
->set('ai.traceable_agent', TraceableAgent::class)
134+
->decorate(AgentInterface::class, priority: 5)
135+
->args([
136+
service('.inner'),
137+
service('ai.data_collector'),
138+
])
140139
->set('ai.data_collector', DataCollector::class)
141140
->args([
142141
tagged_iterator('ai.traceable_platform'),

src/ai-bundle/src/Profiler/DataCollector.php

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
use Symfony\Component\HttpFoundation\Request;
1919
use Symfony\Component\HttpFoundation\Response;
2020
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
21+
use Symfony\Component\VarDumper\Cloner\Data;
2122

2223
/**
2324
* @author Christopher Hertel <[email protected]>
2425
*
25-
* @phpstan-import-type PlatformCallData from TraceablePlatform
2626
* @phpstan-import-type ToolCallData from TraceableToolbox
2727
*/
2828
final class DataCollector extends AbstractDataCollector implements LateDataCollectorInterface
@@ -40,7 +40,7 @@ final class DataCollector extends AbstractDataCollector implements LateDataColle
4040
/**
4141
* @var list<array{method: string, duration: float, input: mixed, result: mixed, error: ?\Throwable}>
4242
*/
43-
private array $collectedChatCalls = [];
43+
private array $collectedCalls = [];
4444

4545
/**
4646
* @param TraceablePlatform[] $platforms
@@ -61,22 +61,51 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
6161

6262
public function lateCollect(): void
6363
{
64+
$platformCalls = [];
65+
foreach ($this->platforms as $platform) {
66+
$calls = $platform->calls;
67+
foreach ($calls as $call) {
68+
$result = $call['result']->await();
69+
if (isset($platform->resultCache[$result])) {
70+
$call['result'] = $platform->resultCache[$result];
71+
} else {
72+
$call['result'] = $result->getContent();
73+
}
74+
75+
$call['model'] = $this->cloneVar($call['model']);
76+
$call['input'] = $this->cloneVar($call['input']);
77+
$call['options'] = $this->cloneVar($call['options']);
78+
$call['result'] = $this->cloneVar($call['result']);
79+
80+
$platformCalls[] = $call;
81+
}
82+
}
83+
84+
$toolCalls = [];
85+
foreach ($this->toolboxes as $toolbox) {
86+
foreach ($toolbox->calls as $call) {
87+
$call['call'] = $this->cloneVar($call['call']);
88+
$call['result'] = $this->cloneVar($call['result']);
89+
$toolCalls[] = $call;
90+
}
91+
}
92+
6493
$this->data = [
6594
'tools' => $this->defaultToolBox->getTools(),
66-
'platform_calls' => array_merge(...array_map($this->awaitCallResults(...), $this->platforms)),
67-
'tool_calls' => array_merge(...array_map(fn (TraceableToolbox $toolbox) => $toolbox->calls, $this->toolboxes)),
68-
'chat_calls' => $this->cloneVar($this->collectedChatCalls),
95+
'platform_calls' => $platformCalls,
96+
'tool_calls' => $toolCalls,
97+
'chat_calls' => $this->collectedCalls,
6998
];
7099
}
71100

72101
public function collectAgentCall(string $method, float $duration, mixed $input, mixed $result, ?\Throwable $error): void
73102
{
74-
$this->collectedChatCalls[] = [
103+
$this->collectedCalls[] = [
75104
'method' => $method,
76105
'duration' => $duration,
77-
'input' => $input,
78-
'result' => $result,
79-
'error' => $error,
106+
'input' => $this->cloneVar($input),
107+
'result' => $this->cloneVar($result),
108+
'error' => $this->cloneVar($error),
80109
];
81110
}
82111

@@ -86,7 +115,12 @@ public static function getTemplate(): string
86115
}
87116

88117
/**
89-
* @return PlatformCallData[]
118+
* @return array{
119+
* model: Data,
120+
* input: Data,
121+
* options: Data,
122+
* result: Data
123+
* }[]
90124
*/
91125
public function getPlatformCalls(): array
92126
{
@@ -114,45 +148,12 @@ public function getToolCalls(): array
114148
*/
115149
public function getAgentCalls(): array
116150
{
117-
if (!isset($this->data['chat_calls'])) {
118-
return [];
119-
}
120-
121-
/** @var list<array{method: string, duration: float, input: mixed, result: mixed, error: ?\Throwable}> $chatCalls */
122-
$chatCalls = $this->data['chat_calls']->getValue(true);
123-
124-
return $chatCalls;
151+
return $this->data['chat_calls'] ?? [];
125152
}
126153

127154
public function reset(): void
128155
{
129156
$this->data = [];
130-
$this->collectedChatCalls = [];
131-
}
132-
133-
/**
134-
* @return array{
135-
* model: Model,
136-
* input: array<mixed>|string|object,
137-
* options: array<string, mixed>,
138-
* result: string|iterable<mixed>|object|null
139-
* }[]
140-
*/
141-
private function awaitCallResults(TraceablePlatform $platform): array
142-
{
143-
$calls = $platform->calls;
144-
foreach ($calls as $key => $call) {
145-
$result = $call['result']->await();
146-
147-
if (isset($platform->resultCache[$result])) {
148-
$call['result'] = $platform->resultCache[$result];
149-
} else {
150-
$call['result'] = $result->getContent();
151-
}
152-
153-
$calls[$key] = $call;
154-
}
155-
156-
return $calls;
157+
$this->collectedCalls = [];
157158
}
158159
}

src/ai-bundle/templates/data_collector.html.twig

Lines changed: 21 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,6 @@
5858
</span>
5959
{% endblock %}
6060

61-
{% macro tool_calls(toolCalls) %}
62-
Tool call{{ toolCalls|length > 1 ? 's' }}:
63-
<ol>
64-
{% for toolCall in toolCalls %}
65-
<li>
66-
<strong>{{ toolCall.name }}({{ toolCall.arguments|map((value, key) => "#{key}: #{value|json_encode}")|join(', ') }})</strong>
67-
<i>(ID: {{ toolCall.id }})</i>
68-
</li>
69-
{% endfor %}
70-
</ol>
71-
{% endmacro %}
72-
7361
{% block panel %}
7462
<h2>Symfony AI</h2>
7563

@@ -90,19 +78,9 @@
9078
<tr>
9179
<td><code>{{ call.method }}</code></td>
9280
<td class="text-nowrap">{{ (call.duration * 1000)|round(2) }} ms</td>
93-
<td>
94-
<div style="max-width: 400px; overflow-x: auto;"><pre>{{ dump(call.input) }}</pre></div>
95-
</td>
96-
<td>
97-
<div style="max-width: 400px; overflow-x: auto;"><pre>{{ dump(call.result) }}</pre></div>
98-
</td>
99-
<td>
100-
{% if call.error %}
101-
<pre>{{ dump(call.error) }}</pre>
102-
{% else %}
103-
<span class="text-muted">None</span>
104-
{% endif %}
105-
</td>
81+
<td>{{ profiler_dump(call.input) }}</td>
82+
<td>{{ profiler_dump(call.result) }}</td>
83+
<td>{{ profiler_dump(call.error) }}</td>
10684
</tr>
10785
{% endfor %}
10886
</tbody>
@@ -157,75 +135,19 @@
157135
<tbody>
158136
<tr>
159137
<th>Model</th>
160-
<td><strong>{{ constant('class', call.model) }}</strong> (Version: {{ call.model.name }})</td>
138+
<td>{{ profiler_dump(call.model) }}</td>
161139
</tr>
162140
<tr>
163141
<th>Input</th>
164-
<td>
165-
{% if call.input.messages is defined %}{# expect MessageBag #}
166-
<ol>
167-
{% for message in call.input.messages %}
168-
<li>
169-
<strong>{{ message.role.value|title }}:</strong>
170-
{% if 'assistant' == message.role.value and message.hasToolCalls%}
171-
{{ _self.tool_calls(message.toolCalls) }}
172-
{% elseif 'tool' == message.role.value %}
173-
<i>Result of tool call with ID {{ message.toolCall.id }}</i><br />
174-
{{ message.content|nl2br }}
175-
{% elseif 'user' == message.role.value %}
176-
{% for item in message.content %}
177-
{% if item.text is defined %}
178-
{{ item.text|nl2br }}
179-
{% else %}
180-
<img src="{{ item.url }}" />
181-
{% endif %}
182-
{% endfor %}
183-
{% else %}
184-
{{ message.content|nl2br }}
185-
{% endif %}
186-
</li>
187-
{% endfor %}
188-
</ol>
189-
{% else %}
190-
<pre>{{ dump(call.input) }}</pre>
191-
{% endif %}
192-
</td>
142+
<td>{{ profiler_dump(call.input) }}</td>
193143
</tr>
194144
<tr>
195145
<th>Options</th>
196-
<td>
197-
<ul>
198-
{% for key, value in call.options %}
199-
{% if key == 'tools' %}
200-
<li>{{ key }}:
201-
<ul>
202-
{% for tool in value %}
203-
<li>{{ tool.name }}</li>
204-
{% endfor %}
205-
</ul>
206-
</li>
207-
{% else %}
208-
<li>{{ key }}: <pre>{{ dump(value) }}</pre></li>
209-
{% endif %}
210-
{% endfor %}
211-
</ul>
212-
</td>
146+
<td>{{ profiler_dump(call.options) }}</td>
213147
</tr>
214148
<tr>
215149
<th>Result</th>
216-
<td>
217-
{% if call.input.messages is defined and call.result is iterable %}{# expect array of ToolCall #}
218-
{{ _self.tool_calls(call.result) }}
219-
{% elseif call.result is iterable %}{# expect array of Vectors #}
220-
<ol>
221-
{% for vector in call.result %}
222-
<li>Vector with <strong>{{ vector.dimensions }}</strong> dimensions</li>
223-
{% endfor %}
224-
</ol>
225-
{% else %}
226-
{{ call.result }}
227-
{% endif %}
228-
</td>
150+
<td>{{ profiler_dump(call.result) }}</td>
229151
</tr>
230152
</tbody>
231153
</table>
@@ -251,27 +173,14 @@
251173
</tr>
252174
</thead>
253175
<tbody>
254-
{% for tool in collector.tools %}
255-
<tr>
256-
<th>{{ tool.name }}</th>
257-
<td>{{ tool.description }}</td>
258-
<td>{{ tool.reference.class }}::{{ tool.reference.method }}</td>
259-
<td>
260-
{% if tool.parameters %}
261-
<ul>
262-
{% for name, parameter in tool.parameters.properties %}
263-
<li>
264-
<strong>{{ name }} ({{ parameter.type }})</strong><br />
265-
<i>{{ parameter.description|default() }}</i>
266-
</li>
267-
{% endfor %}
268-
</ul>
269-
{% else %}
270-
<i>none</i>
271-
{% endif %}
272-
</td>
273-
</tr>
274-
{% endfor %}
176+
{% for tool in collector.tools %}
177+
<tr>
178+
<td>{{ tool.name }}</td>
179+
<td>{{ tool.description }}</td>
180+
<td>{{ profiler_dump(tool.reference) }}</td>
181+
<td>{{ profiler_dump(tool.parameters) }}</td>
182+
</tr>
183+
{% endfor %}
275184
</tbody>
276185
</table>
277186
{% else %}
@@ -296,11 +205,15 @@
296205
</tr>
297206
<tr>
298207
<th>Arguments</th>
299-
<td><pre>{{ dump(call.call.arguments) }}</pre></td>
208+
<td><pre>{{ profiler_dump(call.call.arguments) }}</pre></td>
209+
</tr>
210+
<tr>
211+
<th>Call</th>
212+
<td>{{ profiler_dump(call.call) }}</td>
300213
</tr>
301214
<tr>
302215
<th>Result</th>
303-
<td><pre>{{ dump(call.result) }}</pre></td>
216+
<td>{{ profiler_dump(call.result) }}</td>
304217
</tr>
305218
</tbody>
306219
</table>

src/ai-bundle/tests/Profiler/DataCollectorTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function testCollectsDataForNonStreamingResponse()
4848
$dataCollector->lateCollect();
4949

5050
$this->assertCount(1, $dataCollector->getPlatformCalls());
51-
$this->assertSame('Assistant response', $dataCollector->getPlatformCalls()[0]['result']);
51+
$this->assertSame('Assistant response', $dataCollector->getPlatformCalls()[0]['result']->getValue(true));
5252
}
5353

5454
public function testCollectsDataForStreamingResponse()
@@ -72,6 +72,6 @@ public function testCollectsDataForStreamingResponse()
7272
$dataCollector->lateCollect();
7373

7474
$this->assertCount(1, $dataCollector->getPlatformCalls());
75-
$this->assertSame('Assistant response', $dataCollector->getPlatformCalls()[0]['result']);
75+
$this->assertSame('Assistant response', $dataCollector->getPlatformCalls()[0]['result']->getValue(true));
7676
}
7777
}

0 commit comments

Comments
 (0)