├── 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 | Mermaid 流程图生成器 7 | 8 | 9 | 10 |
11 |

Mermaid 流程图生成器

12 | 13 |
14 |

输入流程图描述

15 | 16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/backend/demo_batch_and_semaphore.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 | semaphore=4, # limit concurrency to 4 13 | ), 14 | oxy.ChatAgent( 15 | name="chat_agent", 16 | llm_model="default_llm", 17 | semaphore=6, # limit concurrency to 6 18 | ), 19 | ] 20 | 21 | 22 | async def main(): 23 | async with MAS(oxy_space=oxy_space) as mas: 24 | outs = await mas.start_batch_processing(["hello"] * 10, return_trace_id=True) 25 | [print(out) for out in outs] 26 | 27 | 28 | if __name__ == "__main__": 29 | asyncio.run(main()) 30 | -------------------------------------------------------------------------------- /oxygent/web/css/gantt.css: -------------------------------------------------------------------------------- 1 | .section0 { 2 | fill:#ffffff !important; 3 | } 4 | .section2 { 5 | fill:#ffffff !important; 6 | } 7 | /* 修改第1种section样式 */ 8 | .task0 { 9 | fill: #FFE6CC !important; 10 | stroke-width: 1px !important; 11 | } 12 | 13 | .task1 { 14 | fill: #DAE8FC !important; 15 | stroke-width: 1px !important; 16 | } 17 | 18 | .task2 { 19 | fill: #D6E8D4 !important; 20 | stroke-width: 1px !important; 21 | } 22 | 23 | .task3 { 24 | fill: #F8CECC !important; 25 | stroke-width: 1px !important; 26 | } 27 | 28 | 29 | .tick { 30 | stroke: lightgrey !important; 31 | shape-rendering: crispEdges !important; 32 | } 33 | 34 | 35 | #flowchart-container-gantt .mermaid-selected { 36 | fill: #5048E5 !important; 37 | stroke: #5048E5 !important; 38 | } 39 | #flowchart-container-gantt .taskText.clickable.mermaid-selected-text { 40 | fill: white !important; 41 | } -------------------------------------------------------------------------------- /oxygent/web/image/group_recent.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/oxy/llms/mock_llm.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Callable 3 | 4 | from pydantic import Field 5 | 6 | from ...schemas import OxyRequest, OxyResponse, OxyState 7 | from .base_llm import BaseLLM 8 | 9 | 10 | class MockLLM(BaseLLM): 11 | func_mock_process: Callable = Field( 12 | None, exclude=True, description="Mock processing function" 13 | ) 14 | 15 | def __init__(self, **kwargs): 16 | super().__init__(**kwargs) 17 | 18 | if self.func_mock_process is None: 19 | self.func_mock_process = self._mock_process 20 | 21 | async def _mock_process(self, oxy_request: OxyRequest): 22 | await asyncio.sleep(1) 23 | return "output" 24 | 25 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 26 | output = await self.func_mock_process(oxy_request) 27 | return OxyResponse(state=OxyState.COMPLETED, output=output) 28 | -------------------------------------------------------------------------------- /oxygent/web/image/pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mcp_servers/math_tools.py: -------------------------------------------------------------------------------- 1 | import math 2 | from decimal import Decimal, getcontext 3 | 4 | from mcp.server.fastmcp import FastMCP 5 | from pydantic import Field 6 | 7 | mcp = FastMCP() 8 | 9 | 10 | @mcp.tool(description="Power calculator tool") 11 | def power( 12 | n: int = Field(description="base"), 13 | m: int = Field(description="index", default=2), 14 | ) -> int: 15 | return math.pow(n, m) 16 | 17 | 18 | @mcp.tool(description="Pi tool") 19 | def calc_pi( 20 | prec: int = Field(description="How many digits after the dot"), 21 | ) -> float: 22 | getcontext().prec = prec 23 | x = 0 24 | for k in range(int(prec / 8) + 1): 25 | a = 2 * Decimal.sqrt(Decimal(2)) / 9801 26 | b = math.factorial(4 * k) * (1103 + 26390 * k) 27 | c = pow(math.factorial(k), 4) * pow(396, 4 * k) 28 | x = x + a * b / c 29 | return 1 / x 30 | 31 | 32 | if __name__ == "__main__": 33 | mcp.run() 34 | -------------------------------------------------------------------------------- /docs/docs_zh/2_2_manage_tools.md: -------------------------------------------------------------------------------- 1 | # 如何管理智能体使用工具的行为? 2 | 3 | 在默认情况下,agent对工具的调用由内部的LLM负责,但是OxyGent提供了一系列参数帮助您管理agent调用工具的方案,以下是您可能用到的参数: 4 | 5 | + 允许/禁止智能体使用工具 6 | 7 | ```python 8 | oxy.LocalAgent( 9 | name="time_agent", 10 | desc="A tool that can get current time", 11 | tools=["time_tools"], # 允许使用 12 | except_tools=["math_tools"], #禁止使用 13 | ), 14 | ``` 15 | 16 | + 管理智能体检索工具 17 | 18 | ```python 19 | oxy.LocalAgent( 20 | name="time_agent", 21 | desc="A tool that can get current time", 22 | tools=["time_tools"], 23 | is_sourcing_tools = True, # 是否检索工具 24 | is_retain_subagent_in_toolset = True, # 是否将subagent放入工具集 25 | top_k_tools = 10; #检索返回的工具数量 26 | is_retrieve_even_if_tools_scarce = True, # 是否在工具数量不足时保持检索 27 | ), 28 | ``` 29 | 30 | [上一章:使用MCP自定义工具](./2_4_use_mcp_tools.md) 31 | [下一章:设置OxyGent Config](./3_set_config.md) 32 | [回到首页](./readme.md) -------------------------------------------------------------------------------- /examples/agents/demo_document_analysis_agent.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={"stream": True}, 15 | ), 16 | preset_tools.document_tools, 17 | oxy.ReActAgent( 18 | name="document_agent", 19 | desc="A tool that can process and analyze documents (PDF, Word, Excel, etc.)", 20 | tools=["document_tools"], 21 | ), 22 | ] 23 | 24 | 25 | async def main(): 26 | async with MAS(oxy_space=oxy_space) as mas: 27 | await mas.start_web_service(first_query="hello") 28 | 29 | 30 | if __name__ == "__main__": 31 | asyncio.run(main()) 32 | -------------------------------------------------------------------------------- /mcp_servers/math_tools_sse.py: -------------------------------------------------------------------------------- 1 | import math 2 | from decimal import Decimal, getcontext 3 | 4 | from mcp.server.fastmcp import FastMCP 5 | from pydantic import Field 6 | 7 | mcp = FastMCP(port=8000) 8 | 9 | 10 | @mcp.tool(description="Power calculator tool") 11 | def power( 12 | n: int = Field(description="base"), 13 | m: int = Field(description="index", default=2), 14 | ) -> int: 15 | return math.pow(n, m) 16 | 17 | 18 | @mcp.tool(description="Pi tool") 19 | def calc_pi( 20 | prec: int = Field(description="How many digits after the dot"), 21 | ) -> float: 22 | getcontext().prec = prec 23 | x = 0 24 | for k in range(int(prec / 8) + 1): 25 | a = 2 * Decimal.sqrt(Decimal(2)) / 9801 26 | b = math.factorial(4 * k) * (1103 + 26390 * k) 27 | c = pow(math.factorial(k), 4) * pow(396, 4 * k) 28 | x = x + a * b / c 29 | return 1 / x 30 | 31 | 32 | if __name__ == "__main__": 33 | mcp.run(transport="sse") 34 | -------------------------------------------------------------------------------- /oxygent/web/image/group_save.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mcp_servers/math_tools_streamable.py: -------------------------------------------------------------------------------- 1 | import math 2 | from decimal import Decimal, getcontext 3 | 4 | from mcp.server.fastmcp import FastMCP 5 | from pydantic import Field 6 | 7 | mcp = FastMCP(port=8000) 8 | 9 | 10 | @mcp.tool(description="Power calculator tool") 11 | def power( 12 | n: int = Field(description="base"), 13 | m: int = Field(description="index", default=2), 14 | ) -> int: 15 | return math.pow(n, m) 16 | 17 | 18 | @mcp.tool(description="Pi tool") 19 | def calc_pi( 20 | prec: int = Field(description="How many digits after the dot"), 21 | ) -> float: 22 | getcontext().prec = prec 23 | x = 0 24 | for k in range(int(prec / 8) + 1): 25 | a = 2 * Decimal.sqrt(Decimal(2)) / 9801 26 | b = math.factorial(4 * k) * (1103 + 26390 * k) 27 | c = pow(math.factorial(k), 4) * pow(396, 4 * k) 28 | x = x + a * b / c 29 | return 1 / x 30 | 31 | 32 | if __name__ == "__main__": 33 | mcp.run(transport="streamable-http") 34 | -------------------------------------------------------------------------------- /docs/docs_zh/0_1_demo.md: -------------------------------------------------------------------------------- 1 | # 如何运行demo? 2 | 3 | 使用`python`直接运行我们的任意一个demo。 4 | 5 | ### 为什么下载后直接运行demo系统不启动? 6 | --- 7 | OxyGent只是智能体系统框架,本身不提供LLM服务。因此您需要在`.env`中设置自己的LLM api。 8 | ```bash 9 | export DEFAULT_LLM_API_KEY="your_api_key" 10 | export DEFAULT_LLM_BASE_URL="your_base_url" # if you want to use a custom base URL 11 | export DEFAULT_LLM_MODEL_NAME="your_model_name" 12 | ``` 13 | ```bash 14 | # create a .env file 15 | DEFAULT_LLM_API_KEY="your_api_key" 16 | DEFAULT_LLM_BASE_URL="your_base_url" 17 | DEFAULT_LLM_MODEL_NAME="your_model_name" 18 | ``` 19 | 20 | ### 为什么我填好了环境变量,但是运行demo时报404? 21 | --- 22 | 首先,请您检查是否引入了不存在的环境变量。 23 | 如果不能解决问题,请查看[关于llm api](./1_2_select_llm.md)。 24 | 25 | ### 如何获得帮助? 26 | --- 27 | 您可以通过以下方式获得帮助: 28 | 29 | + 在Github提交issue 30 | + 加入交流讨论群(参见readme) 31 | + 如果您有企业内部Slack,请直接联系OxyGent Core团队。 32 | 33 | OxyGent未来将提供更完整的社区服务。 34 | 35 | [上一章:安装OxyGent](./0_install.md) 36 | [下一章:创建第一个智能体](./1_register_single_agent.md) 37 | [回到首页](./readme.md) -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Oxygent CI 2 | 3 | on: 4 | push: 5 | branches: [main, dev] 6 | pull_request: 7 | branches: [main, dev] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | python-version: ["3.10"] 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install pytest playwright pytest-asyncio 30 | pip install ruff docformatter 31 | pip install -r requirements.txt 32 | 33 | - name: Format code with Ruff (auto-fix) 34 | run: | 35 | ruff format . 36 | 37 | - name: Run Unit Tests 38 | run: | 39 | pytest test/unittest 40 | -------------------------------------------------------------------------------- /oxygent/web/image/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/stop-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/backend/demo_logger_setup.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, OxyRequest, oxy 5 | from oxygent.log_setup import setup_logging 6 | 7 | logger = setup_logging() 8 | 9 | 10 | def update_query(oxy_request: OxyRequest) -> OxyRequest: 11 | query = oxy_request.get_query() 12 | logger.info(f"The current query is: {query}") 13 | return oxy_request 14 | 15 | 16 | oxy_space = [ 17 | oxy.HttpLLM( 18 | name="default_llm", 19 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 20 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 21 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 22 | ), 23 | oxy.ChatAgent( 24 | name="master_agent", 25 | llm_model="default_llm", 26 | func_process_input=update_query, 27 | ), 28 | ] 29 | 30 | 31 | async def main(): 32 | async with MAS(oxy_space=oxy_space) as mas: 33 | await mas.start_web_service(first_query="hello") 34 | 35 | 36 | if __name__ == "__main__": 37 | asyncio.run(main()) 38 | -------------------------------------------------------------------------------- /oxygent/preset_tools/baidu_search_tools.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import List, Dict 3 | 4 | from oxygent.oxy import FunctionHub 5 | 6 | try: 7 | from baidusearch.baidusearch import search # type: ignore 8 | except ImportError: 9 | raise ImportError( 10 | "`baidusearch` not installed. Please install using `pip install baidusearch`") 11 | 12 | baidu_search_tools = FunctionHub(name="baidu_search_tools") 13 | 14 | 15 | @baidu_search_tools.tool( 16 | description="Search the query to baidu search" 17 | ) 18 | def search_baidu(query: str) -> str: 19 | results = search(keyword=query, num_results=10) 20 | 21 | res: List[Dict[str, str]] = [] 22 | for idx, item in enumerate(results, 1): 23 | res.append( 24 | { 25 | "title": item.get("title", ""), 26 | "url": item.get("url", ""), 27 | "abstract": item.get("abstract", ""), 28 | "rank": str(idx), 29 | } 30 | ) 31 | return json.dumps(res, indent=2, ensure_ascii=False) 32 | -------------------------------------------------------------------------------- /oxygent/web/css/style_load1.css: -------------------------------------------------------------------------------- 1 | .loading{ 2 | width: 80px; 3 | height: 40px; 4 | margin: 0 auto; 5 | margin-top:100px; 6 | } 7 | .loading span{ 8 | display: inline-block; 9 | width: 8px; 10 | height: 100%; 11 | border-radius: 4px; 12 | background: #5048E5; 13 | -webkit-animation: load 1s ease infinite; 14 | animation: load 1s ease infinite; 15 | } 16 | @-webkit-keyframes load{ 17 | 0%, 100%{ 18 | height: 40px; 19 | background: #5048E5; 20 | } 21 | 50%{ 22 | height: 70px; 23 | margin: -15px 0; 24 | background: white; 25 | } 26 | } 27 | .loading span:nth-child(2){ 28 | -webkit-animation-delay:0.2s; 29 | animation-delay:0.2s; 30 | } 31 | .loading span:nth-child(3){ 32 | -webkit-animation-delay:0.4s; 33 | animation-delay:0.4s; 34 | } 35 | .loading span:nth-child(4){ 36 | -webkit-animation-delay:0.6s; 37 | animation-delay:0.6s; 38 | } 39 | .loading span:nth-child(5){ 40 | -webkit-animation-delay:0.8s; 41 | animation-delay:0.8s; 42 | } -------------------------------------------------------------------------------- /oxygent/web/image/arrow-left-double.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/arrow-right-double.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/backend/demo_attachment.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 | is_multimodal_supported=True, 13 | ), 14 | oxy.ChatAgent( 15 | name="qa_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 | # 直接调用 24 | payload = { 25 | "query": "Introduce the content of the file", 26 | "attachments": ["README.md"], 27 | } 28 | oxy_response = await mas.chat_with_agent(payload=payload) 29 | print("LLM: ", oxy_response.output) 30 | # 启动web服务 31 | await mas.start_web_service(first_query="Introduce the content of the file") 32 | 33 | 34 | if __name__ == "__main__": 35 | asyncio.run(main()) 36 | -------------------------------------------------------------------------------- /examples/backend/demo_process_message.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, Config, OxyRequest, oxy 5 | 6 | Config.set_message_is_show_in_terminal(True) 7 | 8 | 9 | def process_message(dict_message: dict, oxy_request: OxyRequest) -> dict: 10 | if dict_message["data"]["type"] == "stream": 11 | dict_message["data"]["content"]["delta"] += "-" 12 | return dict_message 13 | 14 | 15 | oxy_space = [ 16 | oxy.HttpLLM( 17 | name="default_llm", 18 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 19 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 20 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 21 | llm_params={"stream": True}, 22 | ), 23 | oxy.ChatAgent( 24 | name="qa_agent", 25 | llm_model="default_llm", 26 | ), 27 | ] 28 | 29 | 30 | async def main(): 31 | async with MAS(oxy_space=oxy_space, func_process_message=process_message) as mas: 32 | await mas.start_web_service(first_query="hello") 33 | 34 | 35 | if __name__ == "__main__": 36 | asyncio.run(main()) 37 | -------------------------------------------------------------------------------- /oxygent/web/css/style_load2.css: -------------------------------------------------------------------------------- 1 | .loading{ 2 | width: 150px; 3 | height: 15px; 4 | margin: 0 auto; 5 | margin-top:100px; 6 | } 7 | .loading span{ 8 | display: inline-block; 9 | width: 15px; 10 | height: 100%; 11 | margin-right: 5px; 12 | background: lightyellow; 13 | -webkit-transform-origin: right bottom; 14 | -webkit-animation: load 1s ease infinite; 15 | } 16 | .loading span:last-child{ 17 | margin-right: 0px; 18 | } 19 | @-webkit-keyframes load{ 20 | 0%{ 21 | opacity: 1; 22 | -webkit-transform: scale(1); 23 | } 24 | 100%{ 25 | opacity: 0; 26 | -webkit-transform: rotate(90deg) scale(.3); 27 | } 28 | } 29 | .loading span:nth-child(1){ 30 | -webkit-animation-delay:0.13s; 31 | } 32 | .loading span:nth-child(2){ 33 | -webkit-animation-delay:0.26s; 34 | } 35 | .loading span:nth-child(3){ 36 | -webkit-animation-delay:0.39s; 37 | } 38 | .loading span:nth-child(4){ 39 | -webkit-animation-delay:0.52s; 40 | } 41 | .loading span:nth-child(5){ 42 | -webkit-animation-delay:0.65s; 43 | } -------------------------------------------------------------------------------- /oxygent/web/image/group_open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/agents/demo_react_agent.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, OxyRequest, oxy 5 | 6 | 7 | # 自定义reflexion函数 8 | def master_reflexion(response: str, oxy_request: OxyRequest) -> str: 9 | import re 10 | 11 | pattern = r"^[-+]?(\d+(\.\d*)?|\.\d+)$" 12 | if not bool(re.match(pattern, response)): 13 | return "仅回答数字" 14 | 15 | 16 | oxy_space = [ 17 | oxy.HttpLLM( 18 | name="default_llm", 19 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 20 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 21 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 22 | ), 23 | oxy.ReActAgent( 24 | name="master_agent", 25 | llm_model="default_llm", 26 | func_reflexion=master_reflexion, # 注册reflexion函数 27 | additional_prompt="请你根据我的问题,给出最优的回答", # 补充prompt 28 | ), 29 | ] 30 | 31 | 32 | async def main(): 33 | async with MAS(oxy_space=oxy_space) as mas: 34 | await mas.start_web_service(first_query="1+1等于几") 35 | 36 | 37 | if __name__ == "__main__": 38 | asyncio.run(main()) 39 | -------------------------------------------------------------------------------- /oxygent/oxy/flows/workflow.py: -------------------------------------------------------------------------------- 1 | """Workflow module for custom workflow execution. 2 | 3 | This module provides the Workflow class, which enables execution of custom workflow 4 | functions within the OxyGent flow system. It serves as a bridge between the flow 5 | framework and user-defined workflow logic. 6 | """ 7 | 8 | from typing import Callable, Optional 9 | 10 | from pydantic import Field 11 | 12 | from ...schemas import OxyRequest, OxyResponse, OxyState 13 | from ..base_flow import BaseFlow 14 | 15 | 16 | class Workflow(BaseFlow): 17 | """Flow that executes custom workflow functions. 18 | 19 | Attributes: 20 | func_execute (Optional[Callable]): The custom workflow function to execute. 21 | This function should accept an OxyRequest and return an OxyResponse. 22 | """ 23 | 24 | func_workflow: Optional[Callable] = Field(None, exclude=True, description="") 25 | 26 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 27 | return OxyResponse( 28 | state=OxyState.COMPLETED, output=await self.func_workflow(oxy_request) 29 | ) 30 | -------------------------------------------------------------------------------- /oxygent/oxy/__init__.py: -------------------------------------------------------------------------------- 1 | from .agents import ( 2 | ChatAgent, 3 | ParallelAgent, 4 | RAGAgent, 5 | ReActAgent, 6 | SSEOxyGent, 7 | WorkflowAgent, 8 | ) 9 | from .api_tools import HttpTool 10 | from .base_oxy import Oxy 11 | from .flows import ( 12 | MathReflexion, 13 | PlanAndSolve, 14 | Reflexion, 15 | Workflow, 16 | ) 17 | from .function_tools.function_hub import FunctionHub 18 | from .function_tools.function_tool import FunctionTool 19 | from .llms import HttpLLM, MockLLM, OpenAILLM 20 | from .mcp_tools import MCPTool, SSEMCPClient, StdioMCPClient, StreamableMCPClient 21 | 22 | __all__ = [ 23 | "Oxy", 24 | "ChatAgent", 25 | "RAGAgent", 26 | "ReActAgent", 27 | "WorkflowAgent", 28 | "ParallelAgent", 29 | "SSEOxyGent", 30 | "HttpTool", 31 | "HttpLLM", 32 | "OpenAILLM", 33 | "MockLLM", 34 | "MCPTool", 35 | "StdioMCPClient", 36 | "StreamableMCPClient", 37 | "SSEMCPClient", 38 | "FunctionHub", 39 | "FunctionTool", 40 | "Workflow", 41 | "PlanAndSolve", 42 | "Reflexion", 43 | "MathReflexion", 44 | ] 45 | -------------------------------------------------------------------------------- /oxygent/oxy/base_flow.py: -------------------------------------------------------------------------------- 1 | """Base flow module for OxyGent framework. 2 | 3 | This module provides the BaseFlow class, which serves as the abstract base class for all 4 | flows in the OxyGent system. Flows are specialized Oxy instances that orchestrate 5 | complex workflows and coordinate multiple agents or tools. 6 | """ 7 | 8 | from pydantic import Field 9 | 10 | from ..schemas import OxyRequest, OxyResponse 11 | from .base_oxy import Oxy 12 | 13 | 14 | class BaseFlow(Oxy): 15 | """Abstract base class for all flows in the OxyGent system. 16 | 17 | Attributes: 18 | category (str): Flow category identifier. Always "flow". 19 | """ 20 | 21 | is_permission_required: bool = Field( 22 | True, description="Whether this flow requires permission." 23 | ) 24 | category: str = Field("agent", description="") 25 | 26 | is_master: bool = Field( 27 | False, description="Whether this flow is a 'MASTER' (central controller)." 28 | ) 29 | 30 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 31 | raise NotImplementedError("This method is not yet implemented") 32 | -------------------------------------------------------------------------------- /examples/backend/demo_mas_hook.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, Config, oxy 5 | 6 | Config.set_agent_llm_model("default_llm") 7 | 8 | 9 | def func_filter(payload): 10 | print(payload) 11 | payload["group_data"] = {"user_pin": "123456"} 12 | return payload 13 | 14 | 15 | def func_interceptor(payload): 16 | print(payload) 17 | return {"code": 403, "message": "Permission denied."} 18 | 19 | 20 | oxy_space = [ 21 | oxy.HttpLLM( 22 | name="default_llm", 23 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 24 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 25 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 26 | ), 27 | oxy.ChatAgent( 28 | name="master_agent", 29 | llm_model="default_llm", 30 | ), 31 | ] 32 | 33 | 34 | async def main(): 35 | async with MAS( 36 | oxy_space=oxy_space, 37 | func_filter=func_filter, 38 | func_interceptor=func_interceptor, 39 | ) as mas: 40 | await mas.start_web_service(first_query="hello") 41 | 42 | 43 | if __name__ == "__main__": 44 | asyncio.run(main()) 45 | -------------------------------------------------------------------------------- /oxygent/chart/web/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | margin: 0; 4 | padding: 20px; 5 | background-color: #f5f5f5; 6 | } 7 | 8 | .container { 9 | max-width: 800px; 10 | margin: 0 auto; 11 | background-color: white; 12 | padding: 20px; 13 | border-radius: 8px; 14 | box-shadow: 0 2px 10px rgba(0,0,0,0.1); 15 | } 16 | 17 | h1, h2 { 18 | color: #333; 19 | } 20 | 21 | .input-container { 22 | margin-bottom: 20px; 23 | } 24 | 25 | textarea { 26 | width: 100%; 27 | height: 150px; 28 | padding: 10px; 29 | border: 1px solid #ddd; 30 | border-radius: 4px; 31 | font-size: 14px; 32 | margin-bottom: 10px; 33 | } 34 | 35 | button { 36 | background-color: #4CAF50; 37 | color: white; 38 | padding: 10px 15px; 39 | border: none; 40 | border-radius: 4px; 41 | cursor: pointer; 42 | font-size: 14px; 43 | } 44 | 45 | button:hover { 46 | background-color: #45a049; 47 | } 48 | 49 | .result-container { 50 | padding: 15px; 51 | border: 1px solid #ddd; 52 | border-radius: 4px; 53 | background-color: #f9f9f9; 54 | min-height: 100px; 55 | } -------------------------------------------------------------------------------- /examples/distributed/app_time_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from oxygent import MAS, Config, oxy 4 | 5 | Config.set_app_name("app-time") 6 | Config.set_server_port(8082) 7 | 8 | oxy_space = [ 9 | oxy.HttpLLM( 10 | name="default_name", 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 | oxy.StdioMCPClient( 18 | name="time_tools", 19 | params={ 20 | "command": "uvx", 21 | "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"], 22 | }, 23 | ), 24 | oxy.ReActAgent( 25 | name="time_agent", 26 | desc="A tool for time query", 27 | tools=["time_tools"], 28 | llm_model="default_name", 29 | ), 30 | ] 31 | 32 | 33 | async def main(): 34 | async with MAS(oxy_space=oxy_space) as mas: 35 | await mas.start_web_service(first_query="What time is it now?") 36 | 37 | 38 | if __name__ == "__main__": 39 | import asyncio 40 | 41 | asyncio.run(main()) 42 | -------------------------------------------------------------------------------- /oxygent/schemas/observation.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import List 3 | 4 | from pydantic import BaseModel, Field 5 | 6 | from ..utils.common_utils import to_json 7 | from .oxy import OxyOutput, OxyResponse 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class ExecResult(BaseModel): 13 | executor: str 14 | oxy_response: OxyResponse 15 | 16 | 17 | class Observation(BaseModel): 18 | """Observation for multimodal.""" 19 | 20 | exec_results: List[ExecResult] = Field(default_factory=list) 21 | 22 | def add_exec_result(self, exec_result: ExecResult) -> None: 23 | """Add a exec result to exec_results.""" 24 | self.exec_results.append(exec_result) 25 | 26 | def to_str(self): 27 | outs = [] 28 | for exec_result in self.exec_results: 29 | prefix = f"Tool [{exec_result.executor}] execution result: " 30 | if isinstance(exec_result.oxy_response.output, OxyOutput): 31 | outs.append(prefix + to_json(exec_result.oxy_response.output.result)) 32 | else: 33 | outs.append(prefix + to_json(exec_result.oxy_response.output)) 34 | return "\n\n".join(outs) 35 | -------------------------------------------------------------------------------- /examples/agents/demo_rag_agent.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, OxyRequest, oxy 5 | 6 | 7 | async def func_retrieve_knowledge(oxy_request: OxyRequest) -> str: 8 | query = oxy_request.get_query() 9 | print(query) 10 | return "Pi is 3.141592653589793238462643383279502." 11 | 12 | 13 | oxy_space = [ 14 | oxy.HttpLLM( 15 | name="default_llm", 16 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 17 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 18 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 19 | ), 20 | oxy.RAGAgent( 21 | name="qa_agent", 22 | llm_model="default_llm", 23 | prompt="""You are a helpful assistant! You can refer to the following knowledge to answer the questions:\n${knowledge}""", 24 | knowledge_placeholder="knowledge", 25 | func_retrieve_knowledge=func_retrieve_knowledge, 26 | ), 27 | ] 28 | 29 | 30 | async def main(): 31 | async with MAS(oxy_space=oxy_space) as mas: 32 | await mas.start_web_service( 33 | first_query="Please calculate the 20 positions of Pi", 34 | ) 35 | 36 | 37 | if __name__ == "__main__": 38 | asyncio.run(main()) 39 | -------------------------------------------------------------------------------- /docs/docs_zh/0_install.md: -------------------------------------------------------------------------------- 1 | ## 如何安装OxyGent? 2 | 3 | ### 如何配置环境? 4 | --- 5 | 6 | 我们支持通过`conda`或者`uv`配置环境。 7 | 8 | + 创建运行环境(conda) 9 | ```bash 10 | conda create -n oxy_env python==3.10 11 | conda activate oxy_env 12 | ``` 13 | 或者(uv) 14 | ```bash 15 | curl -LsSf https://astral.sh/uv/install.sh | sh 16 | uv python install 3.10 17 | uv venv .venv --python 3.10 18 | source .venv/bin/activate 19 | ``` 20 | + 下载发行包(conda) 21 | ```bash 22 | pip install oxygent 23 | ``` 24 | 或者(uv) 25 | ```bash 26 | uv pip install oxygent 27 | ``` 28 | 29 | ### 可不可以使用其他python版本? 30 | --- 31 | 32 | 建议您现在使用**Python 3.10**运行OxyGent。未来版本将兼容最新的python包。 33 | 34 | ### 是否需要配置其他环境选项? 35 | --- 36 | 37 | 不需要,OxyGent的核心是纯Python。但是为了您的使用体验,建议您先安装好[Node.js](https://nodejs.org)。 38 | 39 | ### 如何安装OxyGent? 40 | --- 41 | ```bash 42 | pip install oxygent # conda 43 | uv pip install oxygent # uv 44 | ``` 45 | 46 | ### 安装OxyGent网速太慢怎么办? 47 | --- 48 | 您可以使用镜像安装,比如 49 | 50 | ```bash 51 | uv pip install oxygent -i https://pypi.tuna.tsinghua.edu.cn/simple 52 | ``` 53 | 54 | 或者从github直接下载`oxygent`文件夹,放在项目根目录下。(使用本地包需要先安装requirements.txt) 55 | 56 | OxyGent会优先引用本地包。 57 | 58 | 59 | [下一章:运行demo](./0_1_demo.md) 60 | [回到首页](./readme.md) -------------------------------------------------------------------------------- /examples/backend/demo_human_in_the_loop.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, OxyRequest, oxy 5 | 6 | 7 | async def workflow(oxy_request: OxyRequest): 8 | # 发消息 9 | await oxy_request.send_message({"type": "msg_type", "content": "msg_content"}) 10 | 11 | # 监听数据流,channel_id 默认是 trace_id 12 | feedbacks = [] 13 | async for data in oxy_request.get_feedback_stream(): 14 | print(data) 15 | feedbacks.append(data) 16 | """requests.post(url="http://127.0.0.1:8080/feedback", json={"channel_id": "xxx", "data": ""})""" 17 | 18 | return ",".join(feedbacks) 19 | 20 | 21 | oxy_space = [ 22 | oxy.HttpLLM( 23 | name="default_llm", 24 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 25 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 26 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 27 | ), 28 | oxy.WorkflowAgent( 29 | name="master_agent", 30 | func_workflow=workflow, 31 | ), 32 | ] 33 | 34 | 35 | async def main(): 36 | async with MAS(oxy_space=oxy_space) as mas: 37 | await mas.start_web_service(first_query="hello") 38 | 39 | 40 | if __name__ == "__main__": 41 | asyncio.run(main()) 42 | -------------------------------------------------------------------------------- /oxygent/preset_tools/shell_tools.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | from typing import List, Optional 4 | 5 | from oxygent.oxy import FunctionHub 6 | from pydantic import Field 7 | 8 | logger = logging.getLogger(__name__) 9 | shell_tools = FunctionHub(name="shell_tools") 10 | 11 | 12 | @shell_tools.tool( 13 | description="Run a shell command and return the output or error." 14 | ) 15 | def run_shell_command( 16 | args: List[str] = Field(description="command arguments"), 17 | tail: int = 10, 18 | base_dir: Optional[str] = None 19 | ) -> str: 20 | """Runs a shell command and returns the output or error.""" 21 | 22 | try: 23 | logger.info(f"Running shell command: {args}") 24 | result = subprocess.run( 25 | args, 26 | capture_output=True, 27 | encoding="utf8", 28 | shell=True, 29 | text=True, 30 | cwd=base_dir 31 | ) 32 | if result.returncode != 0: 33 | return f"Error: {result.stderr}" 34 | return "\n".join(result.stdout.split("\n")[-tail:]) 35 | except Exception as e: 36 | logger.warning(f"Failed to run shell command: {e}") 37 | return f"Error: {e}" 38 | -------------------------------------------------------------------------------- /mcp_servers/_mcp_testing_utilities/mcp_server_show_headers.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import anyio 4 | import uvicorn 5 | from mcp.server.fastmcp import FastMCP 6 | from pydantic import Field 7 | from starlette.middleware.base import BaseHTTPMiddleware 8 | from starlette.requests import Request 9 | 10 | mcp = FastMCP("auth-mcp-server") 11 | 12 | 13 | class PrintHeaderMiddleware(BaseHTTPMiddleware): 14 | async def dispatch(self, request: Request, call_next): 15 | print("----------------") 16 | for k, v in dict(request.headers).items(): 17 | print(k, v) 18 | print("----------------") 19 | return await call_next(request) 20 | 21 | 22 | @mcp.tool(description="A tool for calculating powers") 23 | def power( 24 | n: int = Field(description="base"), 25 | m: int = Field(description="index", default=2), 26 | ) -> int: 27 | return math.pow(n, m) 28 | 29 | 30 | app = mcp.streamable_http_app() 31 | app.add_middleware(PrintHeaderMiddleware) 32 | 33 | 34 | async def run(): 35 | config = uvicorn.Config(app, host="0.0.0.0", port=8000, log_level="info") 36 | server = uvicorn.Server(config) 37 | await server.serve() 38 | 39 | 40 | if __name__ == "__main__": 41 | anyio.run(run) 42 | -------------------------------------------------------------------------------- /oxygent/oxy/agents/workflow_agent.py: -------------------------------------------------------------------------------- 1 | """Workflow agent module for custom workflow execution. 2 | 3 | This module provides the WorkflowAgent class, which enables execution of custom workflow 4 | functions within the OxyGent framework. It serves as a bridge between the agent system 5 | and user-defined workflow logic. 6 | """ 7 | 8 | from typing import Callable, Optional 9 | 10 | from pydantic import Field 11 | 12 | from ...schemas import OxyRequest, OxyResponse, OxyState 13 | from .local_agent import LocalAgent 14 | 15 | 16 | class WorkflowAgent(LocalAgent): 17 | """Agent that executes custom workflow functions. 18 | 19 | This agent provides a flexible way to integrate custom workflow logic 20 | into the OxyGent system. 21 | 22 | Attributes: 23 | func_workflow (Optional[Callable]): The workflow function to execute. 24 | This function should accept an OxyRequest and return the result. 25 | """ 26 | 27 | func_workflow: Optional[Callable] = Field(None, exclude=True, description="") 28 | 29 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 30 | return OxyResponse( 31 | state=OxyState.COMPLETED, output=await self.func_workflow(oxy_request) 32 | ) 33 | -------------------------------------------------------------------------------- /examples/backend/demo_add_router.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | import httpx 5 | from fastapi import APIRouter 6 | 7 | from oxygent import MAS, OxyRequest, oxy 8 | from oxygent.schemas import WebResponse 9 | 10 | router = APIRouter() 11 | 12 | 13 | @router.get("/api_name") 14 | def api_name(): 15 | return WebResponse(data={"key": "value"}).to_dict() 16 | 17 | 18 | async def workflow(oxy_request: OxyRequest): 19 | async with httpx.AsyncClient() as client: 20 | response = await client.get(url="http://127.0.0.1:8080/api_name") 21 | return response.json() 22 | 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 | oxy.WorkflowAgent( 32 | name="master_agent", 33 | llm_model="default_llm", 34 | func_workflow=workflow, 35 | ), 36 | ] 37 | 38 | 39 | async def main(): 40 | async with MAS(oxy_space=oxy_space, routers=[router]) as mas: 41 | await mas.start_web_service(first_query="hello") 42 | 43 | 44 | if __name__ == "__main__": 45 | asyncio.run(main()) 46 | -------------------------------------------------------------------------------- /examples/ecommerce/app_payment_service.py: -------------------------------------------------------------------------------- 1 | # examples/ecommerce/app_payment_service.py 2 | 3 | import os 4 | 5 | from oxygent import MAS, Config, oxy 6 | 7 | Config.set_app_name("payment-service") 8 | Config.set_server_port(8082) 9 | 10 | oxy_space = [ 11 | oxy.HttpLLM( 12 | name="default_name", 13 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 14 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 15 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 16 | llm_params={"temperature": 0.01}, 17 | semaphore=4, 18 | ), 19 | # Paying tool 20 | oxy.StdioMCPClient( 21 | name="payment_tools", 22 | params={ 23 | "command": "uv", 24 | "args": ["--directory", "./mcp_servers", "run", "payment_tools.py"], 25 | }, 26 | ), 27 | # Paying agent 28 | oxy.ReActAgent( 29 | name="payment_service", 30 | is_master=True, 31 | tools=["payment_tools"], 32 | llm_model="default_name", 33 | ), 34 | ] 35 | 36 | 37 | async def main(): 38 | async with MAS(oxy_space=oxy_space) as mas: 39 | await mas.start_web_service() 40 | 41 | 42 | if __name__ == "__main__": 43 | import asyncio 44 | 45 | asyncio.run(main()) 46 | -------------------------------------------------------------------------------- /oxygent/web/image/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/backend/demo_custom_shared_data_schema.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, oxy 7 | 8 | Config.set_es_schema_shared_data( 9 | { 10 | "properties": { 11 | "user_pin": {"type": "keyword"}, 12 | "user_name": {"type": "keyword"}, 13 | } 14 | } 15 | ) 16 | 17 | 18 | def process_input(oxy_request: OxyRequest) -> OxyRequest: 19 | oxy_request.set_shared_data("user_pin", "123456") 20 | oxy_request.set_shared_data("user_name", "oxy") 21 | return oxy_request 22 | 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 | oxy.ReActAgent( 32 | name="master_agent", 33 | llm_model="default_llm", 34 | func_process_input=process_input, 35 | ), 36 | ] 37 | 38 | 39 | async def main(): 40 | async with MAS(oxy_space=oxy_space) as mas: 41 | await mas.start_web_service(first_query="hello") 42 | 43 | 44 | if __name__ == "__main__": 45 | asyncio.run(main()) 46 | -------------------------------------------------------------------------------- /test/unittest/test_observation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for Observation & ExecResult 3 | """ 4 | 5 | from oxygent.schemas.observation import ExecResult, Observation 6 | from oxygent.schemas.oxy import OxyOutput, OxyResponse, OxyState 7 | 8 | 9 | # ────────────────────────────────────────────────────────────────────────────── 10 | # Helpers 11 | # ────────────────────────────────────────────────────────────────────────────── 12 | def make_oxy_resp(output): 13 | return OxyResponse(state=OxyState.COMPLETED, output=output, extra={}) 14 | 15 | 16 | # ────────────────────────────────────────────────────────────────────────────── 17 | # Tests 18 | # ────────────────────────────────────────────────────────────────────────────── 19 | def test_add_exec_result_and_to_str(): 20 | obs = Observation() 21 | 22 | obs.add_exec_result( 23 | ExecResult(executor="search", oxy_response=make_oxy_resp("answer")) 24 | ) 25 | 26 | oxy_out = OxyOutput(result="img_ok", attachments=["http://a.png"]) 27 | obs.add_exec_result( 28 | ExecResult(executor="vision", oxy_response=make_oxy_resp(oxy_out)) 29 | ) 30 | 31 | text = obs.to_str() 32 | assert "Tool [search] execution result: answer" in text 33 | assert "Tool [vision] execution result: img_ok" in text 34 | -------------------------------------------------------------------------------- /oxygent/web/image/tool-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/oxy/base_tool.py: -------------------------------------------------------------------------------- 1 | """Base tool module for OxyGent framework. 2 | 3 | This module provides the BaseTool class, which serves as the abstract base class for all 4 | tools in the OxyGent system. Tools are specialized Oxy instances that typically require 5 | permissions and have shorter timeout periods. 6 | """ 7 | 8 | from pydantic import Field 9 | 10 | from ..schemas import OxyRequest, OxyResponse 11 | from .base_oxy import Oxy 12 | 13 | 14 | class BaseTool(Oxy): 15 | """Abstract base class for all tools in the OxyGent system. 16 | 17 | Attributes: 18 | is_permission_required (bool): Whether permission is required to execute 19 | this tool. Defaults to True for security. 20 | category (str): Tool category identifier. Always "tool". 21 | timeout (float): Execution timeout in seconds. Defaults to 60 seconds. 22 | """ 23 | 24 | is_permission_required: bool = Field( 25 | True, description="Whether permission is required for execution" 26 | ) 27 | category: str = Field("tool", description="Tool category identifier") 28 | timeout: float = Field(60, description="Timeout in seconds.") 29 | 30 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 31 | raise NotImplementedError("This method is not yet implemented") 32 | -------------------------------------------------------------------------------- /examples/advanced/demo_multimodal_transfer.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, oxy, preset_tools 5 | 6 | oxy_space = [ 7 | oxy.HttpLLM( 8 | name="default_vlm", 9 | api_key=os.getenv("DEFAULT_VLM_API_KEY"), 10 | base_url=os.getenv("DEFAULT_VLM_BASE_URL"), 11 | model_name=os.getenv("DEFAULT_VLM_MODEL_NAME"), 12 | is_multimodal_supported=True, 13 | is_convert_url_to_base64=True, 14 | ), 15 | oxy.HttpLLM( 16 | name="default_llm", 17 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 18 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 19 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 20 | ), 21 | preset_tools.image_gen_tools, 22 | oxy.ChatAgent( 23 | name="vision_agent", 24 | desc="一个图片理解工具", 25 | llm_model="default_vlm", 26 | ), 27 | oxy.ReActAgent( 28 | name="master_agent", 29 | is_master=True, 30 | sub_agents=["vision_agent"], 31 | tools=["image_gen_tools"], 32 | llm_model="default_llm", 33 | ), 34 | ] 35 | 36 | 37 | async def main(): 38 | async with MAS(oxy_space=oxy_space) as mas: 39 | await mas.start_web_service(first_query="这是什么,生成一张卡通的") 40 | 41 | 42 | if __name__ == "__main__": 43 | asyncio.run(main()) 44 | -------------------------------------------------------------------------------- /docs/development/api/db_factory.md: -------------------------------------------------------------------------------- 1 | # DBFactory 2 | --- 3 | The position of the class is: 4 | 5 | ``` 6 | oxygent/db_factory.py 7 | ``` 8 | 9 | --- 10 | 11 | ## Introduce 12 | 13 | `DBFactory` is a singleton factory class for database instance management. It ensures only one database instance of a specific class type can be created and maintained throughout the application lifecycle. The factory uses the singleton pattern to provide global access to database instances while preventing multiple instances of the same database type from being created. 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type / Allowed value | Default | Description | 18 | | --------- | -------------------- | ------- | ----------- | 19 | | `_instance` | `object` | `None` | The single database instance maintained by the factory | 20 | | `_created_class` | `class` | `None` | The class type of the created instance | 21 | | `_factory_instance` | `DBFactory` | `None` | The singleton factory instance | 22 | 23 | ## Methods 24 | 25 | | Method | Coroutine (async) | Return Value | Purpose | 26 | | ------ | ----------------- | ------------ | ------- | 27 | | `__new__()` | No | `DBFactory` | Create or return the singleton instance of DBFactory | 28 | | `get_instance()` | No | `object` | Get instance of specified class type, create if not exists | 29 | 30 | ## Usage 31 | 32 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | oxygent/web linguist-generated 2 | oxygent/web/css linguist-generated 3 | oxygent/web/js linguist-generated 4 | 5 | oxygent/web/js/agent_tree.js linguist-generated 6 | oxygent/web/js/autosize.js linguist-generated 7 | oxygent/web/js/cascader.js linguist-generated 8 | oxygent/web/js/flowchart.js linguist-generated 9 | oxygent/web/js/mermaid-sdk-flowchart.js linguist-generated 10 | oxygent/web/js/mermaid-sdk-gantt.js linguist-generated 11 | oxygent/web/js/mermaid-source.js linguist-generated 12 | oxygent/web/js/message.js linguist-generated 13 | oxygent/web/js/upload.js linguist-generated 14 | oxygent/web/js/utils.js linguist-generated 15 | 16 | oxygent/web/css/agent-list.css linguist-generated 17 | oxygent/web/css/cascader.css linguist-generated 18 | oxygent/web/css/file.css linguist-generated 19 | oxygent/web/css/flowchart_view1.css linguist-generated 20 | oxygent/web/css/gantt.css linguist-generated 21 | oxygent/web/css/main.css linguist-generated 22 | oxygent/web/css/node.css linguist-generated 23 | oxygent/web/css/select.css linguist-generated 24 | oxygent/web/css/style.css linguist-generated 25 | oxygent/web/css/style_load1.css linguist-generated 26 | oxygent/web/css/style_load2.css linguist-generated 27 | oxygent/web/css/tree.css linguist-generated 28 | 29 | oxygent/web/index.html linguist-generated 30 | oxygent/web/node.html linguist-generated -------------------------------------------------------------------------------- /mcp_servers/browser/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MCP Server for Browser Operations 3 | 4 | This package provides browser automation tools that can be invoked by an agent. 5 | It includes tools for navigating web pages, interacting with elements, 6 | capturing screenshots, managing browser tabs, and automatic login capabilities 7 | using the Playwright framework. 8 | """ 9 | 10 | from .content import browser_snapshot, browser_take_screenshot 11 | from .core import browser_check_status, check_dependencies, mcp 12 | from .interaction import browser_click, browser_hover, browser_type 13 | from .login import browser_auto_login 14 | from .navigation import ( 15 | browser_navigate, 16 | browser_navigate_back, 17 | browser_navigate_forward, 18 | ) 19 | from .search import browser_search 20 | from .tabs import browser_tab_close, browser_tab_list, browser_tab_new 21 | 22 | # 导出所有工具函数,方便导入 23 | __all__ = [ 24 | "mcp", 25 | "check_dependencies", 26 | "browser_navigate", 27 | "browser_navigate_back", 28 | "browser_navigate_forward", 29 | "browser_click", 30 | "browser_hover", 31 | "browser_type", 32 | "browser_snapshot", 33 | "browser_take_screenshot", 34 | "browser_tab_list", 35 | "browser_tab_new", 36 | "browser_tab_close", 37 | "browser_auto_login", 38 | "browser_search", 39 | "browser_check_status", 40 | ] 41 | -------------------------------------------------------------------------------- /oxygent/preset_tools/python_tools.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Optional 3 | 4 | from oxygent.oxy import FunctionHub 5 | 6 | logger = logging.getLogger(__name__) 7 | python_tools = FunctionHub(name="python_tools") 8 | 9 | 10 | @python_tools.tool( 11 | description="Runs Python code in the current environment." 12 | ) 13 | def run_python_code( 14 | code: str, 15 | variable_to_return: Optional[str] = None, 16 | safe_globals: Optional[dict] = None, 17 | safe_locals: Optional[dict] = None 18 | ) -> str: 19 | try: 20 | logger.debug(f"Running code:\n\n{code}\n\n") 21 | if not safe_globals: 22 | safe_globals = globals() 23 | if not safe_locals: 24 | safe_locals = locals() 25 | 26 | exec(code, safe_globals, safe_locals) 27 | 28 | if variable_to_return: 29 | variable_value = safe_locals.get(variable_to_return) 30 | if variable_value is None: 31 | return f"Variable {variable_to_return} not found" 32 | logger.debug( 33 | f"Variable {variable_to_return} value: {variable_value}") 34 | return str(variable_value) 35 | else: 36 | return "successfully run python code" 37 | except Exception as e: 38 | logger.error(f"Error running python code: {e}") 39 | return f"Error running python code: {e}" 40 | -------------------------------------------------------------------------------- /oxygent/web/image/global-language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/group.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mcp_servers/kubernetes_mcp_server/.env.example: -------------------------------------------------------------------------------- 1 | DEFAULT_LLM_API_KEY="" 2 | DEFAULT_LLM_BASE_URL="" 3 | DEFAULT_LLM_MODEL_NAME="" 4 | 5 | # ================================ 6 | # Kubernetes MCP 基础配置 7 | # ================================ 8 | 9 | # MCP 传输模式: stdio, sse, streamable-http 10 | K8S_MCP_TRANSPORT=stdio 11 | 12 | # 启用的工具集: config,core,helm (逗号分隔) 13 | K8S_MCP_TOOLSETS=config,core,helm 14 | 15 | # 安全模式配置 16 | K8S_MCP_READ_ONLY=false 17 | K8S_MCP_DISABLE_DESTRUCTIVE=false 18 | 19 | # SSE/Streamable HTTP 模式端口 (仅网络模式需要) 20 | K8S_MCP_PORT=8000 21 | 22 | # ================================ 23 | # 单集群配置 24 | # ================================ 25 | 26 | # kubeconfig 文件路径 (可选,默认使用 ~/.kube/config) 27 | KUBECONFIG=~/.kube/config 28 | 29 | 30 | # ================================ 31 | # 调试配置 (可选) 32 | # ================================ 33 | 34 | # 启用调试日志 35 | DEBUG=0 36 | 37 | # Python 路径 (通常不需要修改) 38 | PYTHONPATH=. 39 | 40 | # ================================ 41 | # 使用说明 42 | # ================================ 43 | 44 | # 1. 基础使用 (kubernetes_demo.py): 45 | # - 设置 LLM 配置 46 | # - 设置 KUBECONFIG 47 | # - 运行: python examples/mcp_tools/kubernetes_demo.py 48 | 49 | 50 | # ================================ 51 | # 安全提醒 52 | # ================================ 53 | 54 | # - 生产环境建议使用 K8S_MCP_READ_ONLY=true 55 | # - 敏感环境建议使用 K8S_MCP_DISABLE_DESTRUCTIVE=true 56 | # - 确保 kubeconfig 文件权限设置正确 (600) 57 | # - 定期轮换 API 密钥和访问凭证 -------------------------------------------------------------------------------- /oxygent/web/image/group-vector-active.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/image/group-vector-default.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oxygent/web/css/select.css: -------------------------------------------------------------------------------- 1 | ul,ol,li{ 2 | margin:0; 3 | padding:0; 4 | } 5 | 6 | .nice-select input{ 7 | outline: none; 8 | cursor: pointer; 9 | width: 200px; 10 | height: 28px; 11 | font-size: 1em; 12 | border: 1px solid #E0E0E0; 13 | background: url(../image/icon.png) no-repeat scroll right center transparent; 14 | background-position: 96% 50%; 15 | padding: 0 10px; 16 | -webkit-border-radius: 4px; 17 | -moz-border-radius: 4px; 18 | border-radius: 4px; 19 | line-height: 28px; 20 | background-color: white; 21 | } 22 | 23 | ul{ 24 | list-style: none; 25 | } 26 | 27 | .nice-select{ 28 | width: 200px; 29 | margin: 0 0; 30 | -webkit-border-radius: .3em; 31 | -moz-border-radius: .3em; 32 | border-radius: .3em; 33 | background-color: white; 34 | } 35 | 36 | .nice-select ul{ 37 | display: none; 38 | border: 1px solid #d5d5d5; 39 | width: 13.9em; 40 | position: absolute; 41 | top: 2.4em; 42 | overflow: hidden; 43 | background-color: #fff; 44 | max-height: 150px; 45 | overflow-y: auto; 46 | border-top: 0; 47 | z-index: 10001; 48 | } 49 | 50 | .nice-select ul li{ 51 | height: 30px; 52 | line-height: 2em; 53 | overflow: hidden; 54 | padding: 0 10px; 55 | cursor: pointer; 56 | border-top: 1px solid #d5d5d5; 57 | font-size: small; 58 | } 59 | 60 | .nice-select ul li.on{background-color: #e0e0e0;} -------------------------------------------------------------------------------- /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 | 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 | ), 16 | preset_tools.time_tools, 17 | oxy.ReActAgent( 18 | name="time_agent", 19 | desc="A tool that can query the time", 20 | tools=["time_tools"], 21 | ), 22 | preset_tools.file_tools, 23 | oxy.ReActAgent( 24 | name="file_agent", 25 | desc="A tool that can operate the file system", 26 | tools=["file_tools"], 27 | ), 28 | preset_tools.math_tools, 29 | oxy.ReActAgent( 30 | name="math_agent", 31 | desc="A tool that can perform mathematical calculations.", 32 | tools=["math_tools"], 33 | ), 34 | oxy.ReActAgent( 35 | is_master=True, 36 | name="master_agent", 37 | sub_agents=["time_agent", "file_agent", "math_agent"], 38 | ), 39 | ] 40 | 41 | 42 | async def main(): 43 | async with MAS(oxy_space=oxy_space) as mas: 44 | await mas.start_web_service( 45 | first_query="What time is it now? Please save it into time.txt." 46 | ) 47 | 48 | 49 | if __name__ == "__main__": 50 | asyncio.run(main()) 51 | -------------------------------------------------------------------------------- /docs/docs_zh/readme.md: -------------------------------------------------------------------------------- 1 | # OxyGent中文how-to指南 2 | > 本系列文档将指导您使用OxyGent逐步搭建MAS系统 3 | > 如果您是MAS框架的新用户,建议您按顺序阅读文档,尤其是标注*的部分 4 | 5 | 文档的现有教程包括: 6 | 7 | ## 安装和启动 8 | + [安装OxyGent](./0_install.md)* 9 | + [运行demo](./0_1_demo.md)* 10 | 11 | ## 核心功能 12 | + [创建第一个智能体](./1_register_single_agent.md)* 13 | + [和智能体交流](./1_1_chat_with_agent.md) 14 | + [选择智能体使用的LLM](./1_2_select_llm.md) 15 | + [预设提示词](./1_3_select_prompt.md) 16 | + [选择智能体种类](./1_4_select_agent.md) 17 | 18 | ## 工具 19 | + [注册一个工具](./2_register_single_tool.md)* 20 | + [使用MCP开源工具](./2_3_use_opensource_tools.md)* 21 | + [使用MCP自定义工具](./2_4_use_mcp_tools.md)* 22 | + [管理工具调用](./2_2_manage_tools.md) 23 | 24 | ## 设置 25 | + [设置OxyGent Config](./3_set_config.md)* 26 | + [设置数据库](./3_1_set_database.md) 27 | + [设置全局数据](./3_2_set_global.md) 28 | 29 | ## 多智能体系统 30 | + [创建简单的多agent系统](./6_register_multi_agent.md)* 31 | + [复制相同智能体](./6_1_moa.md) 32 | + [并行调用agent](./7_parallel.md)* 33 | + [提供响应元数据](./8_1_trust_mode.md) 34 | + [处理查询和提示词](./8_update_prompts.md)* 35 | + [处理LLM和智能体输出](./8_2_handle_output.md) 36 | + [反思重做模式](./8_3_reflexion.md) 37 | 38 | ## 流 39 | + [创建工作流](./9_workflow.md)* 40 | + [创建流](./9_2_preset_flow.md) 41 | + [获取记忆和重新生成](./9_1_continue_exec.md) 42 | + [创建分布式系统](./11_dstributed.md)* 43 | 44 | ## 多模态 45 | + [使用多模态智能体](./10_multimodal.md)* 46 | + [导入附件](./10_1_attachments.md) 47 | 48 | ## 动态模型功能 49 | + [检索增强生成(RAG)](./12_rag.md)* 50 | + [生成训练样本](./13_training.md)* 51 | 52 | ## 调试 53 | + [可视化界面调试](./14_debugging.md) -------------------------------------------------------------------------------- /examples/tools/demo_functionhub.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from pydantic import Field 5 | 6 | from oxygent import MAS, oxy 7 | 8 | fh_joke_tools = oxy.FunctionHub(name="joke_tools") 9 | 10 | 11 | @fh_joke_tools.tool(description="a tool for telling jokes") 12 | async def joke_tool(joke_type: str = Field(description="The type of the jokes")): 13 | import random 14 | 15 | jokes = [ 16 | "Teacher: Can you use the word 'because' in a sentence? \n Student: I didn't do my homework because… because I didn't do my homework.", 17 | "Patient: Doctor, I feel like a pair of curtains.\nDoctor: Pull yourself together!", 18 | "How many software engineers does it take to change a light bulb?\nNone. That's a hardware problem.", 19 | ] 20 | print("The type of the jokes", joke_type) 21 | return random.choice(jokes) 22 | 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 | fh_joke_tools, 32 | oxy.ReActAgent( 33 | name="joke_agent", 34 | tools=["joke_tools"], 35 | ), 36 | ] 37 | 38 | 39 | async def main(): 40 | async with MAS(oxy_space=oxy_space) as mas: 41 | await mas.start_web_service(first_query="Please tell a joke") 42 | 43 | 44 | if __name__ == "__main__": 45 | asyncio.run(main()) 46 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | 4 | 5 | ### Related Issue(s) 6 | 7 | 8 | 9 | - Closes #123 10 | - Fixes #456 11 | 12 | ### Changes 13 | 14 | 15 | 16 | - [ ] Feature A added 17 | 18 | contents 19 | 20 | - [ ] Refactor B 21 | 22 | contents 23 | 24 | - [ ] Fix for C 25 | 26 | contents 27 | 28 | 29 | 37 | 38 | ### Screenshots (if UI changes) 39 | 40 | 41 | 42 | ### Checklist 43 | 44 | - [ ] been self-reviewed. 45 | - [ ] Code is formatted 46 | - [ ] Tests added/updated 47 | - [ ] All CI checks passed 48 | - [ ] Documentation updated 49 | - [ ] added documentation for new or modified features or behaviors. 50 | - [ ] added new features, such as new agents, new flows, etc. 51 | - [ ] added or updated version, __license__, or notice information. 52 | - [ ] added or updated demo, integration tests, unit tests, or others. 53 | - [ ] added or updated ui, or had changes in frontend. 54 | 55 | -------------------------------------------------------------------------------- /mcp_servers/kubernetes_mcp_server/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Kubernetes MCP Server package initializer. 3 | 4 | 提供: 5 | - 全局 FastMCP 实例 (用于在各工具模块上通过 @mcp.tool 装饰器统一注册) 6 | - 非破坏模式与禁删/禁更新等安全开关的环境变量读取 7 | - 其他子模块将通过 `from . import mcp` 共享同一个 MCP 实例 8 | """ 9 | 10 | from __future__ import annotations 11 | 12 | import os 13 | from mcp.server.fastmcp import FastMCP 14 | 15 | # 全局 MCP 实例:各工具模块通过 `from . import mcp` 引用此实例并注册工具 16 | # 运行传输模式由启动入口 server.py 控制 (stdio / sse / streamable-http) 17 | mcp = FastMCP() 18 | 19 | # 安全与变更开关(环境变量控制) 20 | # - K8S_MCP_READ_ONLY=true: 仅允许只读/非破坏工具 21 | # - K8S_MCP_DISABLE_DESTRUCTIVE=true: 禁止 destructive 类操作(delete / update 等) 22 | # 注意:这两个开关的生效逻辑由 server.py 的工具过滤器与具体工具内部的保护共同保证 23 | _READ_ONLY = os.getenv("K8S_MCP_READ_ONLY", "").strip().lower() in { 24 | "1", 25 | "true", 26 | "yes", 27 | "on", 28 | } 29 | _DISABLE_DESTRUCTIVE = os.getenv("K8S_MCP_DISABLE_DESTRUCTIVE", "").strip().lower() in { 30 | "1", 31 | "true", 32 | "yes", 33 | "on", 34 | } 35 | 36 | 37 | def is_read_only() -> bool: 38 | """ 39 | 返回是否启用只读模式(READ-ONLY)。 40 | 该模式通常会移除 delete/update 等破坏性工具,仅保留只读与创建/更新安全工具的最小集合。 41 | """ 42 | return _READ_ONLY 43 | 44 | 45 | def is_disable_destructive() -> bool: 46 | """ 47 | 返回是否禁用破坏性操作(DELETE/UPDATE 等)。 48 | 在 READ-ONLY 未开启时,也可通过该开关细粒度限制工具集合。 49 | """ 50 | return _DISABLE_DESTRUCTIVE 51 | 52 | 53 | __all__ = [ 54 | "mcp", 55 | "is_read_only", 56 | "is_disable_destructive", 57 | ] 58 | -------------------------------------------------------------------------------- /examples/advanced/demo_multimodal.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, oxy 5 | 6 | oxy_space = [ 7 | oxy.HttpLLM( 8 | name="default_vlm", 9 | api_key=os.getenv("DEFAULT_VLM_API_KEY"), 10 | base_url=os.getenv("DEFAULT_VLM_BASE_URL"), 11 | model_name=os.getenv("DEFAULT_VLM_MODEL_NAME"), 12 | is_multimodal_supported=True, # 设置支持多模态 13 | is_convert_url_to_base64=True, # 设置将图片链接转换为base64 14 | base64_image_prefix="data:image/jpeg", # 设置base64图片前缀 15 | ), 16 | oxy.ChatAgent( 17 | name="vision_agent", 18 | llm_model="default_vlm", 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="What is this?") 26 | 27 | 28 | async def test(): 29 | async with MAS(oxy_space=oxy_space) as mas: 30 | messages = [ 31 | { 32 | "role": "system", 33 | "content": "You are a helpful assistant.", 34 | }, 35 | { 36 | "role": "user", 37 | "content": [ 38 | {"type": "image_url", "image_url": {"url": "./test_pic.png"}}, 39 | {"type": "text", "text": "What is this?"}, 40 | ], 41 | }, 42 | ] 43 | await mas.call(callee="default_vlm", arguments={"messages": messages}) 44 | 45 | 46 | if __name__ == "__main__": 47 | asyncio.run(main()) 48 | -------------------------------------------------------------------------------- /oxygent/web/image/output_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/distributed/app_master_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from oxygent import MAS, oxy 4 | 5 | oxy_space = [ 6 | oxy.HttpLLM( 7 | name="default_name", 8 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 9 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 10 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 11 | llm_params={"temperature": 0.01}, 12 | semaphore=4, 13 | ), 14 | oxy.StdioMCPClient( 15 | name="file_tools", 16 | params={ 17 | "command": "npx", 18 | "args": ["-y", "@modelcontextprotocol/server-filesystem", "./local_file"], 19 | }, 20 | ), 21 | oxy.ReActAgent( 22 | name="master_agent", 23 | sub_agents=["file_agent", "math_agent"], 24 | is_master=True, 25 | llm_model="default_name", 26 | ), 27 | oxy.ReActAgent( 28 | name="file_agent", 29 | desc="A tool for querying local files", 30 | tools=["file_tools"], 31 | llm_model="default_name", 32 | ), 33 | oxy.SSEOxyGent( 34 | name="math_agent", 35 | desc="A tool for mathematical calculations", 36 | server_url="http://127.0.0.1:8081", 37 | is_share_call_stack=False, 38 | ), 39 | ] 40 | 41 | 42 | async def main(): 43 | async with MAS(oxy_space=oxy_space) as mas: 44 | await mas.start_web_service(first_query="The first 30 positions of pi") 45 | 46 | 47 | if __name__ == "__main__": 48 | import asyncio 49 | 50 | asyncio.run(main()) 51 | -------------------------------------------------------------------------------- /examples/start_distributed.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 distributed services..." 49 | 50 | start_service "python -m examples.distributed.app_time_agent" "TimeAgent" 5 51 | start_service "python -m examples.distributed.app_math_agent" "MathAgent" 5 52 | start_service "python -m examples.distributed.app_master_agent" "MasterAgent" 5 53 | 54 | log "All services have been started" 55 | log "Press Ctrl+C to stop all services" 56 | 57 | wait 58 | } 59 | 60 | main "$@" 61 | -------------------------------------------------------------------------------- /oxygent/oxy/mcp_tools/mcp_tool.py: -------------------------------------------------------------------------------- 1 | """MCP Tool implementation for Model Context Protocol tools. 2 | 3 | This module provides the MCPTool class, which represents individual tools discovered 4 | from MCP servers. Each MCPTool acts as a proxy that delegates execution to its parent 5 | MCP client while providing a standardized tool interface. 6 | """ 7 | 8 | from typing import Any 9 | 10 | from pydantic import Field 11 | 12 | from ...schemas import OxyRequest, OxyResponse 13 | from ..base_tool import BaseTool 14 | 15 | 16 | class MCPTool(BaseTool): 17 | """Individual tool proxy for MCP server tools. 18 | 19 | This class represents a specific tool discovered from an MCP server. 20 | It acts as a lightweight proxy that delegates actual execution to the 21 | parent MCP client while providing the standard BaseTool interface. 22 | 23 | Attributes: 24 | is_permission_required: Whether the tool requires explicit permission before execution. 25 | mcp_client: Reference to the parent MCP client that handles actual execution. 26 | server_name: Name of the MCP server that provides this tool. 27 | """ 28 | 29 | is_permission_required: bool = Field(True, description="") 30 | 31 | mcp_client: Any = Field(None, exclude=True) 32 | server_name: str = Field("") 33 | 34 | async def _execute(self, oxy_request: OxyRequest) -> OxyResponse: 35 | """Execute the MCP tool by delegating to the parent MCP client.""" 36 | return await self.mcp_client._execute(oxy_request) 37 | -------------------------------------------------------------------------------- /examples/ecommerce/app_product_service.py: -------------------------------------------------------------------------------- 1 | # examples/ecommerce/app_product_service.py 2 | 3 | import os 4 | 5 | from oxygent import MAS, Config, oxy 6 | 7 | Config.set_app_name("product-service") 8 | Config.set_server_port(8080) 9 | 10 | oxy_space = [ 11 | oxy.HttpLLM( 12 | name="default_name", 13 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 14 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 15 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 16 | llm_params={"temperature": 0.01}, 17 | semaphore=4, 18 | ), 19 | # Database tool 20 | oxy.StdioMCPClient( 21 | name="product_db", 22 | params={ 23 | "command": "uv", 24 | "args": ["--directory", "./mcp_servers", "run", "product_tools.py"], 25 | }, 26 | ), 27 | # Inventory management tool 28 | oxy.StdioMCPClient( 29 | name="inventory_tools", 30 | params={ 31 | "command": "uv", 32 | "args": ["--directory", "./mcp_servers", "run", "inventory_tools.py"], 33 | }, 34 | ), 35 | # Product agent 36 | oxy.ReActAgent( 37 | name="product_agent", 38 | is_master=True, 39 | tools=["product_db", "inventory_tools"], 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 | -------------------------------------------------------------------------------- /oxygent/web/js/message.js: -------------------------------------------------------------------------------- 1 | // 实现一个简单的message 2 | // Message SDK 3 | (function($) { 4 | // 定义 Message 类 5 | function Message() { 6 | this.$container = null; 7 | } 8 | 9 | // 初始化消息容器 10 | Message.prototype.init = function() { 11 | this.$container = $('
'); 12 | 13 | $('body').append(this.$container); 14 | return this; 15 | }; 16 | 17 | // 显示消息 18 | Message.prototype.show = function(content) { 19 | if (!this.$container) { 20 | this.init(); 21 | } 22 | this.$container.html(content).fadeIn(200); 23 | 24 | // 100ms 后自动消失 25 | const self = this; 26 | setTimeout(function() { 27 | self.$container.fadeOut(200, function() { 28 | // 动画完成后销毁 29 | self.destroy(); 30 | }); 31 | }, 500); 32 | }; 33 | 34 | // 销毁消息容器 35 | Message.prototype.destroy = function() { 36 | if (this.$container) { 37 | this.$container.remove(); 38 | this.$container = null; 39 | } 40 | }; 41 | 42 | // 对外暴露的 API 43 | window.MessageSDK = { 44 | show: function(content) { 45 | const msg = new Message(); 46 | msg.show(content); 47 | } 48 | }; 49 | })(jQuery); 50 | 51 | -------------------------------------------------------------------------------- /examples/backend/demo_save_message.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, Config, OxyRequest, oxy 5 | 6 | Config.set_message_is_show_in_terminal(True) 7 | Config.set_message_is_stored(True) 8 | 9 | 10 | async def update_query(oxy_request: OxyRequest) -> OxyRequest: 11 | await oxy_request.send_message( 12 | {"type": "test1", "content": "test1", "_is_stored": False, "_is_send": False} 13 | ) 14 | await oxy_request.send_message( 15 | {"type": "test2", "content": "test2", "_is_stored": False, "_is_send": True} 16 | ) 17 | await oxy_request.send_message( 18 | {"type": "test3", "content": "test3", "_is_stored": True, "_is_send": False} 19 | ) 20 | await oxy_request.send_message( 21 | {"type": "test4", "content": "test4", "_is_stored": True, "_is_send": True} 22 | ) 23 | return oxy_request 24 | 25 | 26 | oxy_space = [ 27 | oxy.HttpLLM( 28 | name="default_llm", 29 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 30 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 31 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 32 | llm_params={"stream": True}, 33 | ), 34 | oxy.ChatAgent( 35 | name="qa_agent", 36 | llm_model="default_llm", 37 | func_process_input=update_query, 38 | ), 39 | ] 40 | 41 | 42 | async def main(): 43 | async with MAS(oxy_space=oxy_space) as mas: 44 | await mas.start_web_service(first_query="hello") 45 | 46 | 47 | if __name__ == "__main__": 48 | asyncio.run(main()) 49 | -------------------------------------------------------------------------------- /examples/ecommerce/app_logistics_service.py: -------------------------------------------------------------------------------- 1 | # examples/ecommerce/app_logistics_service.py 2 | 3 | import os 4 | 5 | from oxygent import MAS, Config, oxy 6 | 7 | Config.set_app_name("logistics-service") 8 | Config.set_server_port(8083) 9 | 10 | oxy_space = [ 11 | oxy.HttpLLM( 12 | name="default_name", 13 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 14 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 15 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 16 | llm_params={"temperature": 0.01}, 17 | semaphore=4, 18 | ), 19 | # Logistics Tools 20 | oxy.StdioMCPClient( 21 | name="logistics_tools", 22 | params={ 23 | "command": "uv", 24 | "args": ["--directory", "./mcp_servers", "run", "logistics_tools.py"], 25 | }, 26 | ), 27 | # Delivery Tools 28 | oxy.StdioMCPClient( 29 | name="delivery_tools", 30 | params={ 31 | "command": "uv", 32 | "args": ["--directory", "./mcp_servers", "run", "delivery_tools.py"], 33 | }, 34 | ), 35 | # Agent for logistics and delivery 36 | oxy.ReActAgent( 37 | name="logistics_agent", 38 | is_master=True, 39 | tools=["logistics_tools", "delivery_tools"], 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 | -------------------------------------------------------------------------------- /examples/advanced/demo_custom_agent_input_schema.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from oxygent import MAS, OxyRequest, oxy 5 | 6 | 7 | async def workflow(oxy_request: OxyRequest): 8 | print(oxy_request.get_arguments("query")) 9 | PI = "3.141592653589793238462643383279502884197169399375105820974944592307816406286208998" 10 | return PI[: int(oxy_request.get_arguments("precision"))] 11 | 12 | 13 | oxy_space = [ 14 | oxy.HttpLLM( 15 | name="default_llm", 16 | api_key=os.getenv("DEFAULT_LLM_API_KEY"), 17 | base_url=os.getenv("DEFAULT_LLM_BASE_URL"), 18 | model_name=os.getenv("DEFAULT_LLM_MODEL_NAME"), 19 | ), 20 | oxy.ReActAgent( 21 | name="master_agent", 22 | sub_agents=["math_agent"], 23 | is_master=True, 24 | llm_model="default_llm", 25 | ), 26 | oxy.WorkflowAgent( 27 | name="math_agent", 28 | desc="A tool for pi query", 29 | input_schema={ 30 | "properties": { 31 | "query": {"description": "Query question"}, 32 | "precision": {"description": "How many decimal places are there"}, 33 | }, 34 | "required": ["query", "precision"], 35 | }, 36 | func_workflow=workflow, 37 | ), 38 | ] 39 | 40 | 41 | async def main(): 42 | async with MAS(oxy_space=oxy_space) as mas: 43 | await mas.start_web_service( 44 | first_query="Please calculate the 20 positions of Pi", 45 | ) 46 | 47 | 48 | if __name__ == "__main__": 49 | asyncio.run(main()) 50 | -------------------------------------------------------------------------------- /oxygent/chart/web/js/app.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | const generateBtn = document.getElementById('generate-btn'); 3 | const descriptionInput = document.getElementById('description'); 4 | const resultContainer = document.getElementById('result'); 5 | 6 | generateBtn.addEventListener('click', function() { 7 | const description = descriptionInput.value.trim(); 8 | 9 | if (!description) { 10 | alert('请输入流程图描述'); 11 | return; 12 | } 13 | 14 | // 显示加载状态 15 | resultContainer.innerHTML = '

正在生成流程图,请稍候...

'; 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 | --------------------------------------------------------------------------------