[Platform] Add support for Eden AI gateway - #2417
Conversation
18398f3 to
d329111
Compare
Eden AI is an AI gateway exposing hundreds of models from many providers behind a
single API, using `provider/model` identifiers.
The bridge covers both halves of the v3 API:
* the OpenAI-compatible endpoints, `/v3/chat/completions` and `/v3/embeddings`,
reusing the Generic bridge for chat (including streaming and tool calling) and
embeddings;
* the expert models of the `/v3/universal-ai` endpoints: OCR, document parsing
(invoices, resumes, identity documents), text-to-speech, speech-to-text, image
analysis (object detection, explicit content, logo detection, face detection,
AI detection, deepfake detection) and image generation.
Expert models take their input either as a direct file URL, as a file ID, or as
`Audio`, `Document` and `Image` content, which is transparently uploaded through
`/v3/upload` first. Speech-to-text runs on `/v3/universal-ai/async`, whose job is
polled until it reaches a terminal state, unless a `webhook_receiver` is given.
Options that are not root-level request fields are forwarded inside the `input`
object.
Two catalogs are provided. `ModelCatalog` curates a static subset and accepts extra
entries through `$additionalModels`. `ModelApiCatalog` discovers everything the
gateway currently serves - 1083 models against the 65 curated ones - from the public
`/v3/models`, `/v3/embeddings/models` and `/v3/info` endpoints; expert subfeatures
the bridge has no converter for stay hidden, so an unsupported model fails at lookup
instead of at conversion time. Capabilities are derived from the metadata
`/v3/models` exposes per entry: `supports_response_schema` maps to
`OUTPUT_STRUCTURED`, `supports_function_calling` to `TOOL_CALLING`, and the
`input_modalities` to the matching `INPUT_*` cases.
Errors get a dedicated `ErrorHandlingTrait` because Eden AI is a FastAPI application
and reports failures through `detail`, in shapes the shared
`Result\HttpStatusErrorHandlingTrait` cannot read - so an unmapped status used to
surface as a misleading "Response does not contain ..." message. All the shapes below
were captured from the live API, including a 422 body that differs from what the
published OpenAPI schema documents, and a 403 that nothing mapped at all:
403 {"detail": "Not authenticated"}
401 {"detail": "Invalid token"}
404 {"detail": {"error": "Provider not found", "message": "..."}}
400 {"detail": {"error": "Invalid provider format", "message": "..."}}
422 {"detail": "Validation error", "errors": [{"field": "language", ...}]}
A missing required option now reports `Validation error: language: Field required`
rather than `Response does not contain audio_resource_url.`.
Image generation returns every generated image instead of only the first: since
`num_images` accepts up to 10 and each one is billed, several are exposed as a
`MultiPartResult`, mirroring the OpenAI image bridge. The gateway `cost` and
`provider` - which vary per request once `fallbacks` is used - are exposed as result
metadata on the binary and text results too, not only on the object ones. The
text-to-speech download, the asynchronous job decoding and the file upload all map
their own failures onto platform exceptions rather than leaking HttpClient ones, and
an unusable binary input is rejected before it can be uploaded empty and billed.
The synthesized audio is reported with its real format: the CDN serving it answers
`binary/octet-stream` whatever `audio_format` was requested, so the extension Eden AI
puts in the resource URL decides, which was verified against the audio magic bytes for
mp3 and wav.
Endpoints, payload keys, the upload contract and every output shape were checked
against the live API, and the thirteen examples were all run against it. Note that
the `output_schema` that `GET /v3/info` advertises for `image/generation` describes
a single item without the enclosing `items` list the endpoint really returns, which
is documented in the converter so the discrepancy does not read as a bug.
d329111 to
1ab4bb1
Compare
wachterjohannes
left a comment
There was a problem hiding this comment.
Structurally this holds up: scaffolding, deptrac, splitsh and the bundle wiring all match the sibling bridges, and the two things that looked like omissions (no base_url in the bundle config, two catalogs) are what the siblings do. Details inline.
- Record and replay is the direction we want to take bridges, so that a converter is pinned against the provider's real shapes rather than hand-written fixtures. This bridge is the strongest case for it so far, eight converters at once, and you already ran every example against the live API, so
examples/runner --record edenaiwould come almost for free.ExamplesReplayTestiterates over the cassettes, so without one the bridge stays silently uncovered.
On scope I am deliberately not deciding: a gateway with eight expert-feature families and its own result types is a maintenance call, so I would like @chr-hertel to weigh in before this moves.
Verified locally, all green.
| for ($poll = 0; $this->isPending($data); ++$poll) { | ||
| if ($poll >= $this->maxPolls) { |
There was a problem hiding this comment.
decode() forces the response, so the whole poll loop runs inside Platform::invoke(), not when the result is read. Measured with a counting clock:
invoke() returned after 3 sleeps, 4 http calls
reading the result caused 0 further sleeps
With the defaults that is up to 120 × 1s inside invoke(), and the returned DeferredResult is not deferred for this model type.
#2408 is open and adds exactly this as a shared concern (Job\JobClientInterface, JobRunner, JobTimeoutException), with resumability this loop gives up. Not to be solved here, but worth ordering: adopting it once #2408 lands would drop the loop entirely.
| $error = json_decode($response->getContent(false), true); | ||
| $errorMessage = $error['error']['message'] ?? $error['detail'] ?? 'Authentication failed.'; |
There was a problem hiding this comment.
The change and its test are right, but I could not reproduce the reason given in the description ("PHP warnings instead of throwing an AuthenticationException"). With a detail-only body, error as a string, a list, null, invalid JSON or an empty body, the old expression warns in none of them, ?? suppresses the offset notices, and it threw AuthenticationException every time, just with the generic message.
So this improves the message rather than fixing a warning. Worth rewording, since the description lands verbatim in the merge commit.
| "symfony/ai-deepgram-platform": "^0.12", | ||
| "symfony/ai-docker-model-runner-platform": "^0.12", | ||
| "symfony/ai-doctrine-message-store": "^0.12", | ||
| "symfony/ai-eden-ai-platform": "^0.12", |
There was a problem hiding this comment.
We'd need dev-main plus a path repo here for making it work
| "symfony/ai-eden-ai-platform": "^0.12", | |
| "symfony/ai-eden-ai-platform": "dev-main", |
| The result exposes every ``Page`` with its markdown, dimensions, extracted layout images | ||
| (with bounding boxes) and optional annotations. | ||
|
|
||
| Eden AI exposes OCR and document parsing (invoices, resumes, identity documents) from multiple |
There was a problem hiding this comment.
please move the changes from here and below to a provider specific rst file like docs/components/platform/edenai.rst

Eden AI is an AI gateway exposing hundreds of models from many
providers behind a single API, using
provider/modelidentifiers. This adds asymfony/ai-eden-ai-platformbridge covering both halves of its v3 API.OpenAI-compatible endpoints
Chat completions (
/v3/chat/completions, including streaming and tool calling) andembeddings (
/v3/embeddings) reuse the Generic bridge:Expert models
The
/v3/universal-aiendpoints expose non-LLM features, addressed asfeature/subfeature/provider[/model]: OCR, document parsing (invoices, resumes, identitydocuments), text-to-speech, speech-to-text, image analysis (object detection, explicit
content) and image generation.
Expert models accept their input as a direct file URL, as a file ID, or as
Audio,DocumentandImagecontent, which is transparently uploaded through/v3/uploadbeforehand. Speech-to-text runs on
/v3/universal-ai/async, whose job is polled until itreaches a terminal state. Options that are not root-level request fields (
fallbacks,provider_params,show_original_response,webhook_receiver,user_webhook_parameters) are forwarded inside theinputobject, so per-featureparameters like
language,document_typeorvoiceare passed as invocation options.Notes
The catalog ships a curated subset of the available models; any other one can be
registered through the
$additionalModelsconstructor argument. Capabilities are derivedfrom the metadata
/v3/modelsexposes per entry:supports_response_schemamaps toOUTPUT_STRUCTURED,supports_function_callingtoTOOL_CALLING, and theinput_modalitiesto the matchingINPUT_*cases.Gateways like Eden AI answer authentication failures with
{"detail": "..."}rather thanan OpenAI-style error payload, which made the Generic completions converter emit PHP
warnings instead of throwing an
AuthenticationException, so it now reads both shapes.Endpoints, payload keys, the upload contract and every output shape were checked against
the live API, and the twelve examples under
examples/edenai/were all run against it.One discrepancy is worth flagging for future readers: the
output_schemaadvertised byGET /v3/infoforimage/generationdescribes a single item without the enclosingitemslist the endpoint really returns, which the converter documents.Bridge documentation is in
docs/components/platform.rst, and 115 tests cover the modelcatalog, the contract normalizers, both model clients and every result converter.
cc @Guikingone