Skip to content

feat: pluggable image-to-text service with OpenAI vision backend#1038

Merged
cybermaggedon merged 1 commit into
trustgraph-ai:release/v2.7from
sunnyadn:feat/image-to-text
Jul 12, 2026
Merged

feat: pluggable image-to-text service with OpenAI vision backend#1038
cybermaggedon merged 1 commit into
trustgraph-ai:release/v2.7from
sunnyadn:feat/image-to-text

Conversation

@sunnyadn

@sunnyadn sunnyadn commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Closes #879

Implements the full vertical from the issue spec: schema, ImageToTextService base class, OpenAI vision backend, gateway endpoint, client API, and tg-describe-image CLI. Targets release/v2.7.

What's included

  • Schema (schema/services/image_to_text.py): ImageToTextRequest {image, mime_type, prompt, system} / ImageToTextResponse {error, description, in_token, out_token, model}, dataclass style matching llm.py. The image field carries base64 ASCII as str: the pub/sub wire is JSON on all three backends, so raw binary can't ride a bytes field, and base64 is what the vision API consumes anyway. No queue-name constants: flow-scoped services get queues from blueprint interfaces at runtime, same as text-completion.
  • Base class (base/image_to_text_service.py): mirrors LlmService (ConsumerSpec/ProducerSpec, ParameterSpec("model"), duration/model metrics, TooManyRequests re-raise, structured image-to-text-error fallback). Subclasses implement describe_image(image, mime_type, prompt, system, model) -> ImageDescriptionResult. No streaming machinery: the issue lists streaming as future work.
  • OpenAI backend (model/image_to_text/openai/): data-URI content block, prompt defaults to "Describe this image", rate-limit mapping copied from the text-completion openai backend. Default model is gpt-5-mini (vision-capable, cheapest current tier) instead of the issue's gpt-4o example; overridable via -m, config, or the per-flow model parameter. No temperature parameter: the gpt-5 family rejects non-default temperature, so the backend never sends one.
  • Gateway: translators, ImageToTextRequestor, dispatcher-table entry. No new route file: the generic /service/{kind} route covers it. Registered as its own IAM capability image-to-text (reranker precedent), so text-only roles don't silently gain image calls.
  • Client API: image_to_text() on all four surfaces (sync/async REST + sync/async websocket) plus ImageToTextResult in api/types.py. Clients accept raw bytes and do the base64 encoding. One deliberate divergence from the text_completion siblings: the websocket methods check the response body for an error dict and raise. The mux delivers ServiceRequestor errors inside the response field, so without this check a backend failure would come back as an empty description instead of an exception.
  • CLI tg-describe-image (name from the issue): --image photo.jpg works as written; mime type is guessed from the filename, overridable with --mime-type.
  • Specs: OpenAPI path + component schemas, AsyncAPI/websocket registration. Field names follow the actual wire (mime_type, in_token): there is no key-conversion layer in the gateway, so the kebab-case in the sibling text-completion spec doesn't match what its translator actually emits. Can align with the sibling instead if you prefer spec-internal consistency.
  • Blueprint: zero code changes; the gateway is interface-driven and errors cleanly when a flow's blueprint lacks the interface. Docs example added. Default-blueprint wiring lands in the templates repo as a follow-up PR, like the reranker and keyword-index components.

Testing

  • 28 new unit tests + 6 contract tests. Full suites vs clean release/v2.7 baseline: unit 3152 passed (baseline 3124 plus exactly the new tests), contract 125 (119 + 6), integration 247 with an identical failure set. Redocly and AsyncAPI CLIs both validate the specs.
  • Live E2E on a 2.7 stack (compose infra, gateway/iam/image-to-text from source): a generated bar-chart PNG through REST and the CLI against a real vision endpoint returns descriptions identifying the bars and their relative heights, with token counts populated. Killing the processor: REST and CLI both return gateway-error: Timeout at the 30s gateway timeout, no hang. A flow without the interface returns This kind not supported by flow immediately.

Out of scope

Per the issue's future-work list: additional backends (claude/vertexai/local), ingestion-pipeline integration, streaming, image embeddings. Also deliberately untouched: rev_gateway and MCP exposure (reranker precedent), metering wiring (blueprint-level choice).

@github-actions

Copy link
Copy Markdown

Contributor License Agreement ✅

All contributors have signed the CLA. Thank you!

@cybermaggedon cybermaggedon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤯 exemplary work 🤯! Uses all the existing code-base patterns, and extends to add patterns for a new processing class

@cybermaggedon
cybermaggedon merged commit 40f01c1 into trustgraph-ai:release/v2.7 Jul 12, 2026
3 checks passed
@cybermaggedon

Copy link
Copy Markdown
Contributor

Released in v2.7.5

@cybermaggedon

Copy link
Copy Markdown
Contributor

Super piece of work, thanks so much @sunnyadn

cybermaggedon pushed a commit to trustgraph-ai/trustgraph-templates that referenced this pull request Jul 12, 2026
Templates-side wiring for the image-to-text service added in
trustgraph-ai/trustgraph#1038. Mirrors the keyword-index component
surface (#297): optional component deploying the processor from the
standard flow image, plus inert flow-blueprint wiring imported by the
document-store flow module.
@cybermaggedon

Copy link
Copy Markdown
Contributor

Confirmed working end-to-end. I used Gemma4 26B A4B hosted in llama.cpp...

(env) [try]$ time tg-describe-image -i diagsys.png  -p 'Select 5 words from the image'
Here are 5 words selected from the image:

1. Intake
2. Exhaust
3. Ignition
4. Vehicle
5. Controls

real	0m1.533s
user	0m0.172s
sys	0m0.030s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants