{"record":{"id":"d913113344f1162a","repo":"google-gemini/gemini-cli","slug":"malformed-function-call","errorCode":"MALFORMED_FUNCTION_CALL","errorMessage":"Model stream ended with malformed function call.","messagePattern":"Model stream ended with malformed function call\\.","errorType":"exception","errorClass":"InvalidStreamError","httpStatus":null,"severity":"error","filePath":"packages/core/src/core/geminiChat.ts","lineNumber":1583,"sourceCode":"    // Stream validation logic: A stream is considered successful if:\n    // 1. There's a tool call OR\n    // 2. A not MALFORMED_FUNCTION_CALL finish reason and a non-mepty resp\n    //\n    // We throw an error only when there's no tool call AND:\n    // - No finish reason, OR\n    // - MALFORMED_FUNCTION_CALL finish reason OR\n    // - Empty response text (e.g., only thoughts with no actual content)\n    if (!hasToolCall) {\n      if (!finishReason) {\n        if (!isOriginalFunctionResponse) {\n          throw new InvalidStreamError(\n            'Model stream ended without a finish reason.',\n            'NO_FINISH_REASON',\n          );\n        }\n      }\n      if (finishReason === FinishReason.MALFORMED_FUNCTION_CALL) {\n        throw new InvalidStreamError(\n          'Model stream ended with malformed function call.',\n          'MALFORMED_FUNCTION_CALL',\n        );\n      }\n      if (finishReason === FinishReason.UNEXPECTED_TOOL_CALL) {\n        throw new InvalidStreamError(\n          'Model stream ended with unexpected tool call.',\n          'UNEXPECTED_TOOL_CALL',\n        );\n      }\n      if (!responseText) {\n        if (finishReason === FinishReason.MAX_TOKENS) {\n          throw new InvalidStreamError(\n            'Model stream ended due to token limit exhaustion (MAX_TOKENS) with empty response text.',\n            'MAX_TOKENS_EXCEEDED',\n          );\n        }\n        if (finishReason === FinishReason.SAFETY) {","sourceCodeStart":1565,"sourceCodeEnd":1601,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/3c311beac2e78336816dd4a123db39743f9fbf85/packages/core/src/core/geminiChat.ts#L1565-L1601","documentation":"The Gemini API itself reported finishReason MALFORMED_FUNCTION_CALL: the model attempted a tool call but produced arguments that failed validation (typically invalid or truncated JSON), and no valid functionCall part was assembled from the stream. geminiChat.ts:1516 throws immediately whenever this finishReason appears and hasToolCall is false — unlike the text/finishReason checks it is NOT exempted for tool-response turns. It is an InvalidStreamError and is retried internally (with a nudge appended on retry), but if the schema keeps inducing bad arguments the retries fail too.","triggerScenarios":"A streaming generateContent request with tool declarations where the model emits a function call whose arguments are unparseable, so the final chunk carries finishReason=MALFORMED_FUNCTION_CALL instead of a usable functionCall part. Strongly correlated with: tool parameter schemas using constructs outside Gemini's function-calling subset (oneOf/anyOf/allOf/not, additionalProperties, deeply nested objects, exotic formats), very large schemas, dozens of concurrently declared tools (the model mixes up argument shapes), and maxOutputTokens too small to fit the JSON arguments.","commonSituations":"You just added a new tool with a big auto-generated (e.g., zod-to-JSON-Schema) schema and now calls intermittently fail; LangChain/JSON-schema converted types with $refs and unions that Gemini cannot express; intermittent failures on one specific tool out of many while others work; after switching to a smaller/older model with weaker function calling; failures that cluster when the model must produce very long argument strings.","solutions":["Simplify the parameter schema of the failing tool: flatten nested objects, replace unions/oneOf with enum + optional primitives, drop unused optional properties, and keep required lists short.","Reduce the number of tools registered for the turn (or split tool sets across sub-agents) so the model is less likely to garble argument structures.","Raise generationConfig maxOutputTokens so a long JSON argument body is not truncated mid-string (truncated JSON parses as malformed).","Switch to a model version with stronger function-calling (latest pro-tier) if you are on an older/smaller variant.","Retry the turn — a fraction of these are plain model flakiness; the library already retried 3 times, so retry once more after a pause, but do not loop forever on a reproducible schema problem."],"exampleFix":"// before: schema Gemini frequently mangles (unions + deep nesting)\nconst searchTool = {\n  name: 'search',\n  parameters: {\n    type: 'object',\n    properties: {\n      filter: {\n        oneOf: [\n          { type: 'object', properties: { ids: { type: 'array', items: { type: 'string' } } } },\n          { type: 'object', properties: { query: { type: 'string', regex: '.+' } } },\n        ],\n      },\n      options: { type: 'object', properties: { paging: { type: 'object', properties: { offset: { type: 'number' }, limit: { type: 'number' } } } } },\n    },\n  },\n};\n\n// after: flattened, Gemini-function-calling-friendly\nconst searchTool = {\n  name: 'search',\n  parameters: {\n    type: 'object',\n    properties: {\n      filter_ids:   { type: 'array', items: { type: 'string' }, description: 'Match these ids' },\n      filter_query: { type: 'string', description: 'Free-text query' },\n      offset:       { type: 'number' },\n      limit:        { type: 'number' },\n    },\n  },\n};","handlingStrategy":"retry","validationCode":"// Run BEFORE registering tools: flag schema constructs Gemini's function calling\n// frequently mangles into MALFORMED_FUNCTION_CALL.\nfunction auditToolSchemas(tools: Array<{ name: string; parameters: unknown }>): string[] {\n  const problems: string[] = [];\n  const walk = (node: any, path: string) => {\n    if (!node || typeof node !== 'object') return;\n    for (const kw of ['oneOf', 'anyOf', 'allOf', 'not', 'additionalProperties']) {\n      if (kw in node) problems.push(`${path}: uses ${kw}`);\n    }\n    if (path.split('.').length > 4) problems.push(`${path}: nested deeper than 4 levels`);\n    for (const [k, v] of Object.entries(node.properties ?? {})) walk(v, `${path}.${k}`);\n    if (Array.isArray(node.required) && node.required.length > 6) problems.push(`${path}: ${node.required.length} required fields`);\n  };\n  for (const t of tools) walk(t.parameters, t.name);\n  return problems; // non-empty => simplify before sending, or you will retry forever\n}","typeGuard":"import { InvalidStreamError } from './packages/core/src/core/geminiChat.js';\n\nfunction isMalformedFunctionCallError(e: unknown): e is InvalidStreamError & { type: 'MALFORMED_FUNCTION_CALL' } {\n  return e instanceof InvalidStreamError && e.type === 'MALFORMED_FUNCTION_CALL';\n}","tryCatchPattern":"try {\n  for await (const chunk of chat.sendMessageStream(userMsg)) { handle(chunk); }\n} catch (e) {\n  if (isMalformedFunctionCallError(e)) {\n    // One retry is reasonable (model flakiness); a second consecutive failure means\n    // the schema itself is the problem — simplify the tool instead of looping.\n    if (++malformedCount === 1) return runTurn(chat, userMsg);\n    throw new Error('Tool schema induces malformed function calls; simplify it: ' + lastToolName);\n  }\n  throw e;\n}","preventionTips":["Keep tool parameter schemas flat and primitive-heavy; avoid oneOf/anyOf/additionalProperties/$ref constructs from auto-converted zod or JSON-Schema sources.","Limit how many tools are declared per request; split them across focused sub-agents when the catalog grows.","Set maxOutputTokens comfortably above the largest JSON argument body the model must emit.","Log which tool was pending when the error fired so a repeat offender schema is identifiable, and re-run the schema audit whenever tools change."],"tags":["gemini","function-calling","tools","json-schema","malformed-args","invalid-stream"],"backgroundTag":"llm-function-call-invalid-arguments","analyzedSha":"3c311beac2e78336816dd4a123db39743f9fbf85","analyzedAt":"2026-08-21T17:03:46.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}