-
-
Notifications
You must be signed in to change notification settings - Fork 922
Add Google Agent SDK Support #1639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
dfdb2ce
07f5db9
64eb51c
0534daa
b35d800
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,123 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import asyncio | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from typing import Any, AsyncGenerator, List | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from astrbot.api.provider import Personality, Provider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from astrbot.core.db import BaseDatabase | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from astrbot.core.message.message_event_result import MessageChain | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from astrbot.core.provider.entities import LLMResponse, ToolCallsResult | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from astrbot.core.provider.func_tool_manager import FuncCall | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from astrbot.core import logger | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from ..register import register_provider_adapter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from google.agents import Agent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception: # pragma: no cover - optional dependency | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Agent = None # type: ignore | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@register_provider_adapter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"google_agent_sdk", "Google Agent SDK 提供商适配器" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class ProviderGoogleAgentSDK(Provider): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Provider adapter using Google Agent SDK. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
This is a lightweight integration that forwards prompts to a Google Agent. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
If the optional dependency is missing, initialization fails. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def __init__( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
provider_config: dict, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
provider_settings: dict, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
db_helper: BaseDatabase, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
persistant_history: bool = True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default_persona: Personality | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
super().__init__( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
provider_config, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
provider_settings, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
persistant_history, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
db_helper, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default_persona, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if Agent is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
raise ImportError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"google-agents SDK is required for google_agent_sdk provider" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.api_key: str | None = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
keys = provider_config.get("key", []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if keys: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.api_key = keys[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.set_model(provider_config.get("model_config", {}).get("model", "")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Actual Agent initialization; parameters may vary based on SDK version. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# TODO: pass additional configuration such as tools when needed. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.agent = Agent(api_key=self.api_key, model=self.get_model()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def get_current_key(self) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return self.api_key or "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def set_key(self, key: str) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.api_key = key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# The Agent instance might need reconfiguration with new key. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.agent.api_key = key # type: ignore[attr-defined] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception: # pragma: no cover - best effort | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def get_models(self) -> List[str]: # pragma: no cover - simple return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return [self.get_model()] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async def text_chat( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prompt: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
session_id: str | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
image_urls: List[str] | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func_tool: FuncCall | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
contexts: List[dict] | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
system_prompt: str | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tool_calls_result: ToolCallsResult | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
**kwargs: Any, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) -> LLMResponse: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Return chat completion via Google Agent SDK.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if image_urls: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.warning("google_agent_sdk provider does not support images yet") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
history = contexts or [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if system_prompt: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
history = [{"role": "system", "content": system_prompt}, *history] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# TODO: handle func_tool and tool_calls_result via ADK Tool API | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
response = await self.agent.chat(prompt, history=history) # type: ignore[attr-defined] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+88
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 建议: 警告何时提供 func_tool 或 tool_calls_result 如果提供了 func_tool 或 tool_calls_result,请考虑添加警告或异常,因为它们目前被忽略。
Suggested change
Original comment in Englishsuggestion: Warn when func_tool or tool_calls_result is provided Consider adding a warning or exception if func_tool or tool_calls_result is provided, since they are currently ignored.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception as e: # pragma: no cover - runtime errors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
raise Exception(f"Google Agent SDK error: {e}") from e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 问题(代码质量): 引发特定错误,而不是一般的 解释如果一段代码引发特定异常类型,而不是通用的 [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) 或 [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),则调用代码可以:
这样,代码的调用者可以适当地处理错误。 您如何解决这个问题? 因此,与其让代码引发 if incorrect_input(value):
raise Exception("输入不正确") 您可以让代码引发特定错误,例如 if incorrect_input(value):
raise ValueError("输入不正确") 或者 class IncorrectInputError(Exception):
pass
if incorrect_input(value):
raise IncorrectInputError("输入不正确") Original comment in Englishissue (code-quality): Raise a specific error instead of the general ExplanationIf a piece of code raises a specific exception type rather than the generic [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception), the calling code can:
This way, callers of the code can handle the error appropriately. How can you solve this?
So instead of having code raising if incorrect_input(value):
raise Exception("The input is incorrect") you can have code raising a specific error like if incorrect_input(value):
raise ValueError("The input is incorrect") or class IncorrectInputError(Exception):
pass
if incorrect_input(value):
raise IncorrectInputError("The input is incorrect") |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
llm_response = LLMResponse("assistant") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
llm_response.result_chain = MessageChain().message(str(response)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
llm_response.raw_completion = response | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return llm_response | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async def text_chat_stream( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prompt: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
session_id: str | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
image_urls: List[str] | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func_tool: FuncCall | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
contexts: List[dict] | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
system_prompt: str | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tool_calls_result: ToolCallsResult | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
**kwargs: Any, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) -> AsyncGenerator[LLMResponse, None]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Stream chat completions via Google Agent SDK.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
llm_response = await self.text_chat( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prompt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
session_id=session_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
image_urls=image_urls, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func_tool=func_tool, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
contexts=contexts, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
system_prompt=system_prompt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tool_calls_result=tool_calls_result, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
**kwargs, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+113
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 问题(代码质量): 内联立即产生的变量 ( Original comment in Englishissue (code-quality): Inline variable that is immediately yielded ( |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
yield llm_response | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 建议: 实施真正的流式传输或调整方法命名 由于 text_chat_stream 一次性产生完整响应,请实施分块流式传输或更新方法名称/文档,以明确不支持实时流式传输。 建议的实施方式: async def text_chat_full_response(self, prompt, session_id=None, image_urls=None, func_tool=None, contexts=None, system_prompt=None, tool_calls_result=None, **kwargs):
"""
一次性返回完整的 LLM 响应。此方法不提供实时或分块流式传输。
"""
llm_response = await self.text_chat(
prompt,
session_id=session_id,
image_urls=image_urls,
func_tool=func_tool,
contexts=contexts,
system_prompt=system_prompt,
tool_calls_result=tool_calls_result,
**kwargs,
)
yield llm_response
Original comment in Englishsuggestion: Implement true streaming or adjust method naming Since text_chat_stream yields the full response at once, please either implement chunked streaming or update the method name/documentation to clarify that real-time streaming is not supported. Suggested implementation: async def text_chat_full_response(self, prompt, session_id=None, image_urls=None, func_tool=None, contexts=None, system_prompt=None, tool_calls_result=None, **kwargs):
"""
Returns the full LLM response at once. This method does not provide real-time or chunked streaming.
"""
llm_response = await self.text_chat(
prompt,
session_id=session_id,
image_urls=image_urls,
func_tool=func_tool,
contexts=contexts,
system_prompt=system_prompt,
tool_calls_result=tool_calls_result,
**kwargs,
)
yield llm_response
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
建议(代码质量): 使用命名表达式来简化赋值和条件 (
use-named-expression
)Original comment in English
suggestion (code-quality): Use named expression to simplify assignment and conditional (
use-named-expression
)