Skip to content

Commit bd7e521

Browse files
authored
Fix message format and tool handling for Google LLMs (#1877)
1 parent 2960b8e commit bd7e521

1 file changed

Lines changed: 25 additions & 11 deletions

File tree

lumen/ai/llm.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,7 @@ class Google(Llm, GenAIMixin):
20372037
})
20382038

20392039
select_models = param.List(default=[
2040+
"gemini-3.5-flash",
20402041
"gemini-3-flash-preview",
20412042
"gemini-3-pro-preview",
20422043
"gemini-2.5-pro",
@@ -2089,12 +2090,16 @@ def _get_content(cls, response: Any) -> str | BaseModel:
20892090
return str(response)
20902091

20912092
@classmethod
2092-
def _messages_to_contents(cls, messages: list[Message]) -> tuple[list[dict[str, Any]], str | None]:
2093+
def _messages_to_contents(
2094+
cls, messages: list[Message]
2095+
) -> tuple[list[dict[str, Any]], str | None, list[dict[str, Any]]]:
20932096
"""
20942097
Transform messages into contents format expected by Google GenAI API.
20952098
20962099
Extracts system messages and returns them separately since Google
2097-
requires them via the system_instruction parameter.
2100+
requires them via the system_instruction parameter. Also returns a
2101+
role-normalized (assistant → model) flat messages list for use with
2102+
instructor's Gemini provider, which rejects role="assistant".
20982103
20992104
Parameters
21002105
----------
@@ -2103,21 +2108,29 @@ def _messages_to_contents(cls, messages: list[Message]) -> tuple[list[dict[str,
21032108
21042109
Returns
21052110
-------
2106-
tuple[list[dict[str, Any]], str | None]
2107-
Tuple of (contents list, system_instruction)
2111+
tuple[list[dict[str, Any]], str | None, list[dict[str, Any]]]
2112+
Tuple of (contents list, system_instruction, instructor_messages)
21082113
"""
2114+
# Normalize roles up front: Gemini (and instructor's Gemini converter)
2115+
# use "model" instead of "assistant".
2116+
normalized = [
2117+
{**m, "role": "model"} if m.get("role") == "assistant" else m
2118+
for m in messages
2119+
]
2120+
21092121
contents = []
21102122
system_instruction = None
2123+
instructor_messages = []
21112124

2112-
for message in messages:
2125+
for message in normalized:
21132126
role = message["role"]
21142127
content = message["content"]
21152128
if role == "system":
21162129
system_instruction = content
21172130
continue
21182131

2119-
# Assistant message containing tool calls → model with function_call parts
2120-
if role == "assistant" and message.get("tool_calls"):
2132+
# Model message containing tool calls → function_call parts
2133+
if role == "model" and message.get("tool_calls"):
21212134
parts = []
21222135
for tc in message["tool_calls"]:
21232136
name, args, _ = cls._parse_tool_call(tc)
@@ -2158,8 +2171,9 @@ def _messages_to_contents(cls, messages: list[Message]) -> tuple[list[dict[str,
21582171
"role": role,
21592172
"parts": [{"text": content}]
21602173
})
2174+
instructor_messages.append({"role": role, "content": content})
21612175

2162-
return contents, system_instruction
2176+
return contents, system_instruction, instructor_messages
21632177

21642178
async def get_client(self, model_spec: str | dict, response_model: BaseModel | None = None, **kwargs):
21652179
model_kwargs = self._get_model_kwargs(model_spec)
@@ -2241,7 +2255,7 @@ def _translate_tool_specs(cls, tool_specs: list) -> Any:
22412255
description=func.get("description", ""),
22422256
parameters=parameters,
22432257
))
2244-
return Tool(function_declarations=declarations) if declarations else None
2258+
return [Tool(function_declarations=declarations)] if declarations else None
22452259

22462260
@classmethod
22472261
def _get_delta(cls, chunk: Any) -> str:
@@ -2280,7 +2294,7 @@ async def run_client(self, model_spec: str | dict, messages: list[Message], **kw
22802294

22812295
tools = self._translate_tool_specs(kwargs.pop("tools", []))
22822296
client = await self.get_client(model_spec, **kwargs)
2283-
contents, system_instruction = self._messages_to_contents(messages)
2297+
contents, system_instruction, instructor_messages = self._messages_to_contents(messages)
22842298
config = GenerateContentConfig(
22852299
http_options=http_options,
22862300
temperature=self.temperature,
@@ -2290,7 +2304,7 @@ async def run_client(self, model_spec: str | dict, messages: list[Message], **kw
22902304
)
22912305

22922306
if response_model:
2293-
result = await client(messages=messages, config=config, **kwargs)
2307+
result = await client(messages=instructor_messages, config=config, **kwargs)
22942308
return result
22952309

22962310
kwargs.pop("stream", None)

0 commit comments

Comments
 (0)