Make non-Anthropic providers work end-to-end (logging, tool format, message bag)#8
Open
dkd-dobberkau wants to merge 3 commits intob13:mainfrom
Open
Make non-Anthropic providers work end-to-end (logging, tool format, message bag)#8dkd-dobberkau wants to merge 3 commits intob13:mainfrom
dkd-dobberkau wants to merge 3 commits intob13:mainfrom
Conversation
…equests
Both Ai::conversationStream() and AgentDispatcher-style direct
processToolCallingRequest() callers bypass the synchronous middleware
pipeline, so RequestLoggingMiddleware never sees their requests and
tx_aim_request_log stays empty.
This adds inline logging in SymfonyAiPlatformAdapter:
* processConversationRequest(stream=true): wires StreamChunkIterator's
onComplete callback to log after the stream finishes.
* processToolCallingRequest: logs after each invocation, both on
success and on error (so each agent loop round produces one row).
Logging routes through RequestLogRepository directly. Failures are
swallowed to never break the response path.
Closes b13#7
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
processToolCallingRequest currently sends OpenAI's $tool->toArray()
format to every provider. This is wrong for Anthropic, which expects
{name, description, input_schema} instead of OpenAI's {type: function,
function: {...}}.
Switch on $request->configuration->providerIdentifier and emit the
correct shape for each provider family.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
buildMessageBag dropped two pieces of information when converting AiM
messages into Symfony AI MessageBag:
* AssistantMessage->toolCalls were thrown away. With OpenAI-style
providers (Mistral, OpenAI), this turned the next round's history
into an assistant message with neither content nor tool_calls,
which Mistral rejects with HTTP 400 "Assistant message must have
either content or tool_calls".
* ToolMessage (role=tool) silently fell through to Message::ofUser,
so tool results were sent back as user messages without a
tool_call_id and providers couldn't match them to the original
call.
Detect both message subclasses up front and emit Message::ofAssistant
with tool calls / Message::ofToolCall with tool_call_id respectively.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes three closely related gaps that together prevent non-Anthropic providers (Mistral, OpenAI, Gemini, Ollama) from working end-to-end through
SymfonyAiPlatformAdapter, and also fills the request-log gap for streaming/tool-calling regardless of provider.The three commits are independent fixes but share the same call path (
processToolCallingRequestandprocessConversationRequest) and surface together once you actually try to switch from Anthropic to e.g. Mistral with the existing demo.1. Persist request log entries for streaming and tool-calling
Closes #7
Ai::conversationStream()and direct callers ofprocessToolCallingRequest()(e.g. extension-side agent dispatchers usinggetCapability(...)) bypass the synchronous middleware pipeline.RequestLoggingMiddlewarenever sees those requests, sotx_aim_request_logstays empty and the dashboard widgets show "No requests logged".This wires inline logging in
SymfonyAiPlatformAdapter:processConversationRequest(stream=true): passes anonCompletecallback toStreamChunkIterator(the parameter was already declared but never used). Logs after the stream finishes with the accumulated content and final usage statistics.processToolCallingRequest: logs after each invocation, both on success and on error. Agent loops that run multiple rounds produce one row per round.RequestLogRepositoryis resolved viaGeneralUtility::makeInstance(...)with an explicitConnectionPoolconstructor argument because the repository is registered as private inConfiguration/Services.yamland isn't reachable through the container at runtime from outside DI. Logging failures are swallowed so they never break the response path.2. Pick tool definition format based on providerIdentifier
processToolCallingRequestcurrently calls$tool->toArray(), which produces OpenAI's{type: function, function: {name, description, parameters}}. Anthropic, however, expects{name, description, input_schema}. With the current code, agent loops with Anthropic silently ignore the tool definitions because the schema is wrong; with non-Anthropic providers this would work, but in combination with #3 the loop still breaks.Switch on
$request->configuration->providerIdentifier:'anthropic'→ emit Anthropic's tool schema3. Forward assistant tool_calls and tool messages into MessageBag
buildMessageBagdropped two pieces of information when converting AiM messages into Symfony AI'sMessageBag:AssistantMessage->toolCallswere thrown away. With OpenAI-style providers (Mistral, OpenAI), the next round's history then contained an assistant message with neither content nor tool_calls. Mistral rejects this with HTTP 400 "Assistant message must have either content or tool_calls".ToolMessage(role=tool) silently fell through toMessage::ofUser, so tool results were sent back as user messages without atool_call_idand providers couldn't match them to the original call.Detect both subclasses up front and emit
Message::ofAssistant($content, $toolCalls)/Message::ofToolCall(new ToolCall(...), $content)respectively. This relies on Symfony AI'sMessage::ofAssistant(?string, ?array $toolCalls)andMessage::ofToolCall(ToolCall, string)factories.Test plan
symfony/ai-mistral-platform).ConversationRequestrow appears intx_aim_request_logwith non-zero token counts and duration.ToolCallingRequestrow per round, and thetoolspayload uses Anthropic'sinput_schemashape.{type: function, function: ...}shape, and the assistant message in round 2 carries tool_calls in the wire format.provider_identifier.Verified locally with
b13/aim 0.1.0, TYPO3 13.4.28, PHP 8.3, againstclaude-sonnet-4-5-20250929(Anthropic) andmistral-medium-latest(Mistral).🤖 Generated with Claude Code