cloudwego / cloudwego/eino-ext

bug(agenticopenai): synthetic assistant text is encoded as output message with empty id/status

Open Beginner friendly
#977 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
811
Forks
368
Avg merge
16h 22m
Merged PRs (30d)
13

Description

**Describe the bug**

`ResponsesModel` maps `schema.AgenticRoleTypeAssistant` + `AssistantGenText` to a Responses **output message** (`type=message` with `id` / `status` / `output_text`).

That is correct when replaying a model-generated item that still has item metadata.

When the assistant turn is **synthetic** (injected few-shot / language-switch / reconstructed history) there is no `ResponseMeta` and no item id/status. `assistantGenTextToInputItem` still emits an output message with **empty `id` and `status`**. BytePlus Ark (and likely OpenAI) then reject the request:

```
InvalidParameter: The parameter `input.status` specified in the request are not valid: unknown status: .
param=input.status
```

There is also no option to encode an assistant turn as EasyInput `{role: assistant, content: "..."}`. `userInputTextToInputItem` only runs for system/user. Putting `UserInputText` on an assistant-role message errors with `invalid content block type ... with assistant role`.

The only escape hatch is `WithExtraFields` overriding the whole `input` array after eino has already built the body.

**To Reproduce**

Minimal input (no previous response, no item id):

```go
package main

import (
"context"
"fmt"
"os"

"github.com/cloudwego/eino-ext/components/model/agenticopenai"
"github.com/cloudwego/eino/schema"
)

func main() {
ctx := context.Background()
am, err := agenticopenai.NewResponsesModel(ctx, &agenticopenai.ResponsesConfig{
BaseURL: os.Getenv("OPENAI_BASE_URL"), // e.g. Ark OpenAI-compatible /v1
APIKey: os.Getenv("OPENAI_API_KEY"),
Model: os.Getenv("OPENAI_MODEL"),
})
if err != nil {
panic(err)
}

// Synthetic assistant: not produced by a previous Responses call.
fakeAsst := &schema.AgenticMessage{
Role: schema.AgenticRoleTypeAssistant,
ContentBlocks: []*schema.ContentBlock{
schema.NewContentBlock(&schema.AssistantGenText{Text: "OK."}),
},
}

_, err = am.Generate(ctx, []*schema.AgenticMessage{
schema.SystemAgenticMessage("You are a test bot."),
schema.UserAgenticMessage("From now on reply in Korean."),
fakeAsst,
schema.UserAgenticMessage("say hi"),
})
fmt.Println(err)
}
```

Observed request `input` (from `assistantGenTextToInputItem`):

```json
[
{"role": "system", "content": "You are a test bot."},
{"role": "user", "content": "From now on reply in Korean."},
{
"type": "message",
"id": "",
"status": "",
"role": "assistant",
"content": [{"type": "output_text", "text": "OK."}]
},
{"role": "user", "content": "say hi"}
]
```

Ark returns HTTP 400 (`input.status` unknown / missing).

The same payload works if the assistant item is EasyInput:

```json
{"role": "assistant", "content": "OK."}
```

(verified against Ark `POST /api/v3/responses`.)

**Expected behavior**

If an assistant `AssistantGenText` block has **no item id / status**:

1. Encode it as EasyInput `{role: assistant, content}` (same as system/user), **or**
2. Fail fast at encode time instead of sending an invalid output message.

Optionally expose an explicit way to request EasyInput for assistant (the Responses API allows this; it does not require assistant items to be prior model output).

**Version:**

- `github.com/cloudwego/eino-ext/components/model/agenticopenai v0.2.2`
- `github.com/cloudwego/eino v0.9.5`

**Environment:**

```
GOOS=darwin
GOARCH=arm64
GOVERSION=go1.25.6
```

Upstream used for the 400: BytePlus Ark Responses (`/api/v3/responses`). Encoding path is local to `responses_convertor.go` (`toAssistantRoleInputItems` → `assistantGenTextToInputItem`).

**Additional context**

Related code (v0.2.2):

- `toAssistantRoleInputItems` always uses `assistantGenTextToInputItem` → `OfOutputMessage`
- `userInputTextToInputItem` uses `EasyInputMessageParam` but only for system/user
- `isSelfGeneratedMessage` is `ResponseMeta.OpenAIExtension != nil`; synthetic messages are not self-generated, but `AssistantGenText` is still on the whitelist and still becomes an output message

CONTRIBUTING asked for a reduced repro; the snippet above is enough to hit the encoder. Happy to send a PR that falls back to EasyInput when id/status are empty.

Contributor guide

Open the contributing guide

Research direction

Start in responses_convertor.go, following toAssistantRoleInputItems into assistantGenTextToInputItem and comparing it with userInputTextToInputItem. Reproduce the synthetic assistant case from the issue, then ensure assistant text without item metadata is encoded as a valid EasyInput or rejected before the request is sent; verify the resulting request no longer contains empty id or status fields.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.