# Developer Feedback: Image Inputs to llm.complete May Not Be Reaching the Model, and the Documentation Is Less Explicit Than for Agent Session

**URL:** <https://forum.anna.partners/t/developer-feedback-image-inputs-to-llm-complete-may-not-be-reaching-the-model-and-the-documentation-is-less-explicit-than-for-agent-session/322>\
**Category:** Developers\
**Created:** [September 18, 2026, 2:20am UTC](https://forum.anna.partners/t/developer-feedback-image-inputs-to-llm-complete-may-not-be-reaching-the-model-and-the-documentation-is-less-explicit-than-for-agent-session/322 "2026-09-18T02:20:13Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![HappyLight](https://avatars.discourse-cdn.com/v4/letter/h/7feea3/32.png) [@HappyLight](https://forum.anna.partners/u/HappyLight)\
**Post date:** [September 18, 2026, 2:20am UTC](https://forum.anna.partners/t/developer-feedback-image-inputs-to-llm-complete-may-not-be-reaching-the-model-and-the-documentation-is-less-explicit-than-for-agent-session/322/1 "2026-09-18T02:20:13Z")

</div>

While testing image input support in an Anna App, I found that sending an image using the MCP image block structure described in the `llm.complete` documentation resulted in a successful response, but the model said it could not see the attachment. In the same test application, image inputs to Agent Session using uploaded image URLs have been verified to work correctly.

I would appreciate the team’s help in confirming the image input protocol for `llm.complete` and whether the current implementation matches the documentation.

## 1. Official Documentation and Areas That Need Clarification

Relevant documentation:

- [LLM Host API](https://staging.anna.partners/developers/reference/host-api-llm.md)
- [App-Side LLM & Agent API](https://staging.anna.partners/developers/apps/llm-and-agent.md)

### llm.complete

The LLM Host API documentation describes the request as MCP-shaped and states that `messages[].content` can be a string or an MCP-shaped content block:

> `content` is a string OR an MCP-shaped block (`{type:'text', text}` / `{type:'image', ...}`); blocks are flattened into a LangChain `HumanMessage` / `AIMessage` / `SystemMessage` by `_assemble_lc_messages`.

The documentation also states:

> Image content blocks require the selected model to be multimodal; otherwise the provider rejects upstream and the call surfaces as `APP_PROVIDER_ERROR`.

Based on this, I understand that `llm.complete` supports image inputs. However, the documentation uses `{type:'image', ...}` without specifying the image block’s fields and does not provide a complete image request example. This leaves the following details unclear:

- Should images use the MCP `data` and `mimeType` fields?
- Must `data` contain raw base64, or is a full data URI also accepted?
- Are image URLs supported? If so, what structure should be used?
- Can text and images be placed in separate, consecutive `user` messages?

Based on the documentation’s description of MCP, the current implementation uses `{ type: "image", data: "<base64>", mimeType: "image/png" }`. This follows the MCP image block specification, but the Anna documentation itself does not show this request in full.

### Agent Session

In comparison, the “Native image inputs (`attachments`)” section of the Agent Session documentation provides an explicit example:

```js
const stream = sess.run({
  content: "Check these slides for text overflow or layout issues.",
  attachments: [
    { type: "image/png", url: "https://example.com/slide-1.png" },
    {
      type: "image/png",
      url: "https://example.com/slide-2.png",
      detail: "high"
    },
    {
      type: "image/jpeg",
      data: "<base64 or data: URI>",
      filename: "photo.jpg"
    }
  ]
});

```

This documentation explicitly describes both URL and base64 inputs and lists attachment limits and usage considerations, giving developers enough information to implement and verify the feature directly.

## 2. The Actual llm.complete Request

The test application calls `llm.complete` through Anna Runtime. The image is read in the browser using `FileReader.readAsDataURL()`. After removing the data URI prefix, the raw base64 is placed in an MCP image block.

The request structure is shown below, with the image data omitted. The Chinese prompt is translated into English here:

```js
await runtime.call("llm", "complete", {
  messages: [
    {
      role: "user",
      content: {
        type: "text",
        text: "Can you see the contents of the image I attached?"
      }
    },
    {
      role: "user",
      content: {
        type: "image",
        data: "<full image base64 omitted here>",
        mimeType: "image/png"
      }
    }
  ]
});

```

This request does not use the Session `attachments` field or upload the image beforehand to obtain a URL. Instead, it sends the image as an MCP image block, based on the LLM documentation.

The RPC logs confirm that the request contained the full image data. Validation of the image data in the logs produced the following results:

| Item | Result |
| --- | --- |
| MIME type | `image/png` |
| Image dimensions | 1280 × 720 |
| Decoded size | 1,867,166 bytes |
| Base64 character count | 2,489,556 |
| Strict base64 decoding | Passed |
| PNG chunk checksum and compressed data checks | Passed |

Therefore, at least at the point where this RPC request was logged, the image data was complete and could be decoded correctly.

## 3. Actual Response: The Request Succeeded, but the Model Said It Could Not See the Image

The RPC returned `res ok llm.complete`, with no `APP_PROVIDER_ERROR` or parameter error.

The key response fields are shown below. Only the opening of `content.text` is included, translated from Chinese into English:

```json
{
  "role": "assistant",
  "content": {
    "type": "text",
    "text": "At the moment, I **cannot directly see** the image attachment you uploaded."
  },
  "model": "google/gemini-3-flash-preview",
  "stopReason": "endTurn",
  "usage": {
    "inputTokens": 9,
    "outputTokens": 151,
    "totalTokens": 160
  },
  "_meta": {
    "provider": "openrouter",
    "latencyMs": 2984,
    "quotaConsumed": 0.3948275862068965,
    "appSessionUuid": "aps_xjusbrpjg5biqhggfprkdm"
  }
}

```

The rest of the response suggested resending the image using the upload button in the chat interface and checking for delays or format issues. It did not describe the image’s contents.

Two aspects of this response stand out:

1. The model explicitly stated that it could not directly see the uploaded attachment.
2. `usage.inputTokens` was only 9, which appears inconsistent with a request containing the full image and suggests that only the text portion may have been processed.

The model’s natural-language response and token counts alone cannot establish where the image was lost. However, together with the complete outbound RPC request, the observed behavior suggests that **the image input was accepted but did not actually participate in model inference.**

## 4. Comparison Test: URL-Based Image Input Works in Agent Session

In the Session feature of the same test application, I used a different image input path:

1. Upload the image and obtain its URL.
2. Call `sess.run()`, passing the URL through `attachments`.
3. Check whether the model can recognize the image’s contents.

The corresponding request structure is shown below, with the Chinese prompt translated into English:

```js
const stream = sess.run({
  content: "Please describe the contents of this image.",
  attachments: [
    {
      type: "image/png",
      url: "<image URL obtained after upload>"
    }
  ]
});

```

**This Session path has been verified to work: the model can read and recognize the image’s contents correctly.**

The test results for the two paths are compared below:

| Item | llm.complete | Agent Session |
| --- | --- | --- |
| Image input location | `messages[].content` | `attachments[]` |
| Data format used in this test | Raw base64 in an MCP image block | Uploaded image URL |
| Complete image example in the documentation | No; only `{type:'image', ...}` is shown | Yes; examples for both URL and base64 inputs |
| Observed result | Request succeeded, but the model said it could not see the image | Image was recognized correctly |

This comparison shows that the Session URL-based image input path works. It does not establish that the two paths use the same internal message conversion and model routing, so the `llm.complete` implementation still needs to be investigated separately.

## 5. Expected Behavior

If the MCP image block structure above is supported:

- The image should be converted into multimodal input that the upstream model can process.
- The model should be able to answer based on the image.

If this structure is not yet supported, or if there are restrictions on input formats, model capabilities, or other factors:

- A clear parameter or capability error should be returned.
- The call should not return successfully as an ordinary text completion when the image has not participated in inference.

## 6. Questions for the Team

1. Does `llm.complete` currently support the following complete image block structure?

2. Is placing text and images in separate, consecutive `user` messages a supported usage pattern?

3. If the usage above is correct, could you inspect the server-side message conversion (`_assemble_lc_messages`, as mentioned in the documentation) and subsequent provider adaptation to confirm that the image content is passed to the upstream model in full?

4. Could you add a directly runnable image input example for `llm.complete`, similar to the Agent Session documentation, and clarify support and limitations for base64, data URIs, and URLs?

At this point, I suspect either an implementation issue in the `llm.complete` image input path or a discrepancy between the documented and actual support. I would like to confirm the correct protocol first, then determine whether the application-side implementation needs to be adjusted.

---

<div class="post-metadata">

**Author:** ![hunter](https://yyz1.discourse-cdn.com/flex033/user_avatar/forum.anna.partners/hunter/32/8_2.png) [@hunter](https://forum.anna.partners/u/hunter)\
**Post date:** [September 20, 2026, 9:13am UTC](https://forum.anna.partners/t/developer-feedback-image-inputs-to-llm-complete-may-not-be-reaching-the-model-and-the-documentation-is-less-explicit-than-for-agent-session/322/2 "2026-09-20T09:13:55Z")

</div>

Hi @HappyLight 👋

Thank you for this outstanding report — the RPC-level validation, the token-count observation, and the side-by-side comparison with Agent Session made this a joy to investigate. You were spot on. 🎯

## What we found 🔍

You uncovered a real bug. The `llm.complete` message assembler was **text-only** : MCP `{type:'image', ...}` blocks were silently dropped before the request ever reached the model. That’s exactly why you saw a “successful” response, `inputTokens: 9`, and a model that couldn’t see your (perfectly valid) PNG. The same issue affected `llm.stream` and the plugin-side `agent/complete`. The documentation was ahead of the implementation here — sorry about the confusion that caused. 🙇

## What’s fixed — host `1.1.0-beta.169` 🛠

Rolling out **this Friday** :

- 🖼 **Image blocks now work end-to-end** on `llm.complete`, `llm.stream`, and `agent/complete`. Supported shapes:
  - `{ type: "image", data: "<raw base64>", mimeType: "image/png" }` (exactly what you sent — no app-side changes needed!)
  - `data` as a full `data:` URI (`mimeType` optional)
  - `{ type: "image", url: "https://…" }` (public HTTPS)

- 💬 **Yes, your usage pattern is supported** : text and images may share one content array **or** sit in separate consecutive `user` messages — both work.
- 🚦 **No more silent failures** : if the resolved model isn’t vision-capable, the call now fails fast with `APP_MODEL_NOT_VISION_CAPABLE` (use `modelPreferences.hints` to pick a vision model). Malformed or unsupported blocks return `APP_INVALID_REQUEST`. Images are never quietly dropped again.
- 📏 Limits match Agent Session attachments: `image/jpeg|png|gif|webp|bmp|svg+xml`, ≤ 20 MB per image.
- 📚 **Docs updated** : the LLM Host API reference now documents the exact image block fields, limits, and errors, and the App-Side LLM & Agent guide has a new runnable image example — same level of detail as the Agent Session `attachments` section you referenced.
- 🧪 The official `anna-app-llm-demo` example has been updated with an image input so you can compare against a known-good request.

One note: the plugin-side `sampling/createMessage` remains **text-only** for now and rejects image blocks with a clear error — use `agent/complete` or session `attachments` on that surface.

Your original request body should work as-is once the update lands — we’d love to hear how it goes. Thanks again for the meticulous report; reports like this make the platform better for every developer. 💛
