vllm-project/vllm · critical
tool response messages require a tool_call_id; use ChatMessa
Error message
tool response messages require a tool_call_id; use ChatMessage::tool_response() instead
What it means
This is a `panic!`, not a Result error: `ChatMessage::text()` (request.rs:228) refuses to build a ToolResponse message because tool responses require a `tool_call_id` linking them to the assistant's tool call. The API deliberately has no sensible default, so misuse fails loudly. Use `ChatMessage::tool_response(...)` which takes the id.
Source
Thrown at rust/src/chat/src/request.rs:228
/// Tool response content associated with one prior assistant tool call.
ToolResponse {
content: ChatContent,
tool_call_id: String,
},
}
impl ChatMessage {
/// Construct one chat message with plain string content.
pub fn text(role: ChatRole, text: impl Into<String>) -> Self {
let content: String = text.into();
match role {
ChatRole::System => Self::system(content),
ChatRole::Developer => Self::developer(content, None),
ChatRole::User => Self::user(content),
ChatRole::Assistant => Self::assistant_text(content),
ChatRole::ToolResponse => {
panic!(
"tool response messages require a tool_call_id; \
use ChatMessage::tool_response() instead"
)
}
}
}
/// Construct one chat message with system role.
pub fn system(content: impl Into<ChatContent>) -> Self {
Self::System {
content: content.into(),
}
}
/// Construct one chat message with developer role.
pub fn developer(content: impl Into<ChatContent>, tools: Option<Vec<ChatTool>>) -> Self {
Self::Developer {
content: content.into(),View on GitHub (pinned to c794754062)
Solutions
- Replace the call with `ChatMessage::tool_response(tool_call_id, content)`.
- Handle `ChatRole::ToolResponse` as a distinct arm in any role-matching code instead of funneling it through `text()`.
- If replaying a history, look up the tool_call_id from the preceding assistant message's tool_calls.
Example fix
// before let msg = ChatMessage::text(ChatRole::ToolResponse, "42"); // panics // after let msg = ChatMessage::tool_response(tool_call_id.clone(), "42");
Defensive patterns
Strategy: type-guard
Validate before calling
for msg in &incoming_messages {
if msg.role == ChatRole::ToolResponse {
let id = resolve_tool_call_id(&assistant_msg, msg)?; // must exist
built.push(ChatMessage::tool_response(id, msg.content_text()));
}
} Type guard
fn needs_tool_call_id(role: ChatRole) -> bool {
matches!(role, ChatRole::ToolResponse)
} Try / catch
std::panic::set_hook is not a fix — this is a panic by design. Match on the role before constructing:
let msg = match role {
ChatRole::ToolResponse => ChatMessage::tool_response(tool_call_id, content),
other => ChatMessage::text(other, content),
}; Prevention
- Never route ChatRole::ToolResponse through ChatMessage::text(); handle it as its own match arm.
- Carry the tool_call_id alongside the role when replaying conversation histories.
- Add a unit test that builds every supported role to catch misuse at CI time.
When it happens
Trigger: Calling `ChatMessage::text(ChatRole::ToolResponse, "result body")` — e.g. generic code that maps roles in a loop and routes ToolResponse through the text constructor.
Common situations: Deserializing/echoing conversation histories role-by-role with a match on role; porting client code where ToolResponse was treated as a plain role; replaying OpenAI-format histories.
Related errors
- ZMQ runtime task failed
- request `{request_id}` is already in flight
- engine-core client is closed: {message}
- failed to build vLLM ZMQ runtime
- generate request `{request_id}` has an empty prompt_token_id
AI-assisted analysis of vllm-project/vllm@c794754062 (2026-08-14).
Data as JSON: /api/errors/7cd7713c9087023f.
Report an issue: GitHub.