From ac1d39580baed4d66ef0a08173e8b1770747c92e Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 15:05:04 +0800 Subject: [PATCH] feat: add Google Gemini API support (#1418) * feat: add Google Gemini API support Co-Authored-By: Junyan Qin * fix: remove unused imports Co-Authored-By: Junyan Qin * feat: add google-genai dependency Co-Authored-By: Junyan Qin * fix: update Gemini API implementation to use correct API methods Co-Authored-By: Junyan Qin * refactor: improve Gemini API implementation based on official documentation Co-Authored-By: Junyan Qin * fix: remove unsupported timeout parameter from Gemini API implementation Co-Authored-By: Junyan Qin * fix: correct Gemini API implementation based on official documentation Co-Authored-By: Junyan Qin * feat: update geminichatcmpl * deps: add google-generativeai --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Junyan Qin --- pkg/provider/modelmgr/requesters/gemini.svg | 1 + .../modelmgr/requesters/geminichatcmpl.py | 87 +++++++++++++++++++ .../modelmgr/requesters/geminichatcmpl.yaml | 28 ++++++ requirements.txt | 4 +- 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 pkg/provider/modelmgr/requesters/gemini.svg create mode 100644 pkg/provider/modelmgr/requesters/geminichatcmpl.py create mode 100644 pkg/provider/modelmgr/requesters/geminichatcmpl.yaml diff --git a/pkg/provider/modelmgr/requesters/gemini.svg b/pkg/provider/modelmgr/requesters/gemini.svg new file mode 100644 index 00000000..878eb627 --- /dev/null +++ b/pkg/provider/modelmgr/requesters/gemini.svg @@ -0,0 +1 @@ +Gemini \ No newline at end of file diff --git a/pkg/provider/modelmgr/requesters/geminichatcmpl.py b/pkg/provider/modelmgr/requesters/geminichatcmpl.py new file mode 100644 index 00000000..3790b7c2 --- /dev/null +++ b/pkg/provider/modelmgr/requesters/geminichatcmpl.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import typing +import google.genai +from google.genai import types + +from .. import errors, requester +from ....core import entities as core_entities +from ... import entities as llm_entities +from ...tools import entities as tools_entities + + +class GeminiChatCompletions(requester.LLMAPIRequester): + """Google Gemini API 请求器""" + + default_config: dict[str, typing.Any] = { + 'base_url': 'https://generativelanguage.googleapis.com', + 'timeout': 120, + } + + async def initialize(self): + """初始化 Gemini API 客户端""" + pass + + async def invoke_llm( + self, + query: core_entities.Query, + model: requester.RuntimeLLMModel, + messages: typing.List[llm_entities.Message], + funcs: typing.List[tools_entities.LLMFunction] = None, + extra_args: dict[str, typing.Any] = {}, + ) -> llm_entities.Message: + """调用 Gemini API 生成回复""" + try: + self.client = google.genai.Client( + api_key=model.token_mgr.get_token(), + http_options=types.HttpOptions(api_version='v1alpha'), + ) + contents = [] + + system_content = None + + for message in messages: + role = message.role + parts = [] + + if isinstance(message.content, str): + parts.append(types.Part.from_text(text=message.content)) + elif isinstance(message.content, list): + for content in message.content: + if content.type == 'text': + parts.append(types.Part.from_text(text=content.text)) + # elif content.type == 'image_url': + # parts.append(types.Part.from_image_url(url=content.image_url)) + + if role == 'system': + system_content = parts + else: + content = types.Content(role=role, parts=parts) + contents.append(content) + + response = self.client.models.generate_content( + model=model.model_entity.name, + contents=contents, + config=types.GenerateContentConfig( + system_instruction=system_content, + **extra_args, + ), + ) + + return llm_entities.Message( + role='assistant', + content=response.candidates[0].content.parts[0].text, + ) + + except Exception as e: + error_message = str(e).lower() + if 'invalid api key' in error_message: + raise errors.RequesterError(f'无效的 API 密钥: {str(e)}') + elif 'not found' in error_message: + raise errors.RequesterError(f'请求路径错误或模型无效: {str(e)}') + elif any(keyword in error_message for keyword in ['rate limit', 'quota', 'permission denied']): + raise errors.RequesterError(f'请求过于频繁或余额不足: {str(e)}') + elif 'timeout' in error_message: + raise errors.RequesterError(f'请求超时: {str(e)}') + else: + raise errors.RequesterError(f'Gemini API 请求错误: {str(e)}') diff --git a/pkg/provider/modelmgr/requesters/geminichatcmpl.yaml b/pkg/provider/modelmgr/requesters/geminichatcmpl.yaml new file mode 100644 index 00000000..db83b8dd --- /dev/null +++ b/pkg/provider/modelmgr/requesters/geminichatcmpl.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: LLMAPIRequester +metadata: + name: gemini-chat-completions + label: + en_US: Google Gemini + zh_CN: Google Gemini + icon: gemini.svg +spec: + config: + - name: base_url + label: + en_US: Base URL + zh_CN: 基础 URL + type: string + required: true + default: "https://generativelanguage.googleapis.com" + - name: timeout + label: + en_US: Timeout + zh_CN: 超时时间 + type: integer + required: true + default: 120 +execution: + python: + path: ./geminichatcmpl.py + attr: GeminiChatCompletions diff --git a/requirements.txt b/requirements.txt index dafa6985..752ee50d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,9 +37,11 @@ mcp sqlmodel slack_sdk telegramify-markdown +google-genai +google-generativeai # indirect taskgroup==0.0.0a4 ruff pre-commit -python-socks \ No newline at end of file +python-socks