├── oxygent ├── utils │ └── __init__.py ├── web │ ├── css │ │ ├── message.css │ │ ├── gantt.css │ │ ├── style_load1.css │ │ ├── style_load2.css │ │ └── select.css │ ├── image │ │ ├── icon.png │ │ ├── logo.ico │ │ ├── logo.png │ │ ├── tool.png │ │ ├── right-user.png │ │ ├── group-favicon.png │ │ ├── agents │ │ │ ├── agent_0.png │ │ │ ├── agent_1.png │ │ │ ├── agent_10.png │ │ │ ├── agent_11.png │ │ │ ├── agent_12.png │ │ │ ├── agent_13.png │ │ │ ├── agent_14.png │ │ │ ├── agent_15.png │ │ │ ├── agent_16.png │ │ │ ├── agent_17.png │ │ │ ├── agent_2.png │ │ │ ├── agent_3.png │ │ │ ├── agent_4.png │ │ │ ├── agent_5.png │ │ │ ├── agent_6.png │ │ │ ├── agent_7.png │ │ │ ├── agent_8.png │ │ │ └── agent_9.png │ │ ├── group_add.svg │ │ ├── arrow-left.svg │ │ ├── arrow-right.svg │ │ ├── arrow-top.svg │ │ ├── group-up.svg │ │ ├── up-icon.svg │ │ ├── arrow-bottom.svg │ │ ├── group-down.svg │ │ ├── group_delete.svg │ │ ├── group_close.svg │ │ ├── group_export.svg │ │ ├── up_arrow.svg │ │ ├── group_recent.svg │ │ ├── pause.svg │ │ ├── group_save.svg │ │ ├── refresh.svg │ │ ├── stop-circle.svg │ │ ├── arrow-left-double.svg │ │ ├── arrow-right-double.svg │ │ ├── group_open.svg │ │ ├── play.svg │ │ ├── tool-icon.svg │ │ ├── global-language.svg │ │ ├── group.svg │ │ ├── group-vector-active.svg │ │ ├── group-vector-default.svg │ │ ├── output_icon.svg │ │ ├── org_tool.svg │ │ ├── org_flow.svg │ │ ├── right-user.svg │ │ ├── llm-icon.svg │ │ ├── user.svg │ │ ├── org_model.svg │ │ ├── video.svg │ │ ├── chat-at-list-bg.svg │ │ └── global-theme.svg │ └── js │ │ ├── upload.js │ │ └── message.js ├── core_tools │ └── __init__.py ├── databases │ ├── __init__.py │ ├── db_es │ │ └── __init__.py │ ├── db_vector │ │ ├── __init__.py │ │ └── base_vector_db.py │ └── db_redis │ │ └── __init__.py ├── oxy │ ├── api_tools │ │ ├── __init__.py │ │ └── http_tool.py │ ├── function_tools │ │ └── __init__.py │ ├── llms │ │ ├── __init__.py │ │ └── mock_llm.py │ ├── flows │ │ ├── __init__.py │ │ ├── workflow.py │ │ └── parallel_flow.py │ ├── mcp_tools │ │ ├── __init__.py │ │ └── mcp_tool.py │ ├── agents │ │ ├── __init__.py │ │ ├── workflow_agent.py │ │ ├── remote_agent.py │ │ └── rag_agent.py │ ├── __init__.py │ ├── base_flow.py │ └── base_tool.py ├── schemas │ ├── web.py │ ├── color.py │ ├── message.py │ ├── llm.py │ ├── __init__.py │ └── observation.py ├── __init__.py ├── preset_tools │ ├── image_gen_tools.py │ ├── baidu_search_tools.py │ ├── shell_tools.py │ └── python_tools.py ├── chart │ ├── web │ │ ├── index.html │ │ ├── css │ │ │ └── style.css │ │ └── js │ │ │ └── app.js │ └── open_chart_tools.py └── db_factory.py ├── pytest.ini ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── xug_other.md │ ├── suggestion.md │ ├── feature_request.md │ └── bug_report.md ├── workflows │ └── ci.yml └── pull_request_template.md ├── examples ├── shortest_path │ ├── usnet.xlsx │ └── shortest_path_demo.py ├── llms │ ├── ollama_demo.py │ ├── demo_disable_system_prompt.py │ └── demo_reset_llm_params.py ├── agents │ ├── demo_chat_agent_stream.py │ ├── demo_document_analysis_agent.py │ ├── demo_react_agent.py │ ├── demo_rag_agent.py │ ├── demo_single_agent.py │ ├── demo_heterogeneous_agents.py │ └── demo_hierarchical_agents.py ├── backend │ ├── demo_config.py │ ├── demo_batch_and_semaphore.py │ ├── demo_logger_setup.py │ ├── demo_attachment.py │ ├── demo_process_message.py │ ├── demo_mas_hook.py │ ├── demo_human_in_the_loop.py │ ├── demo_add_router.py │ ├── demo_custom_shared_data_schema.py │ ├── demo_save_message.py │ └── demo_global_data.py ├── distributed │ ├── app_time_agent.py │ └── app_master_agent.py ├── ecommerce │ ├── app_payment_service.py │ ├── app_product_service.py │ ├── app_logistics_service.py │ └── app_order_service.py ├── advanced │ ├── demo_multimodal_transfer.py │ ├── demo_multimodal.py │ ├── demo_custom_agent_input_schema.py │ ├── demo_trust_mode.py │ ├── demo_send_message_from_tool.py │ └── demo_continue_exec.py ├── tools │ ├── demo_functionhub.py │ └── demo_mcp.py ├── start_distributed.sh ├── start_ecommerce.sh └── mcp_tools │ └── train_ticket_agent_demo.py ├── mcp_servers ├── kubernetes_mcp_server │ ├── requirements.txt │ ├── core_tools │ │ └── __init__.py │ ├── .env.example │ └── __init__.py ├── browser │ ├── utils.py │ └── __init__.py ├── _mcp_testing_utilities │ ├── mcp_client_sse.py │ ├── mcp_client_streamable.py │ └── mcp_server_show_headers.py ├── math_tools.py ├── math_tools_sse.py └── math_tools_streamable.py ├── docs_building └── docs_ch │ └── examples │ └── tts │ └── demo.gif ├── SECURITY.md ├── docs ├── docs_zh │ ├── 6_1_moa.md │ ├── 2_2_manage_tools.md │ ├── 0_1_demo.md │ ├── 0_install.md │ ├── readme.md │ ├── 2_3_use_opensource_tools.md │ └── 3_2_set_global.md └── development │ └── api │ ├── db_factory.md │ ├── llms │ ├── http_llm.md │ ├── openai_llm.md │ └── remote_llm.md │ ├── flows │ └── parallel_flow.md │ ├── api_tools │ └── http_tool.md │ ├── oxy_factory.md │ ├── tools │ ├── streamable_mcp_client.md │ ├── base_tools.md │ ├── base_mcp_client.md │ └── mcp_tool.md │ ├── databases │ └── db_vector │ │ └── base_vector_db.md │ └── function_tools │ └── function_tool.md ├── requirements.txt ├── test ├── unittest │ ├── test_observation.py │ ├── test_tool │ │ ├── test_python_tools.py │ │ └── test_train_ticket_tools.py │ ├── skip_test_code_interpreter_tools.py │ ├── test_llm_pydantic_parser.py │ ├── test_common_utils.py │ ├── test_base_flow.py │ └── test_workflow.py └── integration │ └── test_continue.py ├── .gitattributes ├── demo.py └── CONTRIBUTING_zh.md /oxygent/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/css/message.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/core_tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/databases/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | pythonpath = . -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /oxygent/web/image/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/icon.png -------------------------------------------------------------------------------- /oxygent/web/image/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/logo.ico -------------------------------------------------------------------------------- /oxygent/web/image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/logo.png -------------------------------------------------------------------------------- /oxygent/web/image/tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/tool.png -------------------------------------------------------------------------------- /oxygent/oxy/api_tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .http_tool import HttpTool 2 | 3 | __all__ = [ 4 | "HttpTool", 5 | ] 6 | -------------------------------------------------------------------------------- /examples/shortest_path/usnet.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/examples/shortest_path/usnet.xlsx -------------------------------------------------------------------------------- /oxygent/web/image/right-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/right-user.png -------------------------------------------------------------------------------- /oxygent/web/image/group-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/group-favicon.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_0.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_1.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_10.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_11.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_12.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_13.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_14.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_15.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_16.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_17.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_2.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_3.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_4.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_5.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_6.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_7.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_8.png -------------------------------------------------------------------------------- /oxygent/web/image/agents/agent_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/oxygent/web/image/agents/agent_9.png -------------------------------------------------------------------------------- /mcp_servers/kubernetes_mcp_server/requirements.txt: -------------------------------------------------------------------------------- 1 | -r ../../requirements.txt 2 | kubernetes==28.1.0 3 | PyYAML==6.0.2 4 | Jinja2==3.1.4 -------------------------------------------------------------------------------- /docs_building/docs_ch/examples/tts/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jd-opensource/OxyGent/HEAD/docs_building/docs_ch/examples/tts/demo.gif -------------------------------------------------------------------------------- /oxygent/databases/db_es/__init__.py: -------------------------------------------------------------------------------- 1 | from .jes_es import JesEs 2 | from .local_es import LocalEs 3 | 4 | __all__ = [ 5 | "JesEs", 6 | "LocalEs", 7 | ] 8 | -------------------------------------------------------------------------------- /oxygent/databases/db_vector/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_vector_db import BaseVectorDB 2 | from .vearch_db import VearchDB 3 | 4 | __all__ = [ 5 | "BaseVectorDB", 6 | "VearchDB", 7 | ] 8 | -------------------------------------------------------------------------------- /oxygent/oxy/function_tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .function_hub import FunctionHub 2 | from .function_tool import FunctionTool 3 | 4 | __all__ = [ 5 | "FunctionHub", 6 | "FunctionTool", 7 | ] 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/xug_other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Other Issues" 3 | about: Other issues 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Issue content 11 | > Please add your issue here. -------------------------------------------------------------------------------- /oxygent/oxy/llms/__init__.py: -------------------------------------------------------------------------------- 1 | from .http_llm import HttpLLM 2 | from .mock_llm import MockLLM 3 | from .openai_llm import OpenAILLM 4 | 5 | __all__ = [ 6 | "HttpLLM", 7 | "OpenAILLM", 8 | "MockLLM", 9 | ] 10 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## Supported Versions 4 | 5 | Check our version at [Pypi](https://pypi.org/project/oxygent/): 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.0.2 | :white_check_mark: | 10 | | 1.0.1 | :white_check_mark: | 11 | 12 | -------------------------------------------------------------------------------- /oxygent/oxy/flows/__init__.py: -------------------------------------------------------------------------------- 1 | from .parallel_flow import ParallelFlow 2 | from .plan_and_solve import PlanAndSolve 3 | from .reflexion import MathReflexion, Reflexion 4 | from .workflow import Workflow 5 | 6 | __all__ = ["Workflow", "ParallelFlow", "PlanAndSolve", "Reflexion", "MathReflexion"] 7 | -------------------------------------------------------------------------------- /oxygent/schemas/web.py: -------------------------------------------------------------------------------- 1 | """Web response in base model.""" 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class WebResponse(BaseModel): 7 | code: int = Field(200) 8 | message: str = Field("SUCCESS") 9 | data: dict = Field(default_factory=dict) 10 | 11 | def to_dict(self): 12 | return self.model_dump() 13 | -------------------------------------------------------------------------------- /oxygent/oxy/mcp_tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .mcp_tool import MCPTool 2 | from .sse_mcp_client import SSEMCPClient 3 | from .stdio_mcp_client import StdioMCPClient 4 | from .streamable_mcp_client import StreamableMCPClient 5 | 6 | __all__ = [ 7 | "MCPTool", 8 | "StdioMCPClient", 9 | "SSEMCPClient", 10 | "StreamableMCPClient", 11 | ] 12 | -------------------------------------------------------------------------------- /oxygent/databases/db_redis/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info < (3, 11): 4 | from .jimdb_ap_redis import JimdbApRedis 5 | else: 6 | JimdbApRedis = None 7 | 8 | from .base_redis import BaseRedis 9 | from .local_redis import LocalRedis 10 | 11 | __all__ = [ 12 | "JimdbApRedis", 13 | "BaseRedis", 14 | "LocalRedis", 15 | ] 16 | -------------------------------------------------------------------------------- /mcp_servers/browser/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | 浏览器工具辅助函数 3 | 4 | 提供状态检查和通用工具函数 5 | """ 6 | 7 | from urllib.parse import urlparse 8 | 9 | 10 | async def _get_domain_from_url(url): 11 | """从URL中提取域名""" 12 | try: 13 | parsed_url = urlparse(url) 14 | domain = parsed_url.netloc.lower() 15 | return domain 16 | except: 17 | return "" 18 | 19 | 20 | # 函数已移至core.py文件 21 | -------------------------------------------------------------------------------- /oxygent/schemas/color.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | """ 4 | Color enum module 5 | 6 | The Color enum is used to represent the color on terminal. 7 | """ 8 | 9 | 10 | class Color(Enum): 11 | DEFAULT = auto() 12 | BLACK = auto() 13 | RED = auto() 14 | GREEN = auto() 15 | YELLOW = auto() 16 | BLUE = auto() 17 | MAGENTA = auto() 18 | CYAN = auto() 19 | WHITE = auto() 20 | -------------------------------------------------------------------------------- /oxygent/oxy/agents/__init__.py: -------------------------------------------------------------------------------- 1 | from .chat_agent import ChatAgent 2 | from .parallel_agent import ParallelAgent 3 | from .rag_agent import RAGAgent 4 | from .react_agent import ReActAgent 5 | from .sse_oxy_agent import SSEOxyGent 6 | from .workflow_agent import WorkflowAgent 7 | 8 | __all__ = [ 9 | "ChatAgent", 10 | "RAGAgent", 11 | "ReActAgent", 12 | "WorkflowAgent", 13 | "ParallelAgent", 14 | "SSEOxyGent", 15 | ] 16 | -------------------------------------------------------------------------------- /oxygent/__init__.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | 3 | from .config import Config 4 | from .mas import MAS 5 | from .oxy import Oxy 6 | from .oxy_factory import OxyFactory 7 | from .schemas import OxyOutput, OxyRequest, OxyResponse, OxyState 8 | 9 | load_dotenv(".env") 10 | 11 | __all__ = [ 12 | "Oxy", 13 | "MAS", 14 | "OxyState", 15 | "OxyRequest", 16 | "OxyOutput", 17 | "OxyResponse", 18 | "OxyFactory", 19 | "Config", 20 | ] 21 | -------------------------------------------------------------------------------- /docs/docs_zh/6_1_moa.md: -------------------------------------------------------------------------------- 1 | # 如何快速复制多个智能体? 2 | 3 | 如果您需要生成多个相同的智能体,您可以使用`team_size`参数快速复制智能体。 4 | 5 | ``` 6 | oxy.ReActAgent( 7 | name="time_agent", 8 | desc="A tool for time query", 9 | tools=["time_tools"], 10 | llm_model="default_llm", 11 | team_size=2, 12 | ), 13 | ``` 14 | 15 | `team_size`目前仅能复制较为简单的智能体,之后我们会支持更完备的智能体复制。 16 | 17 | [上一章:创建简单的多agent系统](./6_register_multi_agent.md) 18 | [下一章:并行调用agent](./7_parallel.md) 19 | [回到首页](./readme.md) -------------------------------------------------------------------------------- /oxygent/schemas/message.py: -------------------------------------------------------------------------------- 1 | """SSE message in base model.""" 2 | 3 | from typing import Any 4 | 5 | from pydantic import BaseModel, Field 6 | 7 | from ..utils.common_utils import generate_uuid, to_json 8 | 9 | 10 | class SSEMessage(BaseModel): 11 | id: str = Field(default_factory=generate_uuid) 12 | event: str = Field("message") 13 | data: Any = Field("") 14 | 15 | def to_sse(self): 16 | return {"id": self.id, "event": self.event, "data": to_json(self.data)} 17 | -------------------------------------------------------------------------------- /mcp_servers/kubernetes_mcp_server/core_tools/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Kubernetes MCP Server - core tools package 3 | 4 | 按功能拆分的核心工具集合: 5 | - pods.py:Pods 相关 list/get/logs 等只读与基础能力 6 | - resources.py:通用资源 list/get/create_or_update/delete 7 | - events.py:事件列表 8 | - namespaces.py:命名空间列表 9 | - nodes.py:节点 top/stats/logs 10 | 11 | 说明: 12 | - 具体工具在子模块中通过 `from .. import mcp` 注册到全局 MCP 实例。 13 | - 服务器入口会按 `--toolsets` 与安全开关(只读/禁破坏)选择性导入子模块。 14 | """ 15 | 16 | from __future__ import annotations 17 | 18 | __all__ = [] 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Suggestion" 3 | about: Other suggestions for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe your suggestion or comments here** 11 | > A clear and concise description of what your suggestion or comment is. 12 | 13 | **Describe factors or reasons you've considered** 14 | > For ugent or essential suggestions please add your consideration here. 15 | 16 | **Additional context** 17 | > Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /oxygent/schemas/llm.py: -------------------------------------------------------------------------------- 1 | """llm.py LLM status module. 2 | 3 | The module difines the status and the output of the LLM. 4 | """ 5 | 6 | from enum import Enum 7 | from typing import Union 8 | 9 | from pydantic import BaseModel, Field 10 | 11 | 12 | class LLMState(Enum): 13 | TOOL_CALL = "tool_call" 14 | ANSWER = "answer" 15 | ERROR_PARSE = "error_parse" 16 | ERROR_CALL = "error_call" 17 | 18 | 19 | class LLMResponse(BaseModel): 20 | state: LLMState 21 | output: Union[str, list, dict] 22 | ori_response: str = Field(default="") 23 | -------------------------------------------------------------------------------- /oxygent/web/image/group_add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/preset_tools/image_gen_tools.py: -------------------------------------------------------------------------------- 1 | from oxygent.oxy import FunctionHub 2 | 3 | image_gen_tools = FunctionHub(name="image_gen_tools") 4 | 5 | 6 | @image_gen_tools.tool( 7 | description="An image generation service that takes text descriptions as input and returns a URL of the image,text " 8 | "descriptions only accept English, so you need to translate the description into English in advance." 9 | ) 10 | def gen_image(description: str) -> str: 11 | """ 12 | Image generation method, returns image URL 13 | """ 14 | return f'https://image.pollinations.ai/prompt/{description}?nologo=true' 15 | -------------------------------------------------------------------------------- /oxygent/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | from .color import Color 2 | from .llm import LLMResponse, LLMState 3 | from .memory import Memory, Message 4 | from .message import SSEMessage 5 | from .observation import ExecResult, Observation 6 | from .oxy import OxyOutput, OxyRequest, OxyResponse, OxyState 7 | from .web import WebResponse 8 | 9 | __all__ = [ 10 | "Color", 11 | "LLMState", 12 | "LLMResponse", 13 | "Message", 14 | "Memory", 15 | "Observation", 16 | "ExecResult", 17 | "OxyState", 18 | "OxyRequest", 19 | "OxyResponse", 20 | "OxyOutput", 21 | "WebResponse", 22 | "SSEMessage", 23 | ] 24 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aioredis==2.0.1 2 | fastapi==0.115.12 3 | httpx==0.28.1 4 | mcp==1.12.3 5 | numpy==1.26.4 6 | openai==1.77.0 7 | pandas==2.2.3 8 | pydantic==2.11.4 9 | requests==2.32.5 10 | shortuuid==1.0.13 11 | tqdm==4.67.1 12 | uvicorn==0.34.2 13 | websockets==15.0.1 14 | aiofiles==24.1.0 15 | colorama==0.4.6 16 | uv==0.6.9 17 | elasticsearch[async]==7.13.0 18 | msgpack==1.1.0 19 | aiohttp==3.11.18 20 | aiohttp-sse-client==0.2.1 21 | python-multipart==0.0.20 22 | pillow==11.2.1 23 | respx==0.22.0 24 | aioresponses==0.7.8 25 | jupyter_client==8.6.3 26 | ipykernel==6.30.1 27 | pytesseract==0.3.13 28 | pytest==8.4.2 29 | pytest-asyncio==1.2.0 30 | -------------------------------------------------------------------------------- /examples/llms/ollama_demo.py: -------------------------------------------------------------------------------- 1 | """gemma_cli_demo.py""" 2 | 3 | import asyncio 4 | import os 5 | 6 | from oxygent import MAS, oxy 7 | 8 | oxy_space = [ 9 | oxy.HttpLLM( 10 | name="default_llm", 11 | base_url="http://localhost:11434/api/chat", 12 | model_name=os.getenv("DEFAULT_OLLAMA_MODEL"), 13 | ), 14 | ] 15 | 16 | 17 | async def main(): 18 | async with MAS(oxy_space=oxy_space) as mas: 19 | await mas.call( 20 | callee="default_llm", 21 | arguments={"messages": [{"role": "user", "content": "hello"}]}, 22 | ) 23 | 24 | 25 | if __name__ == "__main__": 26 | asyncio.run(main()) 27 | -------------------------------------------------------------------------------- /oxygent/web/image/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/js/upload.js: -------------------------------------------------------------------------------- 1 | function uploadFile(file, callback) { 2 | console.log(file); 3 | const formData = new FormData(); 4 | formData.append('file', file); 5 | 6 | fetch('../upload', { 7 | method: 'POST', 8 | body: formData 9 | }) 10 | .then(response => response.json()) 11 | .then(data => { 12 | callback(data) 13 | // document.getElementById('result').textContent = '上传成功: ' + JSON.stringify(data); 14 | }) 15 | .catch(error => { 16 | console.log(error); 17 | // document.getElementById('result').textContent = '上传失败: ' + error; 18 | }); 19 | } -------------------------------------------------------------------------------- /mcp_servers/_mcp_testing_utilities/mcp_client_sse.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from mcp import ClientSession 4 | from mcp.client.sse import sse_client 5 | 6 | url = "http://localhost:8000/sse" 7 | 8 | 9 | async def main(): 10 | async with sse_client(url) as streams: 11 | async with ClientSession(*streams) as session: 12 | await session.initialize() 13 | tool_list_result = await session.list_tools() 14 | print(tool_list_result) 15 | tool_call_result = await session.call_tool("calc_pi", {"prec": 20}) 16 | print(tool_call_result.content[0].text) 17 | 18 | 19 | if __name__ == "__main__": 20 | asyncio.run(main()) 21 | -------------------------------------------------------------------------------- /oxygent/databases/db_vector/base_vector_db.py: -------------------------------------------------------------------------------- 1 | """base_vector_db.py Base Vector Database Class Module. 2 | 3 | This file defines the abstract base class for vector database services, inheriting from 4 | BaseDB and providing the interface contract for Redis operations. 5 | """ 6 | 7 | import logging 8 | from abc import ABC, abstractmethod 9 | 10 | from oxygent.databases.base_db import BaseDB 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class BaseVectorDB(BaseDB, ABC): 16 | @abstractmethod 17 | async def create_space(self, index_name, body): 18 | pass 19 | 20 | @abstractmethod 21 | async def query_search(self, index_name, body): 22 | pass 23 | -------------------------------------------------------------------------------- /oxygent/web/image/arrow-top.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/group-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/up-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/arrow-bottom.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/group-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/group_delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature request" 3 | about: Suggest a feature for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | > A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | > A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | > A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | > Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /oxygent/web/image/group_close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/group_export.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mcp_servers/_mcp_testing_utilities/mcp_client_streamable.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from mcp import ClientSession 4 | from mcp.client.streamable_http import streamablehttp_client 5 | 6 | url = "http://localhost:8000/mcp" 7 | 8 | 9 | async def main(): 10 | async with streamablehttp_client(url) as (read, write, _): 11 | async with ClientSession(read, write) as session: 12 | await session.initialize() 13 | tool_list_result = await session.list_tools() 14 | print(tool_list_result) 15 | tool_call_result = await session.call_tool("calc_pi", {"prec": 20}) 16 | print(tool_call_result.content[0].text) 17 | 18 | 19 | if __name__ == "__main__": 20 | asyncio.run(main()) 21 | -------------------------------------------------------------------------------- /examples/llms/demo_disable_system_prompt.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, oxy 5 | 6 | oxy_space = [ 7 | oxy.HttpLLM( 8 | name="default_llm", 9 | api_key=os.getenv("CHATRHINO_750B_API_KEY"), 10 | base_url=os.getenv("CHATRHINO_750B_BASE_URL"), 11 | model_name=os.getenv("CHATRHINO_750B_MODEL_NAME"), 12 | is_disable_system_prompt=True, # 适配少部分不支持system prompt的模型 13 | ), 14 | oxy.ChatAgent( 15 | name="master_agent", 16 | llm_model="default_llm", 17 | ), 18 | ] 19 | 20 | 21 | async def main(): 22 | async with MAS(oxy_space=oxy_space) as mas: 23 | await mas.start_web_service(first_query="hello") 24 | 25 | 26 | if __name__ == "__main__": 27 | asyncio.run(main()) 28 | -------------------------------------------------------------------------------- /examples/agents/demo_chat_agent_stream.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, Config, oxy 5 | 6 | Config.set_message_is_show_in_terminal(True) 7 | 8 | oxy_space = [ 9 | oxy.HttpLLM( 10 | name="default_llm", 11 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 12 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 13 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 14 | llm_params={"stream": True}, 15 | ), 16 | oxy.ChatAgent( 17 | name="qa_agent", 18 | llm_model="default_llm", 19 | ), 20 | ] 21 | 22 | 23 | async def main(): 24 | async with MAS(oxy_space=oxy_space) as mas: 25 | await mas.start_web_service(first_query="hello") 26 | 27 | 28 | if __name__ == "__main__": 29 | asyncio.run(main()) 30 | -------------------------------------------------------------------------------- /examples/llms/demo_reset_llm_params.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, Config, oxy 5 | 6 | Config.set_llm_config({}) # 重置LLM默认参数,以适配GPT-5模型 7 | 8 | 9 | oxy_space = [ 10 | oxy.HttpLLM( 11 | name="default_llm", 12 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 13 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 14 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 15 | llm_params={"thinking": False, "stream": False}, 16 | ), 17 | ] 18 | 19 | 20 | async def main(): 21 | async with MAS(oxy_space=oxy_space) as mas: 22 | await mas.call( 23 | callee="default_llm", 24 | arguments={"messages": [{"role": "user", "content": "hello"}]}, 25 | ) 26 | 27 | 28 | if __name__ == "__main__": 29 | asyncio.run(main()) 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug report" 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | > A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | > Steps to reproduce the bug. 15 | 16 | **Screenshots** 17 | > If possible, please upload screenshots of the code, console, terminal, etc. to help us understand your problem. 18 | 19 | **Reproduce repository** 20 | > Please provide a condensed code repository and upload it to your own github to help us reproduce your problem. 21 | 22 | **Environment** 23 | - Oxygent version: 24 | - Build tools & version: 25 | - Other deployments: 26 | 27 | **Logs and Reports** 28 | > If possible, please upload files containing `/cache_dir` for detailed error logs. -------------------------------------------------------------------------------- /examples/backend/demo_config.py: -------------------------------------------------------------------------------- 1 | """Demo for using OxyGent with multiple LLMs and an agent.""" 2 | 3 | import asyncio 4 | import os 5 | 6 | from oxygent import MAS, Config, oxy 7 | 8 | Config.load_from_json("./config.json", env="default") 9 | Config.set_agent_llm_model("default_llm") 10 | 11 | oxy_space = [ 12 | oxy.HttpLLM( 13 | name="default_llm", 14 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 15 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 16 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 17 | ), 18 | oxy.ReActAgent( 19 | name="master_agent", 20 | ), 21 | ] 22 | 23 | 24 | async def main(): 25 | async with MAS(oxy_space=oxy_space) as mas: 26 | await mas.start_web_service(first_query="hello") 27 | 28 | 29 | if __name__ == "__main__": 30 | asyncio.run(main()) 31 | -------------------------------------------------------------------------------- /oxygent/web/image/up_arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/chart/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |正在生成流程图,请稍候...
'; 16 | 17 | // 发送请求到后端 18 | fetch('/api/generate', { 19 | method: 'POST', 20 | headers: { 21 | 'Content-Type': 'application/json', 22 | }, 23 | body: JSON.stringify({ description: description }), 24 | }) 25 | .then(response => response.json()) 26 | .then(data => { 27 | if (data.success) { 28 | resultContainer.innerHTML = ` 29 |流程图已生成!
30 |文件路径: ${data.file_path}
31 |正在打开浏览器...
32 | `; 33 | } else { 34 | resultContainer.innerHTML = `生成失败: ${data.error}
`; 35 | } 36 | }) 37 | .catch(error => { 38 | console.error('请求出错:', error); 39 | resultContainer.innerHTML = '请求出错,请查看控制台获取详细信息
'; 40 | }); 41 | }); 42 | }); -------------------------------------------------------------------------------- /examples/agents/demo_single_agent.py: -------------------------------------------------------------------------------- 1 | """Demo for using OxyGent with multiple LLMs and an agent.""" 2 | 3 | import asyncio 4 | import os 5 | 6 | from oxygent import MAS, Config, OxyRequest, OxyResponse, oxy 7 | 8 | Config.set_agent_short_memory_size(7) 9 | 10 | 11 | def update_query(oxy_request: OxyRequest) -> OxyRequest: 12 | query = oxy_request.get_query() 13 | oxy_request.set_query(query + " Please answer in detail.") 14 | return oxy_request 15 | 16 | 17 | def format_output(oxy_response: OxyResponse) -> OxyResponse: 18 | oxy_response.output = "Answer: " + oxy_response.output 19 | return oxy_response 20 | 21 | 22 | oxy_space = [ 23 | oxy.HttpLLM( 24 | name="default_llm", 25 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 26 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 27 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 28 | llm_params={"temperature": 0.01}, 29 | semaphore=4, 30 | timeout=300, 31 | retries=3, 32 | ), 33 | oxy.ChatAgent( 34 | name="master_agent", 35 | llm_model="default_llm", 36 | prompt="You are a helpful assistant.", 37 | func_process_input=update_query, 38 | func_format_output=format_output, 39 | ), 40 | ] 41 | 42 | 43 | async def main(): 44 | async with MAS(oxy_space=oxy_space) as mas: 45 | await mas.start_web_service( 46 | first_query="Hello", 47 | welcome_message="Hi, I’m OxyGent. How can I assist you?", 48 | ) 49 | 50 | 51 | if __name__ == "__main__": 52 | asyncio.run(main()) 53 | -------------------------------------------------------------------------------- /examples/advanced/demo_trust_mode.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, oxy 5 | 6 | oxy_space = [ 7 | oxy.HttpLLM( 8 | name="default_llm", 9 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 10 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 11 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 12 | ), 13 | oxy.StdioMCPClient( 14 | name="time_tools", 15 | params={ 16 | "command": "uvx", 17 | "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"], 18 | }, 19 | ), 20 | # normal mode ReActAgent 21 | oxy.ReActAgent( 22 | name="normal_agent", 23 | tools=["time_tools"], 24 | llm_model="default_llm", 25 | trust_mode=False, # disable trust mode 26 | ), 27 | # trust mode ReActAgent 28 | oxy.ReActAgent( 29 | name="trust_agent", 30 | tools=["time_tools"], 31 | llm_model="default_llm", 32 | trust_mode=True, # enable trust mode 33 | ), 34 | ] 35 | 36 | 37 | async def main(): 38 | async with MAS(oxy_space=oxy_space) as mas: 39 | query = "What is the current time" 40 | 41 | print("=== normal mode test ===") 42 | normal_result = await mas.call("normal_agent", {"query": query}) 43 | print(f"normal mode output: {normal_result}") 44 | 45 | print("=== trust mode test ===") 46 | trust_result = await mas.call("trust_agent", {"query": query}) 47 | print(f"trust mode output: {trust_result}") 48 | 49 | 50 | if __name__ == "__main__": 51 | asyncio.run(main()) 52 | -------------------------------------------------------------------------------- /oxygent/oxy/flows/parallel_flow.py: -------------------------------------------------------------------------------- 1 | """Parallel flow module for concurrent execution workflows. 2 | 3 | This module provides the ParallelFlow class, which orchestrates concurrent execution of 4 | multiple tools or agents and aggregates their results into a unified response. 5 | """ 6 | 7 | import asyncio 8 | 9 | from ...schemas import OxyRequest, OxyResponse, OxyState 10 | from ..base_flow import BaseFlow 11 | 12 | 13 | class ParallelFlow(BaseFlow): 14 | """Flow that executes multiple tools or agents concurrently.""" 15 | 16 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 17 | """Execute the request concurrently across all permitted tools. 18 | 19 | Distributes the same request to all tools in the permitted_tool_name_list 20 | simultaneously and aggregates their outputs into a unified response. 21 | """ 22 | # Execute the same request concurrently across all permitted tools 23 | oxy_responses = await asyncio.gather( 24 | *[ 25 | oxy_request.call( 26 | callee=permitted_tool_name, arguments=oxy_request.arguments 27 | ) 28 | for permitted_tool_name in self.permitted_tool_name_list 29 | ] 30 | ) 31 | 32 | # Aggregate all outputs into a single response 33 | oxy_response = OxyResponse( 34 | state=OxyState.COMPLETED, 35 | output="The following are the results from multiple executions:" 36 | + "\n".join([res.output for res in oxy_responses]), 37 | ) 38 | return oxy_response 39 | -------------------------------------------------------------------------------- /docs/development/api/llms/http_llm.md: -------------------------------------------------------------------------------- 1 | # HttpLLM 2 | --- 3 | The position of the class is: 4 | 5 | 6 | ```markdown 7 | [Oxy](../agent/base_oxy.md) 8 | ├── [BaseLLM](./base_llm.md) 9 | └── [RemoteLLM](./remote_llm.md) 10 | ├──[HttpLLM](./http_llm.md) 11 | └──[OpenAILLM](./openai_llm.md) 12 | ├── [BaseTool](../tools/base_tools.md) 13 | └── [BaseFlow](../agent/base_flow.md) 14 | ``` 15 | 16 | --- 17 | 18 | ## Introduce 19 | 20 | `HttpLLM` is an HTTP-based Large Language Model implementation that provides a concrete implementation of RemoteLLM for communicating with remote language model APIs over HTTP. It supports various LLM providers that follow OpenAI-compatible API standards, including OpenAI, Google Gemini, and Ollama, with automatic provider detection and format handling. 21 | 22 | ## Parameters 23 | 24 | No additional parameters beyond inherited ones. 25 | 26 | ## Methods 27 | 28 | | Method | Coroutine (async) | Return Value | Purpose | 29 | | ------ | ----------------- | ------------ | ------- | 30 | | `_execute(oxy_request)` | Yes | `OxyResponse` | Execute an HTTP request to the remote LLM API with authentication and response parsing | 31 | 32 | ## Inherited 33 | Please refer to the [RemoteLLM](./remote_llm.md) class for inherited parameters and methods. 34 | 35 | ## Usage 36 | 37 | ```python 38 | oxy.HttpLLM( 39 | name="default_name", 40 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 41 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 42 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 43 | llm_params={"temperature": 0.01}, 44 | semaphore=4, 45 | ) 46 | ``` 47 | -------------------------------------------------------------------------------- /oxygent/db_factory.py: -------------------------------------------------------------------------------- 1 | class DBFactory: 2 | _instance = None 3 | _created_class = None 4 | 5 | def __new__(cls, *args, **kwargs): 6 | """Create or return a singleton instance of SingletonFactory, ensure only one 7 | instance of the factory is created.""" 8 | if not hasattr(cls, "_factory_instance"): 9 | cls._factory_instance = super().__new__(cls) 10 | return cls._factory_instance 11 | 12 | def get_instance(self, class_type, *args, **kwargs): 13 | """Get instance of assigned class_type. 14 | 15 | Create instance if not exists, otherwise return the existing instance. 16 | Only permits instance of the same class to be created. 17 | 18 | Args: 19 | class_type: the class type to be created 20 | *args: location arguments 21 | **kwargs: keyword arguments 22 | Returns: 23 | class_type: the instance of the class_type 24 | 25 | Raises: 26 | Exception: when the class_type is not the same as the created class 27 | """ 28 | if self._instance is None: 29 | # Create 1st instance 30 | self._instance = class_type(*args, **kwargs) 31 | self._created_class = class_type 32 | elif self._created_class != class_type: 33 | # Have exsiting instance, but the class_type is not the same as the created class 34 | raise Exception( 35 | f"DBFactory can only produce single instance of a class: {self._created_class.__name__}" 36 | ) 37 | return self._instance 38 | -------------------------------------------------------------------------------- /oxygent/web/image/org_tool.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/test_continue.py: -------------------------------------------------------------------------------- 1 | # tests/integration/test_continue_exec_demo.py 2 | 3 | import re 4 | 5 | import pytest 6 | 7 | from examples.advanced.demo_continue_exec import main 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_continue_exec_demo_integration(capfd): 12 | """Integration test for continue_exec_demo. 13 | 14 | This test runs the continue_exec_demo main function and verifies that 15 | the output contains valid assistant response with expected structural fields. 16 | """ 17 | 18 | # Run the demo main 19 | await main() 20 | 21 | # Capture stdout 22 | captured = capfd.readouterr() 23 | output = captured.out.strip() 24 | 25 | # Basic assertions: 26 | assert output, "Output should not be empty" 27 | 28 | # Extract lines containing 'time' but not error-like phrases 29 | matched_lines = [] 30 | for line in output.splitlines(): 31 | if re.search(r"time", line, re.IGNORECASE): 32 | if not re.search(r"sorry|error|exception", line, re.IGNORECASE): 33 | matched_lines.append(line) 34 | 35 | # Check that at least one valid time-related line is present 36 | assert matched_lines, "No valid time-related output found" 37 | 38 | # Further validate that each matched line is meaningful 39 | for line in matched_lines: 40 | assert len(line) > 10, f"Line too short: {line}" 41 | # Optionally check for typical timezone or time words 42 | assert re.search( 43 | r"time|timezone|datetime|hour|min|second", line, re.IGNORECASE 44 | ), f"Line does not appear to contain time info: {line}" 45 | -------------------------------------------------------------------------------- /test/unittest/test_tool/test_python_tools.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from oxygent.preset_tools.python_tools import run_python_code 4 | 5 | 6 | @pytest.mark.asyncio 7 | async def test_simple_code_execution(): 8 | code = "result = 2 + 3" 9 | output = await run_python_code(code, variable_to_return="result") 10 | assert output == "5" 11 | code = "x = 10" 12 | output = await run_python_code(code) 13 | assert output == "successfully run python code" 14 | code = "x = 5" 15 | output = await run_python_code(code, variable_to_return="y") 16 | assert output == "Variable y not found" 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_error_handling(): 21 | code = "raise ValueError('Test error')" 22 | output = await run_python_code(code) 23 | assert "Error running python code" in output 24 | assert "Test error" in output 25 | 26 | 27 | @pytest.mark.asyncio 28 | async def test_with_others(): 29 | code = "result = test_var * 2" 30 | custom_globals = {"test_var": 10} 31 | output = await run_python_code( 32 | code, variable_to_return="result", 33 | safe_globals=custom_globals) 34 | assert output == "20" 35 | code = "message = 'Hello World'" 36 | output = await run_python_code(code, variable_to_return="message") 37 | 38 | assert output == "Hello World" 39 | code = "numbers = [1, 2, 3, 4, 5]" 40 | output = await run_python_code(code, variable_to_return="numbers") 41 | assert output == "[1, 2, 3, 4, 5]" 42 | 43 | code = "flag = True" 44 | output = await run_python_code(code, variable_to_return="flag") 45 | assert output == "True" 46 | -------------------------------------------------------------------------------- /test/unittest/test_tool/test_train_ticket_tools.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from datetime import datetime, timedelta 3 | from oxygent.preset_tools.train_ticket_tools import get_stations 4 | from oxygent.preset_tools.train_ticket_tools import get_tickets 5 | from oxygent.preset_tools.train_ticket_tools import get_stations_of_city 6 | from oxygent.preset_tools.train_ticket_tools import get_cookie 7 | 8 | 9 | def test_get_stations(): 10 | # 调用被测试函数 11 | result = get_stations() 12 | 13 | # 验证结果 14 | assert len(result) > 0 15 | assert 'BJP' in result 16 | 17 | 18 | def test_get_cookie(): 19 | # 调用被测试函数 20 | result = get_cookie() 21 | print(result) 22 | 23 | # 验证结果 24 | assert len(result) > 0 25 | assert result['Set-Cookie'] 26 | 27 | 28 | @pytest.mark.asyncio 29 | async def test_get_stations_of_city_single_city(): 30 | # 调用被测试函数 31 | result = await get_stations_of_city('广州') 32 | print(result) 33 | 34 | # 验证结果 35 | assert len(result['广州']) 36 | 37 | 38 | @pytest.mark.asyncio 39 | async def test_get_stations_of_city_multiple_cities(): 40 | # 调用被测试函数 41 | result = await get_stations_of_city('广州|西安') 42 | print(result) 43 | 44 | # 验证结果 45 | assert len(result['广州']) and len(result['西安']) 46 | 47 | 48 | @pytest.mark.asyncio 49 | async def test_get_tickets_empty_result(): 50 | # 获取后天的日期,格式为 "%Y-%m-%d" 51 | day_after_tomorrow = (datetime.now() + timedelta(days=2)).strftime("%Y-%m-%d") 52 | # 调用被测试函数 53 | result = await get_tickets(day_after_tomorrow, 'BJP', 'SHH','ADULT') 54 | print(result) 55 | 56 | # 验证结果 57 | assert len(result) > 0 58 | 59 | -------------------------------------------------------------------------------- /oxygent/oxy/agents/remote_agent.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from pydantic import AnyUrl, Field, field_validator 4 | 5 | from ...schemas import OxyRequest, OxyResponse 6 | from .base_agent import BaseAgent 7 | 8 | 9 | class RemoteAgent(BaseAgent): 10 | """Base class for agents that communicate with remote systems. 11 | 12 | This agent provides the foundation for connecting to and interacting with 13 | remote agent systems over HTTP/HTTPS. 14 | 15 | Attributes: 16 | server_url (AnyUrl): The URL of the remote agent server. 17 | org (dict): Organization structure from the remote system. 18 | """ 19 | 20 | server_url: AnyUrl = Field() 21 | org: dict = Field(default_factory=dict) 22 | 23 | @field_validator("server_url") 24 | def check_protocol(cls, v): 25 | if v.scheme not in ("http", "https"): 26 | raise ValueError("server_url must start with http:// or https://") 27 | return v 28 | 29 | def get_org(self): 30 | # Add remote prefix to the copy 31 | def update_children(children): 32 | for node in children: 33 | node["is_remote"] = True 34 | if "children" in node and isinstance(node["children"], list): 35 | update_children(node["children"]) 36 | return children 37 | 38 | # Create deep copy and mark as remote 39 | children_copy = copy.deepcopy(self.org["children"]) 40 | return update_children(children_copy) 41 | 42 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 43 | raise NotImplementedError("This method is not yet implemented") 44 | -------------------------------------------------------------------------------- /examples/agents/demo_heterogeneous_agents.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, Config, OxyRequest, oxy 5 | 6 | Config.set_agent_llm_model("default_llm") 7 | 8 | 9 | async def workflow(oxy_request: OxyRequest): 10 | oxy_response = await oxy_request.call( 11 | callee="get_current_time", 12 | arguments={"timezone": "Asia/Shanghai"}, 13 | ) 14 | return oxy_response.output 15 | 16 | 17 | oxy_space = [ 18 | oxy.HttpLLM( 19 | name="default_llm", 20 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 21 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 22 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 23 | ), 24 | oxy.StdioMCPClient( 25 | name="time_tools", 26 | params={ 27 | "command": "uvx", 28 | "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"], 29 | }, 30 | ), 31 | oxy.ReActAgent( 32 | name="master_agent", 33 | llm_model="default_llm", 34 | sub_agents=["QA_agent", "time_agent"], 35 | ), 36 | oxy.ChatAgent( 37 | name="QA_agent", 38 | desc="A tool for knowledge.", 39 | llm_model="default_llm", 40 | ), 41 | oxy.WorkflowAgent( 42 | name="time_agent", 43 | desc="A tool for time query.", 44 | tools=["time_tools"], 45 | func_workflow=workflow, 46 | ), 47 | ] 48 | 49 | 50 | async def main(): 51 | async with MAS(oxy_space=oxy_space) as mas: 52 | await mas.start_web_service( 53 | first_query="What time it is?", 54 | ) 55 | 56 | 57 | if __name__ == "__main__": 58 | asyncio.run(main()) 59 | -------------------------------------------------------------------------------- /test/unittest/skip_test_code_interpreter_tools.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the code interpreter tools.""" 2 | 3 | import pytest 4 | 5 | from oxygent.preset_tools.code_interpreter_tools import code_interpreter_tools 6 | 7 | 8 | @pytest.mark.asyncio 9 | async def test_execute_code_simple(): 10 | session_id = "test_session_1" 11 | _, execute_code = code_interpreter_tools.func_dict["execute_code"] 12 | _, stop_session = code_interpreter_tools.func_dict["stop_session"] 13 | 14 | result = await execute_code(session_id=session_id, code="a = 10; print(a)") 15 | assert "10" in result 16 | await stop_session(session_id=session_id) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_execute_code_stateful(): 21 | session_id = "test_session_2" 22 | _, execute_code = code_interpreter_tools.func_dict["execute_code"] 23 | _, stop_session = code_interpreter_tools.func_dict["stop_session"] 24 | 25 | await execute_code(session_id=session_id, code="x = 20") 26 | result = await execute_code(session_id=session_id, code="print(x * 2)") 27 | assert "40" in result 28 | await stop_session(session_id=session_id) 29 | 30 | 31 | @pytest.mark.asyncio 32 | async def test_execute_code_error(): 33 | session_id = "test_session_3" 34 | _, execute_code = code_interpreter_tools.func_dict["execute_code"] 35 | _, stop_session = code_interpreter_tools.func_dict["stop_session"] 36 | 37 | # No warm-up: validate first-call error behavior without preheating 38 | result = await execute_code(session_id=session_id, code="print(undefined_variable)") 39 | assert "NameError" in result 40 | await stop_session(session_id=session_id) 41 | -------------------------------------------------------------------------------- /oxygent/web/image/org_flow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/agents/demo_hierarchical_agents.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, oxy 5 | 6 | oxy_space = [ 7 | oxy.HttpLLM( 8 | name="default_llm", 9 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 10 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 11 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 12 | ), 13 | oxy.StdioMCPClient( 14 | name="time_tools", 15 | params={ 16 | "command": "uvx", 17 | "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"], 18 | }, 19 | ), 20 | oxy.StdioMCPClient( 21 | name="file_tools", 22 | params={ 23 | "command": "npx", 24 | "args": ["-y", "@modelcontextprotocol/server-filesystem", "./local_file"], 25 | }, 26 | ), 27 | oxy.ReActAgent( 28 | name="master_agent", 29 | is_master=True, 30 | sub_agents=["time_agent", "file_agent"], 31 | llm_model="default_llm", 32 | ), 33 | oxy.ReActAgent( 34 | name="time_agent", 35 | desc="A tool for time query", 36 | tools=["time_tools"], 37 | llm_model="default_llm", 38 | ), 39 | oxy.ReActAgent( 40 | name="file_agent", 41 | desc="A tool for file operation.", 42 | tools=["file_tools"], 43 | llm_model="default_llm", 44 | ), 45 | ] 46 | 47 | 48 | async def main(): 49 | async with MAS(oxy_space=oxy_space) as mas: 50 | await mas.start_web_service( 51 | first_query="Get what time it is and save in `log.txt` under `/local_file`", 52 | ) 53 | 54 | 55 | if __name__ == "__main__": 56 | asyncio.run(main()) 57 | -------------------------------------------------------------------------------- /oxygent/web/image/right-user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/oxy/agents/rag_agent.py: -------------------------------------------------------------------------------- 1 | """Chat agent module for conversational interactions. 2 | 3 | This module provides the ChatAgent class, which handles conversational AI interactions 4 | by managing conversation memory, processing user queries, and coordinating with language 5 | models to generate responses. 6 | """ 7 | 8 | from typing import Callable 9 | 10 | from pydantic import Field, model_validator 11 | 12 | from ...schemas.oxy import OxyRequest 13 | from .chat_agent import ChatAgent 14 | 15 | 16 | class RAGAgent(ChatAgent): 17 | """A conversational agent that manages chat interactions with language models.""" 18 | 19 | knowledge_placeholder: str = Field("knowledge") 20 | 21 | func_retrieve_knowledge: Callable = Field( 22 | exclude=True, description="Retrieve knowledge function" 23 | ) 24 | 25 | def __init__(self, **kwargs): 26 | """Initialize the RAG agent with appropriate prompt and parsing function.""" 27 | super().__init__(**kwargs) 28 | 29 | @model_validator(mode="after") 30 | def set_default_prompt(self): 31 | if not self.prompt: 32 | self.prompt = ( 33 | "You are a helpful assistant. You can refer to the following information to answer the questions.\n${" 34 | + self.knowledge_placeholder 35 | + "}" 36 | ) 37 | return self 38 | 39 | async def _pre_process(self, oxy_request: OxyRequest) -> OxyRequest: 40 | oxy_request = await super()._pre_process(oxy_request) 41 | knowledge = await self.func_retrieve_knowledge(oxy_request) 42 | oxy_request.arguments[self.knowledge_placeholder] = knowledge 43 | return oxy_request 44 | -------------------------------------------------------------------------------- /examples/shortest_path/shortest_path_demo.py: -------------------------------------------------------------------------------- 1 | import os 2 | from oxygent import MAS, Config, oxy 3 | from oxygent.shortest_path.shortest_path import shortest_path_tools 4 | 5 | Config.set_agent_llm_model("default_llm") 6 | 7 | def create_optimal_agent(): 8 | return oxy.ReActAgent( 9 | name="shortest_path_agent", 10 | desc="Agent for computing shortest path between different cities", 11 | category="agent", 12 | class_name="ReActAgent", 13 | tools=["shortest_path_tools"], 14 | llm_model="default_llm", 15 | is_entrance=False, 16 | is_permission_required=False, 17 | is_save_data=True, 18 | timeout=30, 19 | retries=3, 20 | delay=1, 21 | is_multimodal_supported=False, 22 | semaphore=2, 23 | ) 24 | oxy_space = [ 25 | oxy.HttpLLM( 26 | name="default_llm", 27 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 28 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 29 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 30 | ), 31 | shortest_path_tools, 32 | create_optimal_agent(), 33 | oxy.ReActAgent( 34 | name="excel_agent", 35 | desc="A tool that can read file information based on the Excel file path.", 36 | tools=["shortest_path_tools"], 37 | ), 38 | oxy.ReActAgent( 39 | is_master=True, 40 | name="master_agent", 41 | sub_agents=["excel_agent","shortest_path_agent"], 42 | ), 43 | ] 44 | 45 | async def main(): 46 | async with MAS(oxy_space=oxy_space) as mas: 47 | await mas.start_web_service(first_query="") 48 | 49 | if __name__ == "__main__": 50 | import asyncio 51 | asyncio.run(main()) -------------------------------------------------------------------------------- /oxygent/web/image/llm-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/development/api/llms/openai_llm.md: -------------------------------------------------------------------------------- 1 | # OpenAILLM 2 | --- 3 | The position of the class is: 4 | 5 | 6 | ```markdown 7 | [Oxy](../agent/base_oxy.md) 8 | ├── [BaseLLM](./base_llm.md) 9 | └── [RemoteLLM](./remote_llm.md) 10 | ├──[HttpLLM](./http_llm.md) 11 | └──[OpenAILLM](./openai_llm.md) 12 | ├── [BaseTool](../tools/base_tools.md) 13 | └── [BaseFlow](../agent/base_flow.md) 14 | ``` 15 | 16 | --- 17 | 18 | ## Introduce 19 | 20 | OpenAILLM is a concrete implementation of RemoteLLM specifically designed for OpenAI's language models. It uses the official AsyncOpenAI client for optimal performance and compatibility with OpenAI's API standards. This class supports all OpenAI models and compatible APIs, handling payload construction, configuration merging, and response processing for OpenAI's chat completion API. 21 | 22 | ## Parameters 23 | 24 | No additional parameters beyond inherited ones. 25 | 26 | ## Methods 27 | 28 | 29 | | Method | Coroutine (async) | Return Value | Purpose | 30 | | ------ | ----------------- | ------------ | ------- | 31 | | `_execute(oxy_request)` | Yes | `OxyResponse` | Execute a request using the OpenAI API, creating a chat completion request and processing the response | 32 | 33 | ## Inherited 34 | Please refer to the [RemoteLLM](./remote_llm.md) class for inherited parameters and methods. 35 | 36 | ## Usage 37 | ```python 38 | oxy.OpenAILLM( 39 | name="default_llm", 40 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 41 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 42 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 43 | llm_params={"temperature": 0.01}, 44 | semaphore=4, 45 | timeout=240, 46 | ), 47 | ``` 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/tools/demo_mcp.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, oxy 5 | 6 | oxy_space = [ 7 | oxy.HttpLLM( 8 | name="default_llm", 9 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 10 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 11 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 12 | ), 13 | oxy.StdioMCPClient( 14 | name="time_tools", 15 | params={ 16 | "command": "uvx", 17 | "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"], 18 | }, 19 | ), 20 | oxy.StdioMCPClient( 21 | name="map_tools", 22 | params={ 23 | "command": "npx", 24 | "args": ["-y", "@amap/amap-maps-mcp-server"], 25 | "env": {"AMAP_MAPS_API_KEY": "API_KEY"}, # 配置高德地图API密钥 26 | }, 27 | ), 28 | oxy.StdioMCPClient( 29 | name="math_tools", 30 | params={ 31 | "command": "uv", 32 | "args": ["--directory", "./mcp_servers", "run", "math_tools.py"], 33 | }, 34 | ), 35 | # oxy.SSEMCPClient( 36 | # name="sse_mcp_tools", 37 | # sse_url="http://127.0.0.1:8000/sse", 38 | # ), 39 | # oxy.StreamableMCPClient( 40 | # name="stream_mcp_tools", 41 | # server_url="http://127.0.0.1:8000/mcp", 42 | # ), 43 | oxy.ReActAgent( 44 | name="master_agent", 45 | tools=["time_tools", "math_tools"], 46 | llm_model="default_llm", 47 | ), 48 | ] 49 | 50 | 51 | async def main(): 52 | async with MAS(oxy_space=oxy_space) as mas: 53 | await mas.start_web_service(first_query="What time is it") 54 | 55 | 56 | if __name__ == "__main__": 57 | asyncio.run(main()) 58 | -------------------------------------------------------------------------------- /examples/start_ecommerce.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | RED='\033[0;31m' 6 | GREEN='\033[0;32m' 7 | NC='\033[0m' # No Color 8 | 9 | log() { 10 | echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" 11 | } 12 | 13 | error() { 14 | echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" 15 | } 16 | 17 | cleanup() { 18 | log "Cleaning up processes..." 19 | jobs -p | xargs -r kill 2>/dev/null || true 20 | wait 2>/dev/null || true 21 | log "Cleanup complete" 22 | } 23 | 24 | trap cleanup EXIT INT TERM 25 | 26 | start_service() { 27 | local cmd=$1 28 | local name=$2 29 | local wait_time=${3:-5} 30 | 31 | log "Starting $name..." 32 | $cmd & 33 | local pid=$! 34 | 35 | sleep $wait_time 36 | 37 | # Check if the process is still running 38 | if kill -0 $pid 2>/dev/null; then 39 | log "$name started successfully (PID: $pid)" 40 | return 0 41 | else 42 | error "$name failed to start" 43 | return 1 44 | fi 45 | } 46 | 47 | main() { 48 | log "Starting the Intelligent E-commerce Customer Service System..." 49 | 50 | start_service "python -m examples.ecommerce.app_product_service" "Product Service" 5 51 | start_service "python -m examples.ecommerce.app_payment_service" "Payment Service" 5 52 | start_service "python -m examples.ecommerce.app_order_service" "Order Service" 5 53 | start_service "python -m examples.ecommerce.app_logistics_service" "Logistics Service" 5 54 | start_service "python -m examples.ecommerce.app_gateway" "Gateway Service" 5 55 | 56 | log "All services have been started" 57 | log "Press Ctrl+C to stop all services" 58 | 59 | wait 60 | } 61 | 62 | 63 | main "$@" -------------------------------------------------------------------------------- /examples/ecommerce/app_order_service.py: -------------------------------------------------------------------------------- 1 | # examples/ecommerce/app_order_service.py 2 | 3 | import os 4 | 5 | from oxygent import MAS, Config, oxy 6 | 7 | Config.set_app_name("order-service") 8 | Config.set_server_port(8081) 9 | 10 | 11 | oxy_space = [ 12 | oxy.HttpLLM( 13 | name="default_name", 14 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 15 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 16 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 17 | llm_params={"temperature": 0.01}, 18 | semaphore=4, 19 | ), 20 | # order management tools 21 | oxy.StdioMCPClient( 22 | name="order_tools", 23 | params={ 24 | "command": "uv", 25 | "args": ["--directory", "./mcp_servers", "run", "order_tools.py"], 26 | }, 27 | ), 28 | # payment service agent 29 | oxy.SSEOxyGent( 30 | name="payment_service", 31 | desc="Payment service intelligent twin, professional processing of payment-related business: support to query payment status and payment details according to the order number, as well as provide a variety of payment methods query and payment channel information consulting services", 32 | server_url="http://127.0.0.1:8082", 33 | ), 34 | # order management agent 35 | oxy.ReActAgent( 36 | name="order_agent", 37 | is_master=True, 38 | tools=["order_tools"], 39 | sub_agents=["payment_service"], 40 | llm_model="default_name", 41 | ), 42 | ] 43 | 44 | 45 | async def main(): 46 | async with MAS(oxy_space=oxy_space) as mas: 47 | await mas.start_web_service() 48 | 49 | 50 | if __name__ == "__main__": 51 | import asyncio 52 | 53 | asyncio.run(main()) 54 | -------------------------------------------------------------------------------- /docs/development/api/flows/parallel_flow.md: -------------------------------------------------------------------------------- 1 | # ParallelFlow 2 | --- 3 | The position of the class is: 4 | 5 | 6 | ```markdown 7 | [Oxy](../agent//base_oxy.md) 8 | ├── [BaseFlow](./base_flow.md) 9 | ├── [WorkFlow](./workflow.md) 10 | ├── [ParallelFlow](./parallel_flow.md) 11 | ├── [PlanAndSolve](./plan_and_solve.md) 12 | ├── [Reflexion](./reflexion.md) 13 | └── [BaseAgent](../agent/base_agent.md) 14 | ├── [LocalAgent](../agent/local_agent.md) 15 | │ ├── [ParallelAgent](../agent/parallel_agent.md) 16 | │ ├── [ReActAgent](../agent/react_agent.md) 17 | │ ├── [ChatAgent](../agent/chat_agent.md) 18 | │ └── [WorkflowAgent](../agent/workflow_agent.md) 19 | └── [RemoteAgent](../agent/remote_agent.md) 20 | └── [SSEOxyGent](../agent/sse_oxy_agent.md) 21 | └── [BaseTool](../tools/base_tools.md) 22 | ``` 23 | 24 | --- 25 | 26 | ## Introduce 27 | 28 | `ParallelFlow` is a flow that executes multiple tools or agents concurrently for parallel processing workflows. It orchestrates concurrent execution of the same request across all permitted tools simultaneously and aggregates their results into a unified response, enabling efficient parallel processing and result comparison. 29 | 30 | ## Parameters 31 | 32 | No additional parameters beyond inherited ones. 33 | 34 | ## Methods 35 | 36 | | Method | Coroutine (async) | Return Value | Purpose | 37 | | ------ | ----------------- | ------------ | ------- | 38 | | `_execute(oxy_request)` | Yes | `OxyResponse` | Execute the request concurrently across all permitted tools and aggregate results | 39 | 40 | ## Inherited 41 | Please refer to the [BaseFlow](../agents/base_flow.md) class for inherited parameters and methods. 42 | -------------------------------------------------------------------------------- /oxygent/chart/open_chart_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | 流程图打开工具模块 3 | """ 4 | 5 | from oxygent.oxy import FunctionHub 6 | import os 7 | import webbrowser 8 | import asyncio 9 | 10 | # 初始化 FunctionHub 11 | open_chart_tools = FunctionHub(name="open_chart_tools") 12 | 13 | @open_chart_tools.tool( 14 | description="在浏览器中打开生成的流程图 HTML 文件" 15 | ) 16 | async def open_html_chart(file_path: str) -> str: 17 | """ 18 | 在浏览器中打开生成的流程图 HTML 文件 19 | 20 | Args: 21 | file_path: HTML 文件路径 22 | 23 | Returns: 24 | str: 操作结果消息 25 | """ 26 | try: 27 | # 确保使用绝对路径 28 | if not os.path.isabs(file_path): 29 | # 如果是相对路径,需要考虑当前工作目录 30 | current_dir = os.getcwd() 31 | 32 | # 如果当前在 examples/other 目录,并且文件路径以 output/ 开头,需要调整路径 33 | if (current_dir.endswith('examples/other') or current_dir.endswith('examples\\other')) and file_path.startswith('output/'): 34 | # 回到项目根目录来解析路径 35 | project_root = os.path.abspath(os.path.join(current_dir, '../..')) 36 | output_path_abs = os.path.join(project_root, file_path) 37 | else: 38 | output_path_abs = os.path.abspath(file_path) 39 | else: 40 | output_path_abs = file_path 41 | 42 | # 检查文件是否存在 43 | if not os.path.exists(output_path_abs): 44 | return f"错误:文件不存在: {output_path_abs}" 45 | 46 | print(f"正在打开文件: {output_path_abs}") 47 | webbrowser.open(f"file://{output_path_abs}") 48 | return f"已在浏览器中打开流程图: {output_path_abs}" 49 | except Exception as e: 50 | return f"打开浏览器时出错: {e}\n请手动打开生成的文件: {file_path}" 51 | 52 | # 导出 open_chart_tools 供其他模块使用 53 | __all__ = ['open_chart_tools'] -------------------------------------------------------------------------------- /examples/advanced/demo_send_message_from_tool.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from pydantic import Field 5 | 6 | from oxygent import MAS, OxyRequest, oxy 7 | 8 | fh_math_tools = oxy.FunctionHub(name="math_tools") 9 | 10 | 11 | @fh_math_tools.tool(description="A tool that can calculate the value of pi.") 12 | async def calc_pi( 13 | prec: int = Field(description="how many decimal places"), 14 | oxy_request: OxyRequest = Field(description="The oxy request"), 15 | ) -> float: 16 | import math 17 | from decimal import Decimal, getcontext 18 | 19 | getcontext().prec = int(prec) 20 | x = 0 21 | for k in range(int(int(prec) / 8) + 1): 22 | a = 2 * Decimal.sqrt(Decimal(2)) / 9801 23 | b = math.factorial(4 * k) * (1103 + 26390 * k) 24 | c = pow(math.factorial(k), 4) * pow(396, 4 * k) 25 | x = x + a * b / c 26 | result = 1 / x 27 | 28 | await oxy_request.send_message({"type": "answer", "content": result}) # 发送消息 29 | await oxy_request.break_task() # 中断任务 30 | return result 31 | 32 | 33 | oxy_space = [ 34 | oxy.HttpLLM( 35 | name="default_llm", 36 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 37 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 38 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 39 | ), 40 | fh_math_tools, 41 | oxy.ReActAgent( 42 | name="master_agent", 43 | tools=["math_tools"], 44 | llm_model="default_llm", 45 | ), 46 | ] 47 | 48 | 49 | async def main(): 50 | async with MAS(oxy_space=oxy_space) as mas: 51 | await mas.start_web_service( 52 | first_query="Please calculate the 20 positions of Pi", 53 | ) 54 | 55 | 56 | if __name__ == "__main__": 57 | asyncio.run(main()) 58 | -------------------------------------------------------------------------------- /docs/development/api/llms/remote_llm.md: -------------------------------------------------------------------------------- 1 | # RemoteLLM 2 | --- 3 | The position of the class is: 4 | 5 | 6 | ```markdown 7 | [Oxy](../agent/base_oxy.md) 8 | ├── [BaseLLM](./base_llm.md) 9 | └── [RemoteLLM](./remote_llm.md) 10 | ├──[HttpLLM](./http_llm.md) 11 | └──[OpenAILLM](./openai_llm.md) 12 | ├── [BaseTool](../tools/base_tools.md) 13 | └── [BaseFlow](../agent/base_flow.md) 14 | ``` 15 | 16 | --- 17 | 18 | ## Introduce 19 | 20 | `RemoteLLM` is a concrete implementation of `BaseLLM` for communicating with remote Large Language Model APIs. It provides a standardized interface for connecting to remote LLM services, handling API authentication, request formatting, and response parsing for OpenAI-compatible APIs. This class extends the base functionality with specific remote API communication capabilities. 21 | 22 | ## Parameters 23 | 24 | 25 | | Parameter | Type / Allowed value | Default | Description | 26 | | --------- | -------------------- | ------- | ----------- | 27 | | `api_key` | `Optional[str]` | `None` | The API key for authentication with the remote LLM service | 28 | | `base_url` | `Optional[str]` | `""` | The base URL endpoint for the remote LLM API (required) | 29 | | `model_name` | `Optional[str]` | `""` | The specific model name to use for requests (required) | 30 | 31 | ## Methods 32 | 33 | 34 | | Method | Coroutine (async) | Return Value | Purpose | 35 | | ------ | ----------------- | ------------ | ------- | 36 | | `_execute(oxy_request)` | Yes | `OxyResponse` | Execute the remote LLM API request and return response (to be implemented by subclasses) | 37 | 38 | ## Inherited 39 | Please refer to the [BaseLLM](./base_llm.md) class for inherited parameters and methods. 40 | 41 | ## Usage 42 | 43 | The class `RemoteLLM` must be inherited. 44 | -------------------------------------------------------------------------------- /examples/advanced/demo_continue_exec.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, oxy 5 | 6 | oxy_space = [ 7 | oxy.HttpLLM( 8 | name="default_llm", 9 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 10 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 11 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 12 | ), 13 | oxy.StdioMCPClient( 14 | name="time_tools", 15 | params={ 16 | "command": "uvx", 17 | "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"], 18 | }, 19 | ), 20 | oxy.ReActAgent( 21 | name="time_agent", 22 | desc="A tool for time query.", 23 | tools=["time_tools"], 24 | llm_model="default_llm", 25 | ), 26 | ] 27 | 28 | 29 | async def main(): 30 | async with MAS(oxy_space=oxy_space) as mas: 31 | """first""" 32 | payload = { 33 | "query": "Get what time it is Asia/Shanghai", 34 | } 35 | oxy_response = await mas.chat_with_agent(payload=payload) 36 | print("LLM-first: ", oxy_response.output) 37 | 38 | """second""" 39 | # payload = { 40 | # "query": "Get what time it is Asia/Shanghai", 41 | # "restart_node_id": "wYF2EqBj3RYKiRK7", # 传入第一次调用的中间节点node_id 42 | # "restart_node_output": """{ 43 | # "timezone": "Asia/Shanghai", 44 | # "datetime": "2024-10-14T06:18:00+08:00", 45 | # "day_of_week": "Tuesday", 46 | # "is_dst": false 47 | # }""", 48 | # } 49 | # oxy_response = await mas.chat_with_agent(payload=payload) 50 | # print("LLM-second: ", oxy_response.output) 51 | 52 | 53 | if __name__ == "__main__": 54 | asyncio.run(main()) 55 | -------------------------------------------------------------------------------- /test/unittest/test_llm_pydantic_parser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for PydanticOutputParser 3 | """ 4 | 5 | import pytest 6 | from pydantic import BaseModel 7 | 8 | from oxygent.utils.llm_pydantic_parser import PydanticOutputParser 9 | 10 | 11 | # ────────────────────────────────────────────────────────────────────────────── 12 | # ❶ Sample Pydantic output schema 13 | # ────────────────────────────────────────────────────────────────────────────── 14 | class Answer(BaseModel): 15 | text: str 16 | score: int 17 | 18 | 19 | # ────────────────────────────────────────────────────────────────────────────── 20 | # ❷ Fixtures 21 | # ────────────────────────────────────────────────────────────────────────────── 22 | @pytest.fixture 23 | def parser(): 24 | return PydanticOutputParser(output_cls=Answer) 25 | 26 | 27 | # ────────────────────────────────────────────────────────────────────────────── 28 | # ❸ Tests 29 | # ────────────────────────────────────────────────────────────────────────────── 30 | def test_get_format_string_no_escape(parser): 31 | raw_no_escape = parser.get_format_string(escape_json=False) 32 | raw_escape = parser.get_format_string(escape_json=True) 33 | 34 | # escape=False 时,双大括号出现次数应 *减少*(但不一定为 0) 35 | assert raw_no_escape.count("{{") < raw_escape.count("{{") 36 | assert raw_no_escape.count("}}") < raw_escape.count("}}") 37 | 38 | 39 | def test_parse_success(parser): 40 | blob = 'Result => {"text":"ok","score":10}' 41 | obj = parser.parse(blob) 42 | assert isinstance(obj, Answer) 43 | assert obj.text == "ok" and obj.score == 10 44 | 45 | 46 | def test_format_appends_instruction(parser): 47 | q = "Give answer" 48 | prompt = parser.format(q) 49 | assert q in prompt 50 | assert prompt.endswith(parser.format_string) 51 | -------------------------------------------------------------------------------- /docs/docs_zh/2_3_use_opensource_tools.md: -------------------------------------------------------------------------------- 1 | # 如何使用开源MCP工具? 2 | 3 | 在使用MCP的过程中,您可能希望使用外部工具。OxyGent支持如同本地工具一样集成外部的开源工具,您可以使用基于MCP协议的`oxy.StdioMCPClient`引入外部工具。 4 | 5 | 例如,如果您希望使用工具获取时间,您可以使用`mcp-server-time`工具: 6 | 7 | ```python 8 | oxy.StdioMCPClient( 9 | name="time_tools", 10 | params={ 11 | "command": "uvx", 12 | "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"], 13 | }, 14 | ), 15 | ``` 16 | 17 | ## 完整的可运行样例 18 | 19 | 以下是可运行的完整代码示例: 20 | 21 | ```python 22 | import asyncio 23 | 24 | from oxygent import MAS, oxy, Config 25 | import os 26 | import prompts 27 | import tools 28 | 29 | Config.set_agent_llm_model("default_llm") 30 | 31 | oxy_space = [ 32 | oxy.HttpLLM( 33 | name="default_llm", 34 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 35 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 36 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 37 | llm_params={"temperature": 0.01}, 38 | semaphore=4, 39 | timeout=240, 40 | ), 41 | oxy.StdioMCPClient( 42 | name="time_tools", 43 | params={ 44 | "command": "uvx", 45 | "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"], 46 | }, 47 | ), 48 | tools.file_tools, 49 | oxy.ReActAgent( 50 | name="master_agent", 51 | is_master=True, 52 | tools=["file_tools","time_tools"], 53 | ), 54 | ] 55 | 56 | 57 | async def main(): 58 | async with MAS(oxy_space=oxy_space) as mas: 59 | await mas.start_web_service( 60 | first_query="Hello!" 61 | ) 62 | 63 | 64 | if __name__ == "__main__": 65 | asyncio.run(main()) 66 | ``` 67 | 68 | [上一章:注册一个工具](./2_register_single_tool.md) 69 | [下一章:使用MCP自定义工具](./2_4_use_mcp_tools.md) 70 | [回到首页](./readme.md) -------------------------------------------------------------------------------- /oxygent/web/image/org_model.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/unittest/test_common_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for oxygent.utils.common_utils 3 | """ 4 | 5 | import asyncio 6 | import hashlib 7 | import json 8 | 9 | import pytest 10 | 11 | import oxygent.utils.common_utils as cu 12 | 13 | 14 | def test_chunk_list_and_timestamp(): 15 | assert cu.chunk_list([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4], [5]] 16 | ts = float(cu.get_timestamp()) 17 | assert ts > 0 18 | 19 | 20 | def test_extract_json_functions(): 21 | text = '```json\n{"a":1}\n```' 22 | assert cu.extract_first_json(text) == '{"a":1}' 23 | assert cu.extract_json_str('foo {"x":2}') == '{"x":2}' 24 | with pytest.raises(ValueError): 25 | cu.extract_json_str("no-json") 26 | 27 | 28 | def test_url_helpers(): 29 | assert cu.append_url_path("https://a.com/api", "/v1") == "https://a.com/api/v1" 30 | built = cu.build_url("https://a.com", "chat", {"q": "x", "q": ["y"]}) # noqa: F601 31 | assert built.startswith("https://a.com/chat") 32 | assert "q=y" in built 33 | 34 | 35 | def test_filter_json_types_and_msgpack(): 36 | res = cu.filter_json_types({"k": object()}) 37 | assert res["k"] == "..." 38 | assert cu.msgpack_preprocess({"t": (1, 2)}) == {"t": [1, 2]} 39 | 40 | 41 | def test_get_md5_and_to_json(): 42 | s = "abc" 43 | assert cu.get_md5(s) == hashlib.md5(b"abc").hexdigest() 44 | assert cu.to_json({"x": 1}) == json.dumps({"x": 1}, ensure_ascii=False) 45 | 46 | 47 | @pytest.fixture(autouse=True) 48 | def patch_source_to_bytes(monkeypatch): 49 | monkeypatch.setattr( 50 | cu, 51 | "source_to_bytes", 52 | lambda src: asyncio.new_event_loop().create_future(), 53 | raising=True, 54 | ) 55 | fut = cu.source_to_bytes(None) 56 | fut.set_result(b"\x89PNG\r\n\x1a\n") 57 | yield 58 | -------------------------------------------------------------------------------- /docs/development/api/api_tools/http_tool.md: -------------------------------------------------------------------------------- 1 | # HttpTool 2 | --- 3 | The position of the class is: 4 | 5 | 6 | ```markdown 7 | [Oxy](../agent/base_oxy.md) 8 | ├── [BaseTool](../tools/base_tools.md) 9 | ├── [MCPTool](../tools/mcp_tool.md) 10 | ├── [BaseMCPClient](../tools/base_mcp_client.md) 11 | │ ├──[StdioMCPClient](../tools/stdio_mcp_client.md) 12 | │ ├──[SSEMCPClient](../tools/sse_mcp_client.md) 13 | │ └──[StreamableMCPClient](../tools/streamable_mcp_client.md) 14 | ├── [HttpTool](../api_tools/http_tool.md) 15 | ├── [FunctionHub](../function_tools/function_hub.md) 16 | └── [FunctionTool](../function_tools/function_tool.md) 17 | └── [BaseFlow](../agent/base_flow.md) 18 | ``` 19 | 20 | --- 21 | 22 | ## Introduce 23 | 24 | `HttpTool` is a tool class for making HTTP requests to external APIs and services in the OxyGent system. It supports configurable methods, headers, and parameters with proper timeout handling. 25 | 26 | ## Parameters 27 | 28 | 29 | | Parameter | Type / Allowed value | Default | Description | 30 | | --------- | -------------------- | ------- | ----------- | 31 | | `method` | `str` | `"GET"` | HTTP method to use | 32 | | `url` | `str` | `""` | Target URL for the HTTP request | 33 | | `headers` | `dict` | `{}` | HTTP headers to include in the request | 34 | | `default_params` | `dict` | `{}` | Default parameters that will be merged with request arguments | 35 | 36 | ## Methods 37 | 38 | 39 | | Method | Coroutine (async) | Return Value | Purpose | 40 | | ------ | ----------------- | ------------ | ------- | 41 | | `_execute(oxy_request)` | Yes | `OxyResponse` | Execute the HTTP request with merged parameters and timeout handling | 42 | 43 | ## Inherited 44 | Please refer to the [BaseTool](../agent/base_tools.md) class for inherited parameters and methods. 45 | 46 | ## Usage 47 | 48 | -------------------------------------------------------------------------------- /docs/development/api/oxy_factory.md: -------------------------------------------------------------------------------- 1 | # OxyFactory 2 | --- 3 | The position of the class is: 4 | 5 | ``` 6 | oxygent/oxy_factory.py 7 | ``` 8 | 9 | --- 10 | 11 | ## Introduce 12 | 13 | `OxyFactory` is a factory class for creating OxyGent operators. It provides a centralized way to create various types of Oxy components including agents, tools, LLMs, and workflows. The factory uses a registry pattern with a static dictionary mapping class names to their corresponding class constructors, enabling dynamic component creation based on string identifiers. 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type / Allowed value | Default | Description | 18 | | --------- | -------------------- | ------- | ----------- | 19 | | `_creators` | `dict` | `{}` | Static dictionary mapping class names to class constructors | 20 | 21 | ### Supported Components 22 | 23 | | Component Type | Class Name | Description | 24 | | -------------- | ---------- | ----------- | 25 | | Agent | `ChatAgent` | Chat-based conversational agent | 26 | | Agent | `ReActAgent` | ReAct pattern reasoning agent | 27 | | Agent | `WorkflowAgent` | Workflow-based agent | 28 | | Tool | `HttpTool` | HTTP-based tool for web requests | 29 | | Tool | `MCPTool` | Model Context Protocol tool | 30 | | Tool | `FunctionTool` | Function-based tool | 31 | | LLM | `HttpLLM` | HTTP-based language model | 32 | | LLM | `OpenAILLM` | OpenAI language model | 33 | | MCP Client | `StdioMCPClient` | Standard I/O MCP client | 34 | | MCP Client | `SSEMCPClient` | Server-Sent Events MCP client | 35 | | Workflow | `Workflow` | Workflow component | 36 | 37 | ## Methods 38 | 39 | | Method | Coroutine (async) | Return Value | Purpose | 40 | | ------ | ----------------- | ------------ | ------- | 41 | | `create_oxy()` | No | `object` | Static method to create Oxy component instance based on class name | 42 | 43 | 44 | -------------------------------------------------------------------------------- /oxygent/web/image/video.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/chat-at-list-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/global-theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/development/api/tools/streamable_mcp_client.md: -------------------------------------------------------------------------------- 1 | # StreamableMCPClient 2 | --- 3 | The position of the class is: 4 | 5 | 6 | ```markdown 7 | [Oxy](../agent/base_oxy.md) 8 | ├── [BaseTool](../tools/base_tools.md) 9 | ├── [MCPTool](../tools/mcp_tool.md) 10 | ├── [BaseMCPClient](../tools/base_mcp_client.md) 11 | │ ├──[StdioMCPClient](../tools/stdio_mcp_client.md) 12 | │ ├──[SSEMCPClient](../tools/sse_mcp_client.md) 13 | │ └──[StreamableMCPClient](../tools/streamable_mcp_client.md) 14 | ├── [HttpTool](../api_tools/http_tool.md) 15 | ├── [FunctionHub](../function_tools/function_hub.md) 16 | └── [FunctionTool](../function_tools/function_tool.md) 17 | └── [BaseFlow](../agent/base_flow.md) 18 | ``` 19 | 20 | --- 21 | 22 | ## Introduce 23 | 24 | `StreamableMCPClient` is an MCP client implementation using Streamable-HTTP transport. It extends the BaseMCPClient to provide HTTP streaming connection capabilities for communicating with MCP servers. This client supports custom headers and middlewares for enhanced HTTP communication. 25 | 26 | ## Parameters 27 | 28 | 29 | | Parameter | Type / Allowed value | Default | Description | 30 | | --------- | -------------------- | ------- | ----------- | 31 | | `server_url` | `AnyUrl` | `""` | URL of the MCP server to connect to | 32 | | `headers` | `Dict[str, str]` | `{}` | Extra HTTP headers for server communication | 33 | | `middlewares` | `List[Any]` | `[]` | Client-side MCP middlewares for request processing | 34 | 35 | ## Methods 36 | 37 | 38 | | Method | Coroutine (async) | Return Value | Purpose | 39 | | ------ | ----------------- | ------------ | ------- | 40 | | `init()` | Yes | `None` | Initialize the HTTP streaming connection to the MCP server | 41 | 42 | ## Inherited 43 | Please refer to the [BaseMCPClient](./base_mcp_client.md) class for inherited parameters and methods. 44 | 45 | -------------------------------------------------------------------------------- /docs/development/api/databases/db_vector/base_vector_db.md: -------------------------------------------------------------------------------- 1 | # BaseVectorDB 2 | 3 | --- 4 | The position of the class is: 5 | 6 | ```markdown 7 | [BaseDB](../base_db.md) 8 | ├── [BaseES](../db_es/base_es.md) 9 | ├── [JesES](../db_es/jes_es.md) 10 | └── [LocalES](../db_es/local_es.md) 11 | ├── [BaseRedis](../db_redis/base_redis.md) 12 | └── [BaseVectorDB](../db_vector/base_vector_db.md) 13 | └── [VearchDB](../db_vector/vearch_db.md) 14 | 15 | [LocalRedis](../db_redis/local_redis.md) 16 | [JimdbApRedis](../db_redis/jimdb_ap_redis.md) 17 | [VectorToolAsync](../db_vector/vearch_db.md) 18 | ``` 19 | 20 | --- 21 | 22 | ## Introduction 23 | 24 | `BaseVectorDB` defines the abstract base class for vector database services, inheriting from BaseDB and providing the interface contract for Redis operations. 25 | 26 | ## Parameters 27 | 28 | | Parameter | Type / Allowed value | Default | Description | 29 | | --------------- | -------------------- | ------- | ------------------------------------------------------------------------------- | 30 | | *None declared* | — | — | `BaseVectorDB` adds no fields; it is an abstract interface on top of `BaseDB`. | 31 | 32 | ## Methods 33 | 34 | | Method | Coroutine (async) | Return Value | Purpose (concise) | 35 | | -------------------------------------- | ----------------- | ---------------------- | ----------------- | 36 | | `create_space(self, index_name, body)` | Yes | implementation-defined | in inheritance. | 37 | | `query_search(self, index_name, body)` | Yes | implementation-defined | in inheritance. | 38 | 39 | ## Inherited 40 | 41 | Please refer to the [BaseDB](../base_db.md) class for inherited parameters and methods including retry functionality and error handling. 42 | 43 | -------------------------------------------------------------------------------- /docs/docs_zh/3_2_set_global.md: -------------------------------------------------------------------------------- 1 | # 如何设置系统全局数据? 2 | 3 | OxyGent支持使用非常简单的方式设置和修改系统全局数据,这些数据类似于全局变量,能够在MAS中使用`OxyRequest`进行更改与访问。 4 | 5 | 支持的方法包括: 6 | + `get_global_data`:使用`(key,default_value)`按键值访问全局数据 7 | + `set_global_data`:使用`(key,value)`按键值修改全局数据 8 | 9 | 下面使用全局数据实现简单的计数器。 10 | 11 | ```python 12 | class CounterAgent(BaseAgent): 13 | async def execute(self, oxy_request: OxyRequest): 14 | cnt = oxy_request.get_global_data("counter", 0) + 1 # 获取计数 15 | oxy_request.set_global_data("counter", cnt) # 存储计数+1 16 | 17 | return OxyResponse( 18 | state=OxyState.COMPLETED, 19 | output=f"This MAS has been called {cnt} time(s).", 20 | oxy_request=oxy_request, 21 | ) 22 | ``` 23 | 24 | 将这个`CounterAgent`作为`master`,就可以输出MAS被调用的次数。 25 | 26 | ```python 27 | oxy_space = [ 28 | oxy.HttpLLM( 29 | name="default_llm", 30 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 31 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 32 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 33 | llm_params={"temperature": 0.01}, 34 | semaphore=4, 35 | timeout=240, 36 | ), 37 | CounterAgent( 38 | name="master_agent", 39 | is_master=True, 40 | ), 41 | ] 42 | 43 | async def main(): 44 | async with MAS(name="global_demo", oxy_space=oxy_space) as mas: 45 | # 第一次调用 → counter = 1 46 | r1 = await mas.chat_with_agent({"query": "first"}) 47 | print(r1.output) 48 | 49 | # 第二次调用 → counter = 2 (global_data persisted inside MAS) 50 | r2 = await mas.chat_with_agent({"query": "second"}) 51 | print(r2.output) 52 | 53 | # 直接从MAS中获取: 54 | print("Current global_data:", mas.global_data) 55 | # 全局数据的生命周期和MAS相同 56 | ``` 57 | 58 | [上一章:设置数据库](./3_1_set_database.md) 59 | [下一章:创建简单的多agent系统](./6_register_multi_agent.md) 60 | [回到首页](./readme.md) -------------------------------------------------------------------------------- /oxygent/oxy/api_tools/http_tool.py: -------------------------------------------------------------------------------- 1 | """HTTP tool module for making HTTP requests. 2 | 3 | This module provides the HttpTool class, which enables making HTTP requests to external 4 | APIs and services. It supports configurable methods, headers, and parameters with proper 5 | timeout handling. 6 | """ 7 | 8 | import httpx 9 | from pydantic import Field 10 | 11 | from ...schemas import OxyRequest, OxyResponse, OxyState 12 | from ..base_tool import BaseTool 13 | 14 | 15 | class HttpTool(BaseTool): 16 | """Tool for making HTTP requests to external APIs and services. 17 | 18 | Attributes: 19 | method (str): HTTP method to use. Defaults to "GET". 20 | url (str): Target URL for the HTTP request. 21 | headers (dict): HTTP headers to include in the request. 22 | default_params (dict): Default parameters that will be merged with 23 | request arguments. 24 | """ 25 | 26 | method: str = Field("GET", description="HTTP method to use") 27 | url: str = Field("", description="Target URL for the HTTP request") 28 | headers: dict = Field(default_factory=dict, description="HTTP headers to include") 29 | default_params: dict = Field( 30 | default_factory=dict, description="Default request parameters" 31 | ) 32 | 33 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 34 | """Execute the HTTP request.""" 35 | # Merge default parameters with request arguments 36 | params = self.default_params.copy() 37 | params.update(oxy_request.arguments) 38 | 39 | # Make HTTP request with timeout handling 40 | async with httpx.AsyncClient(timeout=self.timeout) as client: 41 | http_response = await client.get( 42 | self.url, params=params, headers=self.headers 43 | ) 44 | return OxyResponse(state=OxyState.COMPLETED, output=http_response.text) 45 | -------------------------------------------------------------------------------- /docs/development/api/tools/base_tools.md: -------------------------------------------------------------------------------- 1 | # BaseTool 2 | --- 3 | The position of the class is: 4 | 5 | 6 | ```markdown 7 | [Oxy](../agent/base_oxy.md) 8 | ├── [BaseTool](../tools/base_tools.md) 9 | ├── [MCPTool](../tools/mcp_tool.md) 10 | ├── [BaseMCPClient](../tools/base_mcp_client.md) 11 | │ ├──[StdioMCPClient](../tools/stdio_mcp_client.md) 12 | │ ├──[SSEMCPClient](../tools/sse_mcp_client.md) 13 | │ └──[StreamableMCPClient](../tools/streamable_mcp_client.md) 14 | ├── [HttpTool](../api_tools/http_tool.md) 15 | ├── [FunctionHub](../function_tools/function_hub.md) 16 | └── [FunctionTool](../function_tools/function_tool.md) 17 | └── [BaseFlow](../agent/base_flow.md) 18 | ``` 19 | 20 | --- 21 | 22 | ## Introduce 23 | 24 | `BaseTool` is the abstract base class for all tools in the OxyGent system. It provides common functionality for tool implementations including permission control, category identification, and execution timeout management. Tools are specialized Oxy instances that typically require permissions and have shorter timeout periods. 25 | 26 | ## Parameters 27 | 28 | | Parameter | Type / Allowed value | Default | Description | 29 | | --------- | -------------------- | ------- | ----------- | 30 | | `is_permission_required` | `bool` | `True` | Whether permission is required for execution | 31 | | `category` | `str` | `"tool"` | Tool category identifier | 32 | | `timeout` | `float` | `60` | Execution timeout in seconds | 33 | 34 | ## Methods 35 | 36 | | Method | Coroutine (async) | Return Value | Purpose | 37 | | ------ | ----------------- | ------------ | ------- | 38 | | `_execute(oxy_request)` | Yes | `OxyResponse` | **Abstract method** - Execute the tool request (must be implemented by subclasses) | 39 | 40 | ## Inherited 41 | Please refer to the [Oxy](../agents/base_oxy.md) class for inherited parameters and methods. 42 | 43 | ## Usage 44 | 45 | The class `BaseTool` must be inherited. -------------------------------------------------------------------------------- /examples/mcp_tools/train_ticket_agent_demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, Config, oxy, preset_tools 5 | 6 | Config.set_agent_llm_model("default_llm") 7 | 8 | oxy_space = [ 9 | oxy.HttpLLM( 10 | name="default_llm", 11 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 12 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 13 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 14 | llm_params={"temperature": 0.01}, 15 | semaphore=4, 16 | ), 17 | preset_tools.train_ticket_tools, 18 | oxy.ReActAgent( 19 | name="train_ticket_agent", 20 | desc="A tool for train ticket", 21 | is_master=True, 22 | tools=["train_ticket_tools"], 23 | trust_mode=False, 24 | timeout=100, 25 | ) 26 | ] 27 | 28 | 29 | async def main(): 30 | async with MAS(oxy_space=oxy_space) as mas: 31 | await mas.start_web_service( 32 | first_query="请帮我查询明天从西安到北京的火车票", 33 | welcome_message="您好我是火车票查询智能体,非常高兴为您服务。您可以这样问我:\n" 34 | "\"明天从西安到北京的火车票\"\n" 35 | "\"下周一上海到北京的卧铺火车票\"\n" 36 | "\"后天西安到杭州的火车票多少钱\"\n" 37 | "\"明天北京到西安的火车最短运行时间是多长\"\n\n" 38 | "Hello, I am a train ticket inquiry assistant, and I am very happy to assist you. " 39 | "You can ask me questions like:\n" 40 | "\"Train tickets from Xi'an to Beijing tomorrow\"\n" 41 | "\"Sleeper train tickets from Shanghai to Beijing next Monday\"\n" 42 | "\"How much are train tickets from Xi'an to Hangzhou the day after tomorrow\"\n" 43 | "\"What is the shortest travel time for trains from Beijing to Xi'an tomorrow\"\n" 44 | ) 45 | 46 | 47 | if __name__ == "__main__": 48 | asyncio.run(main()) 49 | -------------------------------------------------------------------------------- /test/unittest/test_base_flow.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for BaseFlow 3 | """ 4 | 5 | import pytest 6 | 7 | from oxygent.oxy.base_flow import BaseFlow 8 | from oxygent.schemas import OxyRequest, OxyResponse, OxyState 9 | 10 | 11 | # ────────────────────────────────────────────────────────────────────────────── 12 | # ❶ Dummy Flow 13 | # ────────────────────────────────────────────────────────────────────────────── 14 | class DummyFlow(BaseFlow): 15 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 16 | return OxyResponse( 17 | state=OxyState.COMPLETED, 18 | output=oxy_request.arguments.get("query", ""), 19 | oxy_request=oxy_request, 20 | ) 21 | 22 | 23 | # ────────────────────────────────────────────────────────────────────────────── 24 | # ❷ Fixtures 25 | # ────────────────────────────────────────────────────────────────────────────── 26 | @pytest.fixture 27 | def dummy_flow(): 28 | return DummyFlow(name="dummy_flow", desc="Unit-Test Flow") 29 | 30 | 31 | @pytest.fixture 32 | def oxy_request(): 33 | return OxyRequest( 34 | arguments={"query": "hello"}, 35 | caller="user", 36 | caller_category="user", 37 | current_trace_id="trace123", 38 | ) 39 | 40 | 41 | # ────────────────────────────────────────────────────────────────────────────── 42 | # ❸ Tests 43 | # ────────────────────────────────────────────────────────────────────────────── 44 | def test_initialization(dummy_flow): 45 | assert dummy_flow.is_permission_required is True 46 | assert dummy_flow.name == "dummy_flow" 47 | 48 | 49 | @pytest.mark.asyncio 50 | async def test_execute_lifecycle(dummy_flow, oxy_request): 51 | resp = await dummy_flow.execute(oxy_request) 52 | 53 | assert isinstance(resp, OxyResponse) 54 | assert resp.state is OxyState.COMPLETED 55 | assert resp.output == "hello" 56 | assert resp.oxy_request.call_stack[-1] == "dummy_flow" 57 | -------------------------------------------------------------------------------- /docs/development/api/tools/base_mcp_client.md: -------------------------------------------------------------------------------- 1 | # BaseTool 2 | --- 3 | The position of the class is: 4 | 5 | ```markdown 6 | [Oxy](../agent/base_oxy.md) 7 | ├── [BaseTool](../tools/base_tools.md) 8 | ├── [MCPTool](../tools/mcp_tool.md) 9 | ├── [BaseMCPClient](../tools/base_mcp_client.md) 10 | │ ├──[StdioMCPClient](../tools/stdio_mcp_client.md) 11 | │ ├──[SSEMCPClient](../tools/sse_mcp_client.md) 12 | │ └──[StreamableMCPClient](../tools/streamable_mcp_client.md) 13 | ├── [HttpTool](../api_tools/http_tool.md) 14 | ├── [FunctionHub](../function_tools/function_hub.md) 15 | └── [FunctionTool](../function_tools/function_tool.md) 16 | └── [BaseFlow](../agent/base_flow.md) 17 | ``` 18 | 19 | --- 20 | 21 | ## Introduce 22 | 23 | `BaseMCPClient` is the base client for Model Context Protocol (MCP) servers. It provides a foundation for connecting to and interacting with MCP servers, handling server lifecycle management, tool discovery, dynamic tool registration, and tool execution through the MCP protocol. It inherits from BaseTool and implements the MCP client functionality. 24 | 25 | ## Parameters 26 | 27 | 28 | | Parameter | Type / Allowed value | Default | Description | 29 | | --------- | -------------------- | ------- | ----------- | 30 | | `included_tool_name_list` | `list` | `[]` | List of tool names discovered from the MCP server | 31 | 32 | ## Methods 33 | 34 | 35 | | Method | Coroutine (async) | Return Value | Purpose | 36 | | ------ | ----------------- | ------------ | ------- | 37 | | `list_tools()` | Yes | `None` | Discover and register tools from the MCP server | 38 | | `_execute(oxy_request)` | Yes | `OxyResponse` | Execute a tool call through the MCP server | 39 | | `cleanup()` | Yes | `None` | Clean up MCP server resources and connections | 40 | 41 | ## Inherited 42 | Please refer to the [BaseTool](./base_tools.md) class for inherited parameters and methods. 43 | 44 | ## Usage 45 | 46 | The class `BaseMCPClient` must be inherited. -------------------------------------------------------------------------------- /CONTRIBUTING_zh.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | [English](./CONTRIBUTING.md) | [中文](./CONTRIBUTING_zh.md) 16 | 17 | # OxyGent 贡献指南 18 | 19 | OxyGent致力于为每一位用户和开发者提供开放的智能体系统体验,因此无论您是资深智能体系统开发者还是专注于MAS应用的用户,我们都欢迎您参与我们的项目。 20 | 您可以通过以下方法为项目作出贡献: 21 | 22 | + 撰写/翻译/修改文档 23 | + 提出或回答问题 24 | + 提供使用或测试样例 25 | + 提供建议或其他评论 26 | + 参与[issues](https://github.com/jd-opensource/OxyGent/issues) 或[discussions](https://github.com/jd-opensource/OxyGent/discussions) 27 | + 提交Pull request 28 | + 分享相关研究或应用场景 29 | + 其他任何对OxyGent的帮助 30 | 31 | 如果您希望参与OxyGent的开发,请参考以下提示: 32 | 33 | ## 1. 选择参与贡献的issue 34 | + 您可以选择带有`PR welcome`标签的issue,包括: 35 | + 可复现的bug 36 | + 计划实现的功能 37 | 38 | ## 2. 配置开发环境 39 | + 在开发之前,可以参考我们的 **[文档](http://oxygent.jd.com/docs/)** 40 | + 关于环境配置,参见 **[Readme file](/README.md)** 41 | 42 | ## 3. 项目构建和运行 43 | + 您可以运行如下样例: 44 | ```bash 45 | python demo.py 46 | ``` 47 | ```bash 48 | python -m examples.agents.demo_single_agent 49 | ``` 50 | 51 | ## 4. 测试 52 | + 在提交pr之前,可以使用`pytest`运行项目本地测试: 53 | ```bash 54 | pip install pytest pytest-asyncio 55 | ``` 56 | + 格式化代码 57 | ```bash 58 | ruff format . 59 | docformatter -r -i --wrap-summaries 88 --wrap-descriptions 88 oxygent/ 60 | ``` 61 | + 运行单元测试: 62 | ```bash 63 | pytest oxygent/test/unittest 64 | ``` 65 | + 运行样例综合测试(可选): 66 | ```bash 67 | pytest oxygent/test/integration 68 | ``` 69 | 在pr提交之后,我们会对代码进行格式化及进一步测试。 70 | 我们的测试目前还很不完善,因此欢迎开发者为测试作出贡献! -------------------------------------------------------------------------------- /test/unittest/test_workflow.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for Workflow 3 | """ 4 | 5 | import pytest 6 | 7 | from oxygent.oxy.flows.workflow import Workflow 8 | from oxygent.schemas import OxyRequest, OxyResponse, OxyState 9 | 10 | 11 | # ────────────────────────────────────────────────────────────────────────────── 12 | # ❶ Dummy workflow 13 | # ────────────────────────────────────────────────────────────────────────────── 14 | async def echo_workflow(req: OxyRequest) -> str: 15 | return f"echo({req.arguments.get('query')})" 16 | 17 | 18 | # ────────────────────────────────────────────────────────────────────────────── 19 | # ❷ Fixtures 20 | # ────────────────────────────────────────────────────────────────────────────── 21 | @pytest.fixture 22 | def workflow(): 23 | return Workflow(name="wf", desc="UT Workflow", func_workflow=echo_workflow) 24 | 25 | 26 | @pytest.fixture 27 | def oxy_request(): 28 | return OxyRequest( 29 | arguments={"query": "hello"}, 30 | caller="user", 31 | caller_category="user", 32 | current_trace_id="trace123", 33 | ) 34 | 35 | 36 | # ────────────────────────────────────────────────────────────────────────────── 37 | # ❸ Tests 38 | # ────────────────────────────────────────────────────────────────────────────── 39 | def test_initialization(workflow): 40 | assert workflow.name == "wf" 41 | assert workflow.func_workflow is echo_workflow 42 | 43 | 44 | @pytest.mark.asyncio 45 | async def test_execute_success(workflow, oxy_request): 46 | resp: OxyResponse = await workflow.execute(oxy_request) 47 | 48 | assert resp.state is OxyState.COMPLETED 49 | assert resp.output == "echo(hello)" 50 | assert resp.oxy_request.call_stack[-1] == "wf" 51 | 52 | 53 | # TODO: add error 54 | # @pytest.mark.asyncio 55 | # async def test_execute_without_func_raises(oxy_request): 56 | # bad_flow = Workflow(name="bad", desc="no func") 57 | # with pytest.raises(TypeError): 58 | # await bad_flow.execute(oxy_request) 59 | -------------------------------------------------------------------------------- /examples/backend/demo_global_data.py: -------------------------------------------------------------------------------- 1 | """ 2 | global_data_demo.py 3 | ─────────────────── 4 | A minimal example (modelled after single_demo.py) that shows how to 5 | read/write the new MAS.global_data store from inside an agent. 6 | 7 | • The agent keeps a simple “call counter” in global_data["counter"]. 8 | • Every time you call the agent it increments the counter and 9 | returns the current value. 10 | """ 11 | 12 | import asyncio 13 | import os 14 | 15 | from oxygent import MAS, OxyRequest, OxyResponse, oxy 16 | from oxygent.oxy.agents.base_agent import BaseAgent 17 | from oxygent.schemas import OxyState 18 | 19 | 20 | class CounterAgent(BaseAgent): 21 | async def execute(self, oxy_request: OxyRequest): 22 | cnt = oxy_request.get_global_data("counter", 0) + 1 23 | oxy_request.set_global_data("counter", cnt) 24 | 25 | return OxyResponse( 26 | state=OxyState.COMPLETED, 27 | output=f"This MAS has been called {cnt} time(s).", 28 | oxy_request=oxy_request, 29 | ) 30 | 31 | 32 | oxy_space = [ 33 | oxy.HttpLLM( 34 | name="default_llm", 35 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 36 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 37 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 38 | ), 39 | CounterAgent( 40 | name="master_agent", # mark as master so chat_with_agent works 41 | is_master=True, 42 | ), 43 | ] 44 | 45 | 46 | async def main(): 47 | async with MAS(oxy_space=oxy_space) as mas: 48 | # first call → counter = 1 49 | r1 = await mas.chat_with_agent({"query": "first"}) 50 | print(r1.output) 51 | 52 | # second call → counter = 2 (global_data persisted inside MAS) 53 | r2 = await mas.chat_with_agent({"query": "second"}) 54 | print(r2.output) 55 | 56 | # you can also inspect the entire dict: 57 | print("Current global_data:", mas.global_data) 58 | 59 | 60 | if __name__ == "__main__": 61 | asyncio.run(main()) 62 | -------------------------------------------------------------------------------- /docs/development/api/function_tools/function_tool.md: -------------------------------------------------------------------------------- 1 | # FunctionTool 2 | --- 3 | The position of the class is: 4 | 5 | 6 | ```markdown 7 | [Oxy](../agent/base_oxy.md) 8 | ├── [BaseTool](../tools/base_tools.md) 9 | ├── [MCPTool](../tools/mcp_tool.md) 10 | ├── [BaseMCPClient](../tools/base_mcp_client.md) 11 | │ ├──[StdioMCPClient](../tools/stdio_mcp_client.md) 12 | │ ├──[SSEMCPClient](../tools/sse_mcp_client.md) 13 | │ └──[StreamableMCPClient](../tools/streamable_mcp_client.md) 14 | ├── [HttpTool](../api_tools/http_tool.md) 15 | ├── [FunctionHub](../function_tools/function_hub.md) 16 | └── [FunctionTool](../function_tools/function_tool.md) 17 | └── [BaseFlow](../agent/base_flow.md) 18 | ``` 19 | 20 | --- 21 | 22 | ## Introduce 23 | 24 | `FunctionTool` is a tool that wraps Python functions for execution within the OxyGent system. It automatically extracts input schemas from function signatures and handles execution with proper error handling, providing a bridge between regular Python functions and the OxyGent tool system. 25 | 26 | ## Parameters 27 | 28 | | Parameter | Type / Allowed value | Default | Description | 29 | | --------- | -------------------- | ------- | ----------- | 30 | | `is_permission_required` | `bool` | `True` | Whether permission is required for execution | 31 | | `func_process` | `Optional[Callable]` | `None` | The Python function to execute | 32 | | `needs_oxy_request` | `bool` | `False` | Whether this tool needs oxy_request parameter | 33 | 34 | ## Methods 35 | 36 | | Method | Coroutine (async) | Return Value | Purpose | 37 | | ------ | ----------------- | ------------ | ------- | 38 | | `_extract_input_schema(func)` | No | `dict` | Extract input schema from function signature with parameters and types | 39 | | `_execute(oxy_request)` | Yes | `OxyResponse` | Execute the wrapped function with provided arguments and error handling | 40 | 41 | ## Inherited 42 | Please refer to the [BaseTool](../tools/base_tools.md) class for inherited parameters and methods. 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/development/api/tools/mcp_tool.md: -------------------------------------------------------------------------------- 1 | # MCPTool 2 | --- 3 | The position of the class is: 4 | 5 | 6 | ```markdown 7 | [Oxy](../agent/base_oxy.md) 8 | ├── [BaseTool](../tools/base_tools.md) 9 | ├── [MCPTool](../tools/mcp_tool.md) 10 | ├── [BaseMCPClient](../tools/base_mcp_client.md) 11 | │ ├──[StdioMCPClient](../tools/stdio_mcp_client.md) 12 | │ ├──[SSEMCPClient](../tools/sse_mcp_client.md) 13 | │ └──[StreamableMCPClient](../tools/streamable_mcp_client.md) 14 | ├── [HttpTool](../api_tools/http_tool.md) 15 | ├── [FunctionHub](../function_tools/function_hub.md) 16 | └── [FunctionTool](../function_tools/function_tool.md) 17 | └── [BaseFlow](../agent/base_flow.md) 18 | ``` 19 | 20 | --- 21 | 22 | ## Introduce 23 | 24 | `MCPTool` is an individual tool proxy for MCP server tools in the OxyGent system. It represents a specific tool discovered from an MCP server and acts as a lightweight proxy that delegates actual execution to the parent MCP client while providing the standard BaseTool interface. Each MCPTool serves as a bridge between the OxyGent tool system and Model Context Protocol servers. 25 | 26 | ## Parameters 27 | 28 | 29 | | Parameter | Type / Allowed value | Default | Description | 30 | | --------- | -------------------- | ------- | ----------- | 31 | | `is_permission_required` | `bool` | `True` | Whether the tool requires explicit permission before execution | 32 | | `mcp_client` | `Any` | `None` | Reference to the parent MCP client that handles actual execution | 33 | | `server_name` | `str` | `""` | Name of the MCP server that provides this tool | 34 | 35 | ## Methods 36 | 37 | 38 | | Method | Coroutine (async) | Return Value | Purpose | 39 | | ------ | ----------------- | ------------ | ------- | 40 | | `_execute(oxy_request)` | Yes | `OxyResponse` | Execute the MCP tool by delegating to the parent MCP client | 41 | 42 | ## Inherited 43 | Please refer to the [BaseTool](./base_tools.md) class for inherited parameters and methods. 44 | 45 | ## Usage 46 | 47 | The class `MCPTool` must be inherited. 48 | --------------------------------------------------------------------------------