Skip to content

Add lingua support for streaming tool calls#81

Open
knjiang wants to merge 1 commit intomainfrom
ken/support-anthropic-streaming-tool-calls
Open

Add lingua support for streaming tool calls#81
knjiang wants to merge 1 commit intomainfrom
ken/support-anthropic-streaming-tool-calls

Conversation

@knjiang
Copy link
Contributor

@knjiang knjiang commented Feb 5, 2026

This PR adds support for responses and anthropic streaming tool calls.

"target": "*",
"fields": [
{ "pattern": "choices[*].delta.refusal", "reason": "ChatCompletions refusal field has no equivalent in other providers" },
{ "pattern": "choices[*].delta.tool_calls", "reason": "Streaming tool_calls transformation not yet implemented" }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is the big one to remove choices[*].delta.tool_calls and what was blocking loop anthropic from working.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the way the testing works here is the following:
we have coverage-report binary that produces an HTML matrix report -> https://github.com/braintrustdata/lingua/actions/runs/21727047150?pr=81

^ the HTML report is helpful for when u want the AI to crank at supporting smth.

in addition we run https://github.com/braintrustdata/lingua/blob/main/crates/coverage-report/tests/cross_provider_test.rs

which runs cargo test on the coverage-report matrix and basically it'll fail if we see an "unexpected diff" where it's not listed in crates/coverage-report/src/streaming_expected_differences.json.

curerntly we don't have full support for all providers so we are flagging them in one by one, the "strict" mode tests only run for:

const REQUIRED_PROVIDERS: &[ProviderFormat] = &[
    ProviderFormat::Responses,
    ProviderFormat::OpenAI, // ChatCompletions
    ProviderFormat::Anthropic,
];

@knjiang knjiang requested a review from clutchski February 5, 2026 20:52
Copy link
Contributor Author

knjiang commented Feb 6, 2026

@knjiang knjiang requested a review from remh February 6, 2026 02:33
@knjiang knjiang changed the title Support anthropic streaming tool calls Add lingua support for streaming tool calls Feb 6, 2026
Copy link
Contributor

remh commented Feb 6, 2026

looks like there are some code duplication between adapter.rs and responses_adapter.rs, should we add some helpers ?

Copy link
Contributor

remh commented Feb 6, 2026

should we add a few more unit tests ?

if let Some(delta) = &choice.delta {
// Check for tool_calls in the delta
if let Some(tool_calls) = delta.get("tool_calls").and_then(Value::as_array) {
if let Some(tc) = tool_calls.first() {
Copy link
Contributor

Choose a reason for hiding this comment

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

what if there are more than one tool call ?

if let Some(delta) = &choice.delta {
// Check for tool_calls in the delta
if let Some(tool_calls) = delta.get("tool_calls").and_then(Value::as_array) {
if let Some(tc) = tool_calls.first() {
Copy link
Contributor

Choose a reason for hiding this comment

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

same, what if there are more than one tool call ?

// Role-only delta or null content without tool_calls - return empty text_delta
let content_is_missing_or_null =
delta.get("content").is_none() || delta.get("content") == Some(&Value::Null);
let has_tool_calls = delta.get("tool_calls").is_some();
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't we check that tool calls are not empty ?

let is_initial_metadata =
(chunk.model.is_some() || chunk.id.is_some() || chunk.usage.is_some())
&& !has_finish
&& !has_tool_calls
Copy link
Contributor

Choose a reason for hiding this comment

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

When the first chunk from ChatCompletions carries both metadata and tool_calls, this skips message_start entirely and id/model would be lost, is that expected?

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