From 8d7273924fd4c6980e07fc46fdf671301900a5fb Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Tue, 22 Apr 2025 22:12:03 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20Gemini=E4=BF=9D=E8=AF=81=E5=81=B6?= =?UTF-8?q?=E6=95=B0=E7=B4=A2=E5=BC=95=E4=B8=BA=E7=94=A8=E6=88=B7=E6=B6=88?= =?UTF-8?q?=E6=81=AF=EF=BC=8C=E5=A5=87=E6=95=B0=E7=B4=A2=E5=BC=95=E4=B8=BA?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/gemini_source.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index ef2305069..038d57751 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -260,6 +260,20 @@ class ProviderGoogleGenAI(Provider): ) ) + # 保证偶数索引为用户消息,奇数索引为模型消息 + content_num = len(gemini_contents) + for i in range(content_num): + expected_type = types.UserContent if i % 2 == 0 else types.ModelContent + if not isinstance(gemini_contents[i], expected_type): + for j in range(i + 1, content_num): + if isinstance(gemini_contents[j], expected_type): + logger.debug(f"交换索引 {i} 与 {j}") + gemini_contents[i], gemini_contents[j] = ( + gemini_contents[j], + gemini_contents[i], + ) + break + return gemini_contents @staticmethod From f07c54d47caf0b4f2caf4b256cc197dd1a79c390 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 00:48:25 +0800 Subject: [PATCH 2/5] =?UTF-8?q?style:=20=E5=87=8F=E5=B0=91=E4=B8=80?= =?UTF-8?q?=E5=B1=82=20intent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com> --- .../core/provider/sources/gemini_source.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index 038d57751..8600d3e9f 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -264,15 +264,16 @@ class ProviderGoogleGenAI(Provider): content_num = len(gemini_contents) for i in range(content_num): expected_type = types.UserContent if i % 2 == 0 else types.ModelContent - if not isinstance(gemini_contents[i], expected_type): - for j in range(i + 1, content_num): - if isinstance(gemini_contents[j], expected_type): - logger.debug(f"交换索引 {i} 与 {j}") - gemini_contents[i], gemini_contents[j] = ( - gemini_contents[j], - gemini_contents[i], - ) - break + if isinstance(gemini_contents[i], expected_type): + continue + for j in range(i + 1, content_num): + if isinstance(gemini_contents[j], expected_type): + logger.debug(f"交换索引 {i} 与 {j}") + gemini_contents[i], gemini_contents[j] = ( + gemini_contents[j], + gemini_contents[i], + ) + break return gemini_contents From c8f567347bafe91fb2e882f28a9b1add2a980680 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 11:52:22 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E9=87=8D?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=E9=80=BB=E8=BE=91=E4=B8=BA=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E8=BF=9E=E7=BB=AD=E7=9B=B8=E5=90=8C=E7=B1=BB=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/provider/sources/gemini_source.py | 97 +++++++++---------- 1 file changed, 44 insertions(+), 53 deletions(-) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index 8600d3e9f..d8d4c789c 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -182,11 +182,11 @@ class ProviderGoogleGenAI(Provider): def _prepare_conversation(self, payloads: Dict) -> List[types.Content]: """准备 Gemini SDK 的 Content 列表""" - def create_text_part(text: str) -> types.UserContent: + def create_text_part(text: str) -> types.Part: content_a = text if text else " " if not text: logger.warning("文本内容为空,已添加空格占位") - return types.UserContent(parts=[types.Part.from_text(text=content_a)]) + return types.Part.from_text(text=content_a) def process_image_url(image_url_dict: dict) -> types.Part: url = image_url_dict["url"] @@ -205,75 +205,66 @@ class ProviderGoogleGenAI(Provider): role, content = message["role"], message.get("content") if role == "user": - if isinstance(content, str): - gemini_contents.append(create_text_part(content)) - elif isinstance(content, list): + if isinstance(content, list): parts = [ types.Part.from_text(text=item["text"] or " ") if item["type"] == "text" else process_image_url(item["image_url"]) for item in content ] + else: + parts = [create_text_part(content)] + + if gemini_contents and isinstance(gemini_contents[-1], types.UserContent): + gemini_contents[-1].parts.extend(parts) + else: gemini_contents.append(types.UserContent(parts=parts)) elif role == "assistant": if content: - gemini_contents.append( - types.ModelContent(parts=[types.Part.from_text(text=content)]) - ) - elif "tool_calls" in message and not native_tool_enabled: - gemini_contents.extend( - [ - types.ModelContent( - parts=[ - types.Part.from_function_call( - name=tool["function"]["name"], - args=json.loads(tool["function"]["arguments"]), - ) - ] - ) - for tool in message["tool_calls"] - ] - ) + parts = [types.Part.from_text(text=content)] + if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): + gemini_contents[-1].parts.extend(parts) + else: + gemini_contents.append(types.ModelContent(parts=parts)) + elif not native_tool_enabled and "tool_calls" in message : + parts = [ + types.Part.from_function_call( + name=tool["function"]["name"], + args=json.loads(tool["function"]["arguments"]), + ) + for tool in message["tool_calls"] + ] + if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): + gemini_contents[-1].parts.extend(parts) + else: + gemini_contents.append(types.ModelContent(parts=parts)) else: logger.warning("assistant 角色的消息内容为空,已添加空格占位") - if native_tool_enabled: + if native_tool_enabled and "tool_calls" in message: logger.warning( "检测到启用Gemini原生工具,且上下文中存在函数调用,建议使用 /reset 重置上下文" ) - gemini_contents.append( - types.ModelContent(parts=[types.Part.from_text(text=" ")]) - ) + parts = [types.Part.from_text(text=" ")] + if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): + gemini_contents[-1].parts.extend(parts) + else: + gemini_contents.append(types.ModelContent(parts=parts)) elif role == "tool" and not native_tool_enabled: - gemini_contents.append( - types.UserContent( - parts=[ - types.Part.from_function_response( - name=message["tool_call_id"], - response={ - "name": message["tool_call_id"], - "content": message["content"], - }, - ) - ] + parts = [ + types.Part.from_function_response( + name=message["tool_call_id"], + response={ + "name": message["tool_call_id"], + "content": message["content"], + }, ) - ) - - # 保证偶数索引为用户消息,奇数索引为模型消息 - content_num = len(gemini_contents) - for i in range(content_num): - expected_type = types.UserContent if i % 2 == 0 else types.ModelContent - if isinstance(gemini_contents[i], expected_type): - continue - for j in range(i + 1, content_num): - if isinstance(gemini_contents[j], expected_type): - logger.debug(f"交换索引 {i} 与 {j}") - gemini_contents[i], gemini_contents[j] = ( - gemini_contents[j], - gemini_contents[i], - ) - break + ] + if gemini_contents and isinstance(gemini_contents[-1], types.UserContent): + gemini_contents[-1].parts.extend(parts) + else: + gemini_contents.append(types.UserContent(parts=parts)) return gemini_contents From 97a6a1fdc29a1228dc95479ba7800e8d7d2ee30e Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 12:20:18 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=E4=BF=9D=E8=AF=81=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E6=9D=A1=E6=B6=88=E6=81=AF=E4=B8=8D=E4=B8=BAmodel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/gemini_source.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index d8d4c789c..fe9cc4346 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -266,6 +266,9 @@ class ProviderGoogleGenAI(Provider): else: gemini_contents.append(types.UserContent(parts=parts)) + if gemini_contents and isinstance(gemini_contents[0], types.ModelContent): + gemini_contents.pop() + return gemini_contents @staticmethod From a23d5be056a96da724fdcbbf4b0c059400677fd2 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 12:49:27 +0800 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20=E5=87=8F=E5=B0=91=E5=B5=8C?= =?UTF-8?q?=E5=A5=97=E6=9D=A1=E4=BB=B6=E5=92=8C=E9=87=8D=E5=A4=8D=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/provider/sources/gemini_source.py | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index fe9cc4346..a175a3d68 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -194,6 +194,12 @@ class ProviderGoogleGenAI(Provider): image_bytes = base64.b64decode(url.split(",", 1)[1]) return types.Part.from_bytes(data=image_bytes, mime_type=mime_type) + def append_or_extend(contents: list[types.Content], part: list[types.Part], content_cls: type[types.Content]) -> None: + if contents and isinstance(contents[-1], content_cls): + contents[-1].parts.extend(part) + else: + contents.append(content_cls(parts=part)) + gemini_contents: List[types.Content] = [] native_tool_enabled = any( [ @@ -214,19 +220,12 @@ class ProviderGoogleGenAI(Provider): ] else: parts = [create_text_part(content)] - - if gemini_contents and isinstance(gemini_contents[-1], types.UserContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.UserContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.UserContent) elif role == "assistant": if content: parts = [types.Part.from_text(text=content)] - if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.ModelContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.ModelContent) elif not native_tool_enabled and "tool_calls" in message : parts = [ types.Part.from_function_call( @@ -235,10 +234,7 @@ class ProviderGoogleGenAI(Provider): ) for tool in message["tool_calls"] ] - if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.ModelContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.ModelContent) else: logger.warning("assistant 角色的消息内容为空,已添加空格占位") if native_tool_enabled and "tool_calls" in message: @@ -246,10 +242,7 @@ class ProviderGoogleGenAI(Provider): "检测到启用Gemini原生工具,且上下文中存在函数调用,建议使用 /reset 重置上下文" ) parts = [types.Part.from_text(text=" ")] - if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.ModelContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.ModelContent) elif role == "tool" and not native_tool_enabled: parts = [ @@ -261,10 +254,7 @@ class ProviderGoogleGenAI(Provider): }, ) ] - if gemini_contents and isinstance(gemini_contents[-1], types.UserContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.UserContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.UserContent) if gemini_contents and isinstance(gemini_contents[0], types.ModelContent): gemini_contents.pop()