From 80c46c063949587e952b575f95e49099e207b843 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Mon, 23 Mar 2026 12:53:00 +0800 Subject: [PATCH] feat: add openai-agents SDK integration Add integration layer for using the openai-agents library with AstrBot's existing agent infrastructure: - OpenAIAgentsRunner: A BaseAgentRunner implementation that wraps the openai-agents Agent class - Tool adapter to convert AstrBot FunctionTool to openai-agents format - Support for tool handlers and FunctionToolManager integration --- .../agent/runners/openai_agents/__init__.py | 21 +++ .../agent/runners/openai_agents/runner.py | 147 ++++++++++++++++++ .../runners/openai_agents/tool_adapter.py | 59 +++++++ 3 files changed, 227 insertions(+) create mode 100644 astrbot/core/agent/runners/openai_agents/__init__.py create mode 100644 astrbot/core/agent/runners/openai_agents/runner.py create mode 100644 astrbot/core/agent/runners/openai_agents/tool_adapter.py diff --git a/astrbot/core/agent/runners/openai_agents/__init__.py b/astrbot/core/agent/runners/openai_agents/__init__.py new file mode 100644 index 000000000..5a8f960ae --- /dev/null +++ b/astrbot/core/agent/runners/openai_agents/__init__.py @@ -0,0 +1,21 @@ +"""OpenAI Agents SDK integration for AstrBot. + +This module provides integration with the openai-agents library, +allowing AstrBot to leverage the openai-agents Agent implementation +while using its existing tool and provider infrastructure. + +Usage: + from astrbot.core.agent.runners.openai_agents import OpenAIAgentsRunner + + runner = OpenAIAgentsRunner(agent_config) + async for response in runner.run(): + print(response) +""" + +from .runner import OpenAIAgentsRunner +from .tool_adapter import astrbot_tool_to_agents_tool + +__all__ = [ + "OpenAIAgentsRunner", + "astrbot_tool_to_agents_tool", +] diff --git a/astrbot/core/agent/runners/openai_agents/runner.py b/astrbot/core/agent/runners/openai_agents/runner.py new file mode 100644 index 000000000..ef4cdad22 --- /dev/null +++ b/astrbot/core/agent/runners/openai_agents/runner.py @@ -0,0 +1,147 @@ +"""OpenAI Agents SDK Runner for AstrBot. + +This module provides an integration layer between AstrBot's agent system +and the openai-agents library from OpenAI. +""" + +from __future__ import annotations + +from typing import Any + +from agents import Agent as OpenAIAgent +from agents import Model, Runner, RunResult +from agents.run_config import RunConfig + +from astrbot.core.agent.hooks import BaseAgentRunHooks +from astrbot.core.agent.run_context import ContextWrapper, TContext +from astrbot.core.agent.runners.base import AgentState, BaseAgentRunner +from astrbot.core.agent.runners.openai_agents.tool_adapter import ( + astrbot_tool_to_agents_tool, +) +from astrbot.core.agent.tool import FunctionTool +from astrbot.core.provider.entities import LLMResponse +from astrbot.core.provider.func_tool_manager import FunctionToolManager + + +class OpenAIAgentsRunner(BaseAgentRunner[TContext]): + """An agent runner that uses the openai-agents library. + + This runner wraps the openai-agents Agent and integrates it with + AstrBot's existing infrastructure including tools, hooks, and context. + """ + + def __init__( + self, + model: Model | str, + instructions: str | None = None, + tools: list[FunctionTool] | None = None, + tool_manager: FunctionToolManager | None = None, + agent_name: str = "AstrBotAgent", + **openai_agents_kwargs: Any, + ) -> None: + """Initialize the openai-agents runner. + + Args: + model: The model to use (either a Model instance or a model name string) + instructions: System prompt/instructions for the agent + tools: List of AstrBot FunctionTool instances + tool_manager: Optional FunctionToolManager for tool resolution + agent_name: Name of the agent + **openai_agents_kwargs: Additional arguments passed to openai-agents Agent + """ + super().__init__() + self._model = model + self._instructions = instructions + self._tools = tools or [] + self._tool_manager = tool_manager + self._agent_name = agent_name + self._openai_agents_kwargs = openai_agents_kwargs + + self._agent: OpenAIAgent[Any] | None = None + self._run_result: RunResult | None = None + self._run_context: ContextWrapper[TContext] | None = None + self._agent_hooks: BaseAgentRunHooks[TContext] | None = None + self._state = AgentState.IDLE + + def _create_agents_tools(self) -> list[Any]: + """Convert AstrBot tools to openai-agents tools.""" + agents_tools = [] + for tool in self._tools: + handler = self._get_tool_handler(tool.name) + agents_tool = astrbot_tool_to_agents_tool(tool, handler) + agents_tools.append(agents_tool) + return agents_tools + + def _get_tool_handler(self, tool_name: str) -> Any: + """Get the handler function for a tool by name.""" + if self._tool_manager: + tool = self._tool_manager.get_func(tool_name) + if tool and tool.handler: + return tool.handler + for tool in self._tools: + if tool.name == tool_name and hasattr(tool, "handler"): + return tool.handler + raise ValueError(f"Tool handler not found for: {tool_name}") + + async def reset( + self, + run_context: ContextWrapper[TContext], + agent_hooks: BaseAgentRunHooks[TContext], + **kwargs: Any, + ) -> None: + """Reset the agent to its initial state.""" + self._run_context = run_context + self._agent_hooks = agent_hooks + self._state = AgentState.IDLE + self._run_result = None + + agents_tools = self._create_agents_tools() + + self._agent = OpenAIAgent( + name=self._agent_name, + instructions=self._instructions, + tools=agents_tools, + **self._openai_agents_kwargs, + ) + + async def step(self) -> Any: + """Process a single step of the agent (not directly supported).""" + raise NotImplementedError( + "step() is not directly supported. Use step_until_done() instead." + ) + + async def step_until_done(self, max_step: int = 50) -> Any: + """Run the agent until completion or max steps.""" + if not self._agent or not self._run_context: + raise RuntimeError("Agent not initialized. Call reset() first.") + + self._state = AgentState.RUNNING + + run_config = RunConfig(max_turns=max_step) + + try: + self._run_result = await Runner.run( + self._agent, + input=self._run_context.messages, + run_config=run_config, + ) + self._state = AgentState.DONE + except Exception: + self._state = AgentState.ERROR + raise + + return self._run_result + + def done(self) -> bool: + """Check if the agent has completed its task.""" + return self._state == AgentState.DONE + + def get_final_llm_resp(self) -> LLMResponse | None: + """Get the final LLM response from the agent run.""" + if not self._run_result: + return None + return LLMResponse( + content=self._run_result.final_output, + model=self._run_result.model, + usage=self._run_result.usage, + ) diff --git a/astrbot/core/agent/runners/openai_agents/tool_adapter.py b/astrbot/core/agent/runners/openai_agents/tool_adapter.py new file mode 100644 index 000000000..3d9ad927a --- /dev/null +++ b/astrbot/core/agent/runners/openai_agents/tool_adapter.py @@ -0,0 +1,59 @@ +"""Adapter to convert AstrBot FunctionTool to openai-agents FunctionTool. + +This module provides utilities to convert between AstrBot's tool representation +and the openai-agents library's FunctionTool format. +""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from typing import Any + +from agents import FunctionTool as AgentsFunctionTool +from agents.tool import ToolContext + +from astrbot.core.agent.tool import FunctionTool + + +def create_agents_tool( + name: str, + description: str, + params_json_schema: dict[str, Any], + on_invoke_tool: Callable[[ToolContext[Any], str], Awaitable[Any]], +) -> AgentsFunctionTool: + """Create an openai-agents FunctionTool from a handler function. + + This is a helper to create tools that integrate with the openai-agents SDK. + """ + return AgentsFunctionTool( + name=name, + description=description, + params_json_schema=params_json_schema, + on_invoke_tool=on_invoke_tool, + ) + + +def astrbot_tool_to_agents_tool( + tool: FunctionTool, + handler: Callable[..., Awaitable[Any]], +) -> AgentsFunctionTool: + """Convert an AstrBot FunctionTool to an openai-agents FunctionTool. + + Args: + tool: The AstrBot FunctionTool to convert + handler: The async function to call when the tool is invoked. + Should have the signature: async def handler(tool_context, tool_name) -> Any + + Returns: + An openai-agents FunctionTool that wraps the AstrBot tool + """ + + async def wrapper(tool_context: ToolContext[Any], tool_name: str) -> Any: + return await handler(tool.context, tool.name) + + return AgentsFunctionTool( + name=tool.name, + description=tool.desc, + params_json_schema=tool.parameters, + on_invoke_tool=wrapper, + )