Update Gemini service to include function name when sending function responses in context
This commit is contained in:
@@ -110,7 +110,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|||||||
system = NOT_GIVEN
|
system = NOT_GIVEN
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
# first, map messages using self._from_universal_context_message(m)
|
# First, map messages using self._from_universal_context_message(m)
|
||||||
try:
|
try:
|
||||||
messages = [self._from_universal_context_message(m) for m in universal_context_messages]
|
messages = [self._from_universal_context_message(m) for m in universal_context_messages]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
|||||||
system = None
|
system = None
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
# first, map messages using self._from_universal_context_message(m)
|
# First, map messages using self._from_universal_context_message(m)
|
||||||
try:
|
try:
|
||||||
messages = [self._from_universal_context_message(m) for m in universal_context_messages]
|
messages = [self._from_universal_context_message(m) for m in universal_context_messages]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, Dict, List, Optional, Tuple, TypedDict
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from openai import NotGiven
|
from openai import NotGiven
|
||||||
@@ -133,6 +133,28 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
messages: List[Content]
|
messages: List[Content]
|
||||||
system_instruction: Optional[str] = None
|
system_instruction: Optional[str] = None
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MessageConversionResult:
|
||||||
|
"""Result of converting a single universal context message to Google format.
|
||||||
|
|
||||||
|
Either content (a Google Content object) or a system instruction string
|
||||||
|
is guaranteed to be set.
|
||||||
|
|
||||||
|
Also returns a tool call ID to name mapping for any tool calls
|
||||||
|
discovered in the message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
content: Optional[Content] = None
|
||||||
|
system_instruction: Optional[str] = None
|
||||||
|
tool_call_id_to_name_mapping: Dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MessageConversionParams:
|
||||||
|
"""Parameters for converting a single universal context message to Google format."""
|
||||||
|
|
||||||
|
already_have_system_instruction: bool
|
||||||
|
tool_call_id_to_name_mapping: Dict[str, str]
|
||||||
|
|
||||||
def _from_universal_context_messages(
|
def _from_universal_context_messages(
|
||||||
self, universal_context_messages: List[LLMContextMessage]
|
self, universal_context_messages: List[LLMContextMessage]
|
||||||
) -> ConvertedMessages:
|
) -> ConvertedMessages:
|
||||||
@@ -156,24 +178,26 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
"""
|
"""
|
||||||
system_instruction = None
|
system_instruction = None
|
||||||
messages = []
|
messages = []
|
||||||
|
tool_call_id_to_name_mapping = {}
|
||||||
|
|
||||||
# Process each message, preserving Google-formatted messages and converting others
|
# Process each message, preserving Google-formatted messages and converting others
|
||||||
for message in universal_context_messages:
|
for message in universal_context_messages:
|
||||||
if isinstance(message, LLMSpecificMessage):
|
result = self._from_universal_context_message(
|
||||||
# Assume that LLMSpecificMessage wraps a message in Google format
|
message,
|
||||||
messages.append(message.message)
|
params=self.MessageConversionParams(
|
||||||
continue
|
already_have_system_instruction=bool(system_instruction),
|
||||||
|
tool_call_id_to_name_mapping=tool_call_id_to_name_mapping,
|
||||||
# Convert standard format to Google format
|
),
|
||||||
converted = self._from_standard_message(
|
|
||||||
message, already_have_system_instruction=bool(system_instruction)
|
|
||||||
)
|
)
|
||||||
if isinstance(converted, Content):
|
# Each result is either a Content or a system instruction
|
||||||
# Regular (non-system) message
|
if result.content:
|
||||||
messages.append(converted)
|
messages.append(result.content)
|
||||||
else:
|
elif result.system_instruction:
|
||||||
# System instruction
|
system_instruction = result.system_instruction
|
||||||
system_instruction = converted
|
|
||||||
|
# Merge tool call ID to name mapping
|
||||||
|
if result.tool_call_id_to_name_mapping:
|
||||||
|
tool_call_id_to_name_mapping.update(result.tool_call_id_to_name_mapping)
|
||||||
|
|
||||||
# Check if we only have function-related messages (no regular text)
|
# Check if we only have function-related messages (no regular text)
|
||||||
has_regular_messages = any(
|
has_regular_messages = any(
|
||||||
@@ -193,9 +217,16 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
|
|
||||||
return self.ConvertedMessages(messages=messages, system_instruction=system_instruction)
|
return self.ConvertedMessages(messages=messages, system_instruction=system_instruction)
|
||||||
|
|
||||||
|
def _from_universal_context_message(
|
||||||
|
self, message: LLMContextMessage, *, params: MessageConversionParams
|
||||||
|
) -> MessageConversionResult:
|
||||||
|
if isinstance(message, LLMSpecificMessage):
|
||||||
|
return message.message
|
||||||
|
return self._from_standard_message(message, params=params)
|
||||||
|
|
||||||
def _from_standard_message(
|
def _from_standard_message(
|
||||||
self, message: LLMStandardMessage, already_have_system_instruction: bool
|
self, message: LLMStandardMessage, *, params: MessageConversionParams
|
||||||
) -> Content | str:
|
) -> MessageConversionResult:
|
||||||
"""Convert standard universal context message to Google Content object.
|
"""Convert standard universal context message to Google Content object.
|
||||||
|
|
||||||
Handles conversion of text, images, and function calls to Google's
|
Handles conversion of text, images, and function calls to Google's
|
||||||
@@ -205,10 +236,11 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
Args:
|
Args:
|
||||||
message: Message in standard universal context format.
|
message: Message in standard universal context format.
|
||||||
already_have_system_instruction: Whether we already have a system instruction
|
already_have_system_instruction: Whether we already have a system instruction
|
||||||
|
params: Parameters for conversion.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Content object with role and parts, or a plain string for system
|
MessageConversionResult containing either a Content object or a
|
||||||
messages.
|
system instruction string.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
Standard text message::
|
Standard text message::
|
||||||
@@ -248,26 +280,36 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
"""
|
"""
|
||||||
role = message["role"]
|
role = message["role"]
|
||||||
content = message.get("content", [])
|
content = message.get("content", [])
|
||||||
|
|
||||||
if role == "system":
|
if role == "system":
|
||||||
if already_have_system_instruction:
|
if params.already_have_system_instruction:
|
||||||
role = "user" # Convert system message to user role if we already have a system instruction
|
role = "user" # Convert system message to user role if we already have a system instruction
|
||||||
else:
|
else:
|
||||||
# System instructions are returned as plain text
|
system_instruction: str = None
|
||||||
if isinstance(content, str):
|
if isinstance(content, str):
|
||||||
return content
|
system_instruction = content
|
||||||
elif isinstance(content, list):
|
elif isinstance(content, list):
|
||||||
# If content is a list, we assume it's a list of text parts, per the standard
|
# If content is a list, we assume it's a list of text parts, per the standard
|
||||||
return " ".join(part["text"] for part in content if part.get("type") == "text")
|
system_instruction = " ".join(
|
||||||
|
part["text"] for part in content if part.get("type") == "text"
|
||||||
|
)
|
||||||
|
if system_instruction:
|
||||||
|
return self.MessageConversionResult(system_instruction=system_instruction)
|
||||||
elif role == "assistant":
|
elif role == "assistant":
|
||||||
role = "model"
|
role = "model"
|
||||||
|
|
||||||
parts = []
|
parts = []
|
||||||
|
tool_call_id_to_name_mapping = {}
|
||||||
|
|
||||||
if message.get("tool_calls"):
|
if message.get("tool_calls"):
|
||||||
for tc in message["tool_calls"]:
|
for tc in message["tool_calls"]:
|
||||||
|
id = tc["id"]
|
||||||
|
name = tc["function"]["name"]
|
||||||
|
tool_call_id_to_name_mapping[id] = name
|
||||||
parts.append(
|
parts.append(
|
||||||
Part(
|
Part(
|
||||||
function_call=FunctionCall(
|
function_call=FunctionCall(
|
||||||
name=tc["function"]["name"],
|
name=name,
|
||||||
args=json.loads(tc["function"]["arguments"]),
|
args=json.loads(tc["function"]["arguments"]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -284,9 +326,16 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
# Response might not be JSON-deserializable.
|
# Response might not be JSON-deserializable.
|
||||||
# This occurs with a UserImageFrame, for example, where we get a plain "COMPLETED" string.
|
# This occurs with a UserImageFrame, for example, where we get a plain "COMPLETED" string.
|
||||||
response_dict = {"value": message["content"]}
|
response_dict = {"value": message["content"]}
|
||||||
|
|
||||||
|
# Get function name from mapping using tool_call_id, or fallback
|
||||||
|
tool_call_id = message.get("tool_call_id")
|
||||||
|
function_name = "tool_call_result" # Default fallback
|
||||||
|
if tool_call_id and tool_call_id in params.tool_call_id_to_name_mapping:
|
||||||
|
function_name = params.tool_call_id_to_name_mapping[tool_call_id]
|
||||||
|
|
||||||
parts.append(
|
parts.append(
|
||||||
Part.from_function_response(
|
Part.from_function_response(
|
||||||
name="tool_call_result", # seems to work to hard-code the same name every time
|
name=function_name,
|
||||||
response=response_dict,
|
response=response_dict,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -310,4 +359,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
audio_bytes = base64.b64decode(input_audio["data"])
|
audio_bytes = base64.b64decode(input_audio["data"])
|
||||||
parts.append(Part(inline_data=Blob(mime_type="audio/wav", data=audio_bytes)))
|
parts.append(Part(inline_data=Blob(mime_type="audio/wav", data=audio_bytes)))
|
||||||
|
|
||||||
return Content(role=role, parts=parts)
|
return self.MessageConversionResult(
|
||||||
|
content=Content(role=role, parts=parts),
|
||||||
|
tool_call_id_to_name_mapping=tool_call_id_to_name_mapping,
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user