cloudwego / cloudwego/eino-ext
bug(agenticopenai): synthetic assistant text is encoded as output message with empty id/status
- 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
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