Skip to content

fix: gemini models via databricks#8042

Open
alexhancock wants to merge 4 commits intomainfrom
alexhancock/gemini-databricks-fix
Open

fix: gemini models via databricks#8042
alexhancock wants to merge 4 commits intomainfrom
alexhancock/gemini-databricks-fix

Conversation

@alexhancock
Copy link
Collaborator

Fixes two major issues blocking usage of gemini models via databricks

  1. A hard error that stream_options isn't supported for gemini models via databricks
  2. Deserialization errors when content containing an array of objects with text and thoughtSignature comes back

Before

🐚 11:32:46 ~/Development/goose main $ GOOSE_PROVIDER=databricks GOOSE_MODEL=databricks-gemini-2-5-pro cargo run --bin goose -- run -t "hello"
   Compiling goose v1.28.0 (/Users/alexhancock/Development/goose/crates/goose)
   Compiling goose-acp v1.28.0 (/Users/alexhancock/Development/goose/crates/goose-acp)
   Compiling goose-cli v1.28.0 (/Users/alexhancock/Development/goose/crates/goose-cli)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 30.52s
     Running `target/debug/goose run -t hello`

    __( O)>  ● new session · databricks databricks-gemini-2-5-pro
   \____)    20260320_22 · /Users/alexhancock/Development/goose
     L L     goose is ready
Ran into this error: Request failed: Bad request (400): {
  "error": {
    "code": 400,
    "message": "Invalid JSON payload received. Unknown name \"stream_options\" at 'generation_config': Cannot find field.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.BadRequest",
        "fieldViolations": [
          {
            "field": "generation_config",
            "description": "Invalid JSON payload received. Unknown name \"stream_options\" at 'generation_config': Cannot find field."
          }
        ]
      }
    ]
  }
}
.

Please retry if you think this is a transient or recoverable error.

and once that is fixed:

Ran into this error: Request failed: Stream decode error: Failed to parse streaming chunk: invalid type: sequence, expected a string at line 1 column 76: Some("{"model":"gemini-2.5-pro","choices":[{"delta":{"role":"assistant","content":[{"type":"text","text":"This README describes goose, a local, extensible, open-source AI agent designed to automate engineering tasks.\n\nKey Features:\n\n* **Autonomous","thoughtSignature":"CiIBjz1rX9XvFDl+iquAYN9PUzHE7bYXnv+P/cKniGXbCZcrCmYBjz1rX0xwhxjEAlpjWVe1jgajNY3P4dtVaddnSWsG0/kDn5c7jkINWH7R+qRTEMMQxfrABK9GTRgZUuKCX7gvLBL2kJ7ZYo8nD7tIIDp72/djW4f4rLcNKyRNfSh+pzwj7eQMdpUKYAGPPWtf8xOhOD5dDMKc+Ruuzg0cYvf1GQ273JyxE+f0IIOkic+l0txwh6EhhqjL5dLWPQ3SaKh/jdeLn+nhzLgH4cNNX+KHRBpmDTUT9FlpOJeMZhKBG9hG1e1MkTZG4wrIAQGPPWtfUtbJg3Tn22p1JcOvsoTixZxsrYzMyGXwnpTni7ZogC9Db0wD2ibYUOfUTjrSmdRSXGtkKhRVT4A9zms/flZ8tWlTu/wSGBkg3WTuwWCtWFwYLBjTBcnaFI7pvAg5WxeKfF0YoS7HsTo4/4nxlDXEHVHx3lnbm/tSYFQjGyDPDOxbCrWJNIRJCnc3WZdMwXAR/Bpzdu6WkRRUca5lh630Vne7gc7riFygU+mtldT7Y3o9AWdaLkhsl5IC6kIIod6RQi1ICsABAY89a18AiO0dQ2HJR626f6O4F3dXfNEN3U720+lqz0n67IsXvWT9s8Jl4pIxMWveEollFeUhb17mc/u7Nv5Q9AKrFz1M9j3iMMeho/y+TkuJTiwKkTpKGE5EaPX3JNMFQEx0MHgHRAoPpXhqVMO5BCdYBuB6pibfuVI1RJGBa3h56zzmzo+f4+0AMqI9xpNcXyxue3JcT9uI8ACH/N4clCoo7A44oUfkLR50u/vuX8gmVh7yk1srCZtlyc5x54S4CsoCAY89a1+z1RNnMXtBTnzCz8z2vVRQH6o10xGMxzk/FGozmHk6QCNmMExvpAoYmdg22x1BhGtnpnKEXGLu39RQ56SUKBuFKynALqmWHj0eDvWZw5Cu9A+SH7romVp9sDWJ0fV4kUHcxLCH/C8lLeeOst3ohB19c9gpIUTlyH97BWKZenSKe9P3tU6SoDsKI7gmQzQe80JjOrFU8rSid6j80fcFLJg2nGA9wz7I0mYxYnkObRpLjNamHeHidtPHIwuVE6KV1C1Wa3V9UtEvNPB/EdYlfqP1OVoDwSJL9P1EeG8Aq80QGRMx9d866VKmzpc609/pfw+fVUpvUJXO3ALYwC2Ixfsu/dlvOqbJnsfmHXzJcoTQoUWNzA2Xn0lzjbZC6qb3GzMVtGNDgaKpdk2t9z7tomtjYuJEjuWVP/CaV8Y+WyLJZozC0JQAClQBjz1rX5BL5me4h3XYtHc0KiMTkMX0Ge1txz/G1Q0LNl9R+WSFk/idt9AZih/ZemaeiLx8iIdlVXeXuwlsH9tzD2kubFPNF0VdA5W0BfwvrJq3LKc="}]},"index":0,"finish_reason":null}],"usage":{"prompt_tokens":null,"completion_tokens":null,"total_tokens":null},"object":"chat.completion.chunk","id":null,"created":1774030276}").

Please retry if you think this is a transient or recoverable error.

After

🐚 11:34:53 ~/Development/goose alexhancock/gemini-databricks-fix $ GOOSE_PROVIDER=databricks GOOSE_MODEL=databricks-gemini-2-5-pro cargo run --bin goose -- run -t "hello"
   Compiling goose v1.28.0 (/Users/alexhancock/Development/goose/crates/goose)
   Compiling goose-acp v1.28.0 (/Users/alexhancock/Development/goose/crates/goose-acp)
   Compiling goose-cli v1.28.0 (/Users/alexhancock/Development/goose/crates/goose-cli)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.45s
     Running `target/debug/goose run -t hello`

    __( O)>  ● new session · databricks databricks-gemini-2-5-pro
   \____)    20260320_23 · /Users/alexhancock/Development/goose
     L L     goose is ready
Hello! I'm goose, an AI agent framework from Block.

How can I help you today? If you have a task for me, I'll update my todo list right away.

There is an issue in desktop even after these fixes where multiple timestamps appear interspersed between text chunks when streaming, but hard to tell if that's pre-existing. Open to ideas on a fix.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49f99c3d69

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +260 to +262
fn supports_stream_options(model_name: &str) -> bool {
let normalized = model_name.to_ascii_lowercase();
!normalized.contains("gemini")

Choose a reason for hiding this comment

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

P1 Badge Detect Gemini endpoints without relying on endpoint name

supports_stream_options() keys off model_config.model_name, but in this provider that value is the Databricks serving endpoint name: get_endpoint_path() interpolates it directly into /serving-endpoints/{name}/invocations, and fetch_supported_models() returns endpoint.name. In deployments where a Gemini-backed endpoint is aliased to something like prod-chat or assistant-v1, this predicate stays true, Goose still sends stream_options, and the request reproduces the same 400 INVALID_ARGUMENT this patch is meant to eliminate.

Useful? React with 👍 / 👎.

}

if let Some(text) = &chunk.choices[0].delta.content {
let (text_content, _thought_signature) = extract_content_and_signature(chunk.choices[0].delta.content.as_ref());

Choose a reason for hiding this comment

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

P2 Badge Keep Gemini thought signatures when parsing content arrays

The new array-content path parses thoughtSignature and then drops it: extract_content_and_signature() flattens all parts into one string, and response_to_streaming_message() ignores the returned signature entirely. That makes the first streamed turn succeed, but the assistant Message no longer contains the signature-bearing part, so the next request cannot replay Gemini's saved reasoning state. The native Google formatter in this repo preserves these signatures for follow-up turns/tool loops, so this OpenAI-compatible path will still lose context after the initial response.

Useful? React with 👍 / 👎.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 852929dc53

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 412 to +420
let status = resp.status();
let error_text = resp.text().await.unwrap_or_default();

// Parse as JSON if possible to pass to map_http_error_to_provider_error
let json_payload = serde_json::from_str::<Value>(&error_text).ok();
return Err(map_http_error_to_provider_error(status, json_payload));
}
Ok(resp)
})
.await
.inspect_err(|e| {
let _ = log.error(e);
})?;
.await;

Choose a reason for hiding this comment

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

P2 Badge Bypass generic retries before removing stream_options

On Gemini-backed Databricks endpoints this 400 is deterministic, but the request goes through with_retry() before the stream_options fallback runs. I checked crates/goose/src/providers/retry.rs, and should_retry() treats every ProviderError::RequestFailed as retryable, so affected requests will send the same bad payload up to four times with backoff before line 423 finally strips stream_options. That adds several seconds of latency and extra billable calls to every streaming turn on the models this patch is trying to unblock.

Useful? React with 👍 / 👎.

Comment on lines +99 to +104
let text_parts: Vec<_> = parts.iter().filter(|p| p.r#type == "text").collect();

let text = text_parts
.iter()
.map(|p| p.text.as_str())
.collect::<String>();

Choose a reason for hiding this comment

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

P1 Badge Keep thought parts out of visible assistant text

Gemini's OpenAI-compatible chat completions support include_thoughts, and those thought summaries arrive as content parts rather than reasoning_content. This code concatenates every type == "text" part into MessageContent::text, but ContentPart never records whether a part was a thought, so any thought: true segment will be shown and persisted as normal assistant output instead of hidden MessageContent::Thinking. In thought-enabled sessions that leaks the model's internal reasoning to users.

Useful? React with 👍 / 👎.

@DOsinga DOsinga added the needs_human label to set when a robot looks at a PR and can't handle it label Mar 21, 2026
* main:
  feat(acp): add session/set_config and stabilize list, delete and close (#7984)
  docs: Correct `gosoe` typo to `goose` (#8062)
  fix: use default provider and model when provider in session no longer exists (#8035)
@michaelneale michaelneale self-assigned this Mar 22, 2026
Copy link
Collaborator

@michaelneale michaelneale left a comment

Choose a reason for hiding this comment

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

I think this is fine if clean, as we are the users of this, alex would have tried it with db internally

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 71a7300ea8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +106 to +108
let signature = text_parts
.iter()
.find_map(|p| p.thought_signature.as_ref())

Choose a reason for hiding this comment

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

P1 Badge Use the last thoughtSignature in a streamed content array

Gemini 3 places the continuation signature on the last part of a non-function response, but extract_content_and_signature() keeps the first thoughtSignature it sees. If a single delta.content array contains multiple text parts, last_signature can be set to a stale value, and the next tool call/follow-up turn will be replayed with the wrong signature. That breaks the continuation contract this patch is trying to restore and can reproduce Gemini's missing/invalid thought_signature 400s on later turns.

Useful? React with 👍 / 👎.

Comment on lines +797 to 800
if let Some(text) = text_content {
if !text.is_empty() {
content.push(MessageContent::text(text));
content.push(MessageContent::text(&text));
}

Choose a reason for hiding this comment

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

P1 Badge Preserve thought signatures on plain-text assistant turns

This branch updates last_signature and then downgrades the signed Gemini part to MessageContent::Text, which means the signature is discarded unless a tool call happens later in the same response. In format_messages() assistant text is serialized back as bare text, so a successful first reply from Gemini 3 cannot be appended "including thought signatures" on the next request. For text-only chats that turns this fix into a one-turn success followed by lost reasoning context or another thought_signature validation error on the next turn.

Useful? React with 👍 / 👎.

* main:
  deps: replace sigstore-verification with sigstore-verify to kill vulns (#8064)
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5fbf1f704

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +423 to +425
Err(e) if e.to_string().contains("stream_options") => {
payload.as_object_mut().unwrap().remove("stream_options");
self.with_retry(|| async {

Choose a reason for hiding this comment

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

P2 Badge Preserve usage accounting in the stream_options fallback

When a Databricks endpoint rejects stream_options, this branch drops the whole object before retrying. On the OpenAI-compatible path, stream_openai_compat() only produces ProviderUsage from streamed usage blocks, and crates/goose/src/agents/agent.rs:1232-1234 only updates session metrics when that usage arrives. As a result, every Gemini/Databricks turn that takes this fallback can succeed while recording no token usage or cost data, silently under-reporting spend for the exact models this patch is meant to unblock.

Useful? React with 👍 / 👎.

Comment on lines +791 to +799
let (text_content, thought_signature) = extract_content_and_signature(chunk.choices[0].delta.content.as_ref());

if let Some(sig) = thought_signature {
last_signature = Some(sig);
}

if let Some(text) = text_content {
if !text.is_empty() {
content.push(MessageContent::text(text));
content.push(MessageContent::text(&text));

Choose a reason for hiding this comment

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

P2 Badge Reuse a stable id when array-content chunks omit id

Databricks/Gemini chunks on this new DeltaContent::Array path can have "id": null (the example in this commit does), and ui/desktop/src/hooks/useChatStream.ts:178-212 only coalesces streamed deltas when consecutive messages share an id. Because these array-content chunks now flow through as separate assistant messages with no fallback id, the desktop stream renders one timestamped bubble per delta instead of extending a single reply, which matches the regression called out in the commit message.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs_human label to set when a robot looks at a PR and can't handle it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants