agent-os-base 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_base/__init__.py +40 -0
- agent_base/kernel/__init__.py +12 -0
- agent_base/kernel/adk_agent.py +188 -0
- agent_base/kernel/agent_config.py +48 -0
- agent_base/main.py +144 -0
- agent_base/observability/__init__.py +0 -0
- agent_base/observability/tracing.py +17 -0
- agent_base/operator.py +98 -0
- agent_base/templates/__init__.py +0 -0
- agent_base/templates/agent.py.template +22 -0
- agent_base/templates/env.template +10 -0
- agent_os_base-0.1.2.dist-info/METADATA +179 -0
- agent_os_base-0.1.2.dist-info/RECORD +17 -0
- agent_os_base-0.1.2.dist-info/WHEEL +5 -0
- agent_os_base-0.1.2.dist-info/entry_points.txt +2 -0
- agent_os_base-0.1.2.dist-info/licenses/LICENSE +0 -0
- agent_os_base-0.1.2.dist-info/top_level.txt +1 -0
agent_base/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from ag_ui_adk import ADKAgent
|
|
2
|
+
from .kernel.agent_config import AgentConfig, ModelProvider
|
|
3
|
+
from .kernel import Kernel
|
|
4
|
+
|
|
5
|
+
def create_agent(
|
|
6
|
+
agent_root_path: str,
|
|
7
|
+
agent_framework: str,
|
|
8
|
+
agent_name:str,
|
|
9
|
+
agent_description:str,
|
|
10
|
+
agent_instruction: str,
|
|
11
|
+
model_name: str,
|
|
12
|
+
model_base_url: str,
|
|
13
|
+
model_api_key: str,
|
|
14
|
+
model_temperature: float,
|
|
15
|
+
is_entrypoint: bool = False,
|
|
16
|
+
use_memory: bool = False
|
|
17
|
+
):
|
|
18
|
+
# 生成agent配置文件
|
|
19
|
+
agent_config = AgentConfig(
|
|
20
|
+
agent_root_path=agent_root_path if not (agent_root_path is None or agent_root_path == "") else "./",
|
|
21
|
+
agent_framework=agent_framework if agent_framework in ["adk"] else "adk",
|
|
22
|
+
agent_name=agent_name if not (agent_name is None or agent_name == "") else "default_agent",
|
|
23
|
+
description=agent_description if not (
|
|
24
|
+
agent_description is None or agent_description == "") else "这是一个默认的智能体",
|
|
25
|
+
instruction=agent_instruction if not (
|
|
26
|
+
agent_instruction is None or agent_instruction == "") else "你是一个乐于助人的智能体助手",
|
|
27
|
+
provider=ModelProvider(
|
|
28
|
+
model_name=model_name if not (model_name is None or model_name == "") else "qwen3",
|
|
29
|
+
model_base_url=model_base_url if not (
|
|
30
|
+
model_base_url is None or model_base_url == "") else "http://localhost:8080/v1",
|
|
31
|
+
model_api_key=model_api_key if not (model_base_url is None or model_base_url == "") else "sk-test",
|
|
32
|
+
model_temperature=model_temperature
|
|
33
|
+
),
|
|
34
|
+
is_entrypoint=is_entrypoint,
|
|
35
|
+
use_memory=use_memory
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# 生成Agent
|
|
39
|
+
agent_kernel = Kernel(agent_config=agent_config)
|
|
40
|
+
return agent_kernel.agent
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .adk_agent import ADKAgent
|
|
2
|
+
from .agent_config import AgentConfig
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Kernel(object):
|
|
6
|
+
def __init__(self, agent_config: AgentConfig) -> None:
|
|
7
|
+
if agent_config.agent_framework == "adk":
|
|
8
|
+
self.agent = ADKAgent(agent_config=agent_config)
|
|
9
|
+
elif agent_config.agent_framework == "maf":
|
|
10
|
+
raise ValueError("Microsoft Agent Framework is not yet supported")
|
|
11
|
+
else:
|
|
12
|
+
raise ValueError(f"No such framework {agent_config.agent_framework}")
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# !/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Router
|
|
6
|
+
|
|
7
|
+
Authors: fubo
|
|
8
|
+
Date: 2026/03/19 00:00:00
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import logging
|
|
13
|
+
import pathlib
|
|
14
|
+
from typing import List, Dict
|
|
15
|
+
import importlib.util
|
|
16
|
+
import inspect
|
|
17
|
+
|
|
18
|
+
from google.adk.agents.llm_agent import Agent
|
|
19
|
+
from google.adk.models.lite_llm import LiteLlm
|
|
20
|
+
from google.adk.skills import load_skill_from_dir
|
|
21
|
+
from google.adk.tools import skill_toolset, mcp_tool, load_memory
|
|
22
|
+
from google.adk.code_executors.unsafe_local_code_executor import UnsafeLocalCodeExecutor
|
|
23
|
+
|
|
24
|
+
from .agent_config import AgentConfig, AgentMCPToolConfig
|
|
25
|
+
|
|
26
|
+
class ADKAgent(Agent):
|
|
27
|
+
def __init__(self, agent_config: AgentConfig):
|
|
28
|
+
super().__init__(
|
|
29
|
+
model=LiteLlm(
|
|
30
|
+
model=agent_config.provider.model_name,
|
|
31
|
+
api_key=agent_config.provider.model_api_key,
|
|
32
|
+
base_url=agent_config.provider.model_base_url,
|
|
33
|
+
temperature=agent_config.provider.model_temperature
|
|
34
|
+
),
|
|
35
|
+
name=agent_config.agent_name,
|
|
36
|
+
description=agent_config.description,
|
|
37
|
+
instruction=agent_config.instruction,
|
|
38
|
+
tools=ADKAgent.load_local_function_toolsets(
|
|
39
|
+
f"{agent_config.agent_root_path}/{agent_config.agent_name}/local-functions"
|
|
40
|
+
) + ADKAgent.load_skills(
|
|
41
|
+
f"{agent_config.agent_root_path}/{agent_config.agent_name}/skills"
|
|
42
|
+
) + ADKAgent.load_remote_mcp_toolsets(
|
|
43
|
+
f"{agent_config.agent_root_path}/{agent_config.agent_name}/mcp-toolsets"
|
|
44
|
+
) + [load_memory] if agent_config.use_memory else [],
|
|
45
|
+
output_key=f"{agent_config.agent_name}-result"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def load_functions_from_py_code(code_file_path: str) -> Dict:
|
|
50
|
+
# 1. 检查文件是否存在
|
|
51
|
+
if not os.path.isfile(code_file_path):
|
|
52
|
+
raise FileNotFoundError(f"No such file: {code_file_path}")
|
|
53
|
+
|
|
54
|
+
# 2. 生成一个唯一的模块名(避免与已加载模块冲突)
|
|
55
|
+
base_name = os.path.splitext(os.path.basename(code_file_path))[0]
|
|
56
|
+
unique_suffix = str(hash(os.path.abspath(code_file_path))).replace('-', '_')
|
|
57
|
+
module_name = f"{base_name}_{unique_suffix}"
|
|
58
|
+
|
|
59
|
+
# 3. 创建模块 spec
|
|
60
|
+
spec = importlib.util.spec_from_file_location(module_name, code_file_path)
|
|
61
|
+
if spec is None:
|
|
62
|
+
raise ImportError(f"Create sepc from {code_file_path}")
|
|
63
|
+
|
|
64
|
+
# 4. 创建模块对象并加入 sys.modules
|
|
65
|
+
module = importlib.util.module_from_spec(spec)
|
|
66
|
+
sys.modules[module_name] = module
|
|
67
|
+
|
|
68
|
+
# 5. 执行模块代码
|
|
69
|
+
try:
|
|
70
|
+
spec.loader.exec_module(module)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
raise ImportError(f"Error when load module {module_name}: {e}")
|
|
73
|
+
|
|
74
|
+
# 6. 收集模块中定义的顶层函数
|
|
75
|
+
functions = {}
|
|
76
|
+
for name, obj in inspect.getmembers(module, inspect.isfunction):
|
|
77
|
+
# 只取 __module__ 等于当前模块名的函数(即在该模块内定义,非导入)
|
|
78
|
+
if obj.__module__ == module.__name__:
|
|
79
|
+
functions[name] = obj
|
|
80
|
+
|
|
81
|
+
return functions
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def load_skills(skills_path: str = None) -> List[skill_toolset.SkillToolset]:
|
|
85
|
+
"""Load skills from the skills path."""
|
|
86
|
+
if skills_path is None:
|
|
87
|
+
logging.warning(f"The skills path is {skills_path}. No skills will be loaded.")
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
if not os.path.exists(skills_path):
|
|
91
|
+
logging.warning(f"No such path named {skills_path}. No skills will be loaded.")
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
logging.info(f"Loading skills from {skills_path}")
|
|
95
|
+
skills = []
|
|
96
|
+
for skill in os.listdir(skills_path):
|
|
97
|
+
if skill.startswith("."):
|
|
98
|
+
continue
|
|
99
|
+
logging.info(f"Loading Skill {skill}")
|
|
100
|
+
skills.append(load_skill_from_dir(pathlib.Path(skills_path) / skill))
|
|
101
|
+
skills_toolsets = [
|
|
102
|
+
skill_toolset.SkillToolset(skills=skills, code_executor=UnsafeLocalCodeExecutor())
|
|
103
|
+
]
|
|
104
|
+
logging.info(f"All skills were loaded count {len(skills)}")
|
|
105
|
+
|
|
106
|
+
return skills_toolsets
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def load_local_function_toolsets(local_function_path: str = None) -> List:
|
|
110
|
+
"""加载本地代码函数工具"""
|
|
111
|
+
functions = []
|
|
112
|
+
if local_function_path is None:
|
|
113
|
+
logging.warning(f"The local function code file is {local_function_path}. No function will be loaded.")
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
if not os.path.exists(local_function_path):
|
|
117
|
+
logging.warning(f"No such path named {local_function_path}. No functions will be loaded.")
|
|
118
|
+
return []
|
|
119
|
+
logging.info(f"Loading local functions from {local_function_path}")
|
|
120
|
+
for code_file in os.listdir(local_function_path):
|
|
121
|
+
if not code_file.endswith(".py"):
|
|
122
|
+
continue
|
|
123
|
+
functions = functions + list(
|
|
124
|
+
ADKAgent.load_functions_from_py_code(f"{local_function_path}/{code_file}").values()
|
|
125
|
+
)
|
|
126
|
+
logging.info(f"All local functions were loaded function count {len(functions)}")
|
|
127
|
+
return functions
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def load_remote_mcp_toolsets(mcp_toolset_path: str = None) -> List[mcp_tool.McpToolset]:
|
|
131
|
+
"""加载远端的MCP服务"""
|
|
132
|
+
toolsets = []
|
|
133
|
+
if mcp_toolset_path is None:
|
|
134
|
+
logging.warning(f"The mcp-toolsets is {mcp_toolset_path}. No remote MCP tools will be loaded.")
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
if not os.path.exists(mcp_toolset_path):
|
|
138
|
+
logging.warning(f"No such path named {mcp_toolset_path}. No remote MCP tools will be loaded.")
|
|
139
|
+
return []
|
|
140
|
+
|
|
141
|
+
logging.info(f"Loading remote mcp toolsets from {mcp_toolset_path}")
|
|
142
|
+
for toolset_config_file in os.listdir(mcp_toolset_path):
|
|
143
|
+
if not toolset_config_file.endswith(".json"):
|
|
144
|
+
continue
|
|
145
|
+
# 解析工具集参数
|
|
146
|
+
logging.info(f"Loading MCP toolset files {toolset_config_file}")
|
|
147
|
+
toolset_config = AgentMCPToolConfig.parse_file(f"{mcp_toolset_path}/{toolset_config_file}")
|
|
148
|
+
# 构建mcp工具集
|
|
149
|
+
if toolset_config.server_type.lower() == "sse":
|
|
150
|
+
if toolset_config.auth_token is not None and toolset_config.auth_token != "":
|
|
151
|
+
toolset = mcp_tool.McpToolset(
|
|
152
|
+
connection_params=mcp_tool.mcp_session_manager.SseServerParams(
|
|
153
|
+
url=toolset_config.url,
|
|
154
|
+
headers=dict(Authorization=f'Bearer {toolset_config.auth_token}')
|
|
155
|
+
),
|
|
156
|
+
errlog=None
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
toolset = mcp_tool.McpToolset(
|
|
160
|
+
connection_params=mcp_tool.mcp_session_manager.SseServerParams(url=toolset_config.url),
|
|
161
|
+
errlog=None
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
elif toolset_config.server_type.lower() == "streamablehttp":
|
|
165
|
+
if toolset_config.auth_token is not None and toolset_config.auth_token != "":
|
|
166
|
+
toolset = mcp_tool.McpToolset(
|
|
167
|
+
connection_params=mcp_tool.mcp_session_manager.StreamableHTTPServerParams(
|
|
168
|
+
url=toolset_config.url,
|
|
169
|
+
headers=dict(Authorization=f'Bearer {toolset_config.auth_token}')
|
|
170
|
+
),
|
|
171
|
+
errlog=None
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
toolset = mcp_tool.McpToolset(
|
|
175
|
+
connection_params=mcp_tool.mcp_session_manager.StreamableHTTPServerParams(
|
|
176
|
+
url=toolset_config.url
|
|
177
|
+
),
|
|
178
|
+
errlog=None
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
toolset = None
|
|
182
|
+
|
|
183
|
+
if toolset is not None:
|
|
184
|
+
toolsets.append(toolset)
|
|
185
|
+
|
|
186
|
+
logging.info(f"All MCP toolsets were loaded count {len(toolsets)}")
|
|
187
|
+
|
|
188
|
+
return toolsets
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# !/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Router
|
|
6
|
+
|
|
7
|
+
Authors: fubo
|
|
8
|
+
Date: 2026/03/19 00:00:00
|
|
9
|
+
"""
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
class AgentMCPToolConfig(BaseModel):
|
|
13
|
+
# toolse name
|
|
14
|
+
name: str
|
|
15
|
+
# Server URL
|
|
16
|
+
url: str
|
|
17
|
+
# 服务类型
|
|
18
|
+
server_type: str = "StreamableHTTP"
|
|
19
|
+
# Auth
|
|
20
|
+
auth_token: str = ""
|
|
21
|
+
|
|
22
|
+
class ModelProvider(BaseModel):
|
|
23
|
+
# 使用的模型名
|
|
24
|
+
model_name: str
|
|
25
|
+
# 模型api key
|
|
26
|
+
model_api_key: str
|
|
27
|
+
# 模型base url
|
|
28
|
+
model_base_url: str
|
|
29
|
+
# 模型参数温度
|
|
30
|
+
model_temperature: float = 0.0
|
|
31
|
+
|
|
32
|
+
class AgentConfig(BaseModel):
|
|
33
|
+
# 智能体所在路径
|
|
34
|
+
agent_root_path: str
|
|
35
|
+
# 智能体框架
|
|
36
|
+
agent_framework: str
|
|
37
|
+
# 智能体名
|
|
38
|
+
agent_name: str
|
|
39
|
+
# 智能体描述
|
|
40
|
+
description: str = ""
|
|
41
|
+
# 智能体instruction
|
|
42
|
+
instruction: str = ""
|
|
43
|
+
# LLM配置
|
|
44
|
+
provider: ModelProvider
|
|
45
|
+
# 是否智能体入口
|
|
46
|
+
is_entrypoint: bool = False
|
|
47
|
+
# 是否使用memory
|
|
48
|
+
use_memory: bool = False
|
agent_base/main.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# !/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Router
|
|
6
|
+
|
|
7
|
+
Authors: fubo
|
|
8
|
+
Date: 2026/03/19 00:00:00
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
from .kernel.agent_config import AgentConfig, ModelProvider, AgentMCPToolConfig
|
|
15
|
+
from .operator import Operator
|
|
16
|
+
|
|
17
|
+
def commander():
|
|
18
|
+
parser = argparse.ArgumentParser(description="智能体系统构建")
|
|
19
|
+
parser.add_argument("-an", "--agent-name", required=True, help="智能体名称")
|
|
20
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
21
|
+
|
|
22
|
+
# create-agent 子命令
|
|
23
|
+
parser_create_agent = subparsers.add_parser("create-agent", help="创建智能体")
|
|
24
|
+
parser_create_agent.add_argument(
|
|
25
|
+
"-af", "--agent_framework",
|
|
26
|
+
required=True, help="智能体框架(adk/maf)"
|
|
27
|
+
)
|
|
28
|
+
parser_create_agent.add_argument(
|
|
29
|
+
"-ad", "--agent-description",
|
|
30
|
+
required=True, help="智能体描述信息"
|
|
31
|
+
)
|
|
32
|
+
parser_create_agent.add_argument(
|
|
33
|
+
"-ai", "--agent-instruction",
|
|
34
|
+
required=True, help="智能体instruction"
|
|
35
|
+
)
|
|
36
|
+
parser_create_agent.add_argument(
|
|
37
|
+
"-mdu", "--model-url",
|
|
38
|
+
required=True, help="智能体使用的模型base url"
|
|
39
|
+
)
|
|
40
|
+
parser_create_agent.add_argument(
|
|
41
|
+
"-mdn", "--model-name",
|
|
42
|
+
required=True, help="智能体使用的模型名称"
|
|
43
|
+
)
|
|
44
|
+
parser_create_agent.add_argument(
|
|
45
|
+
"-mdk", "--model-api-key",
|
|
46
|
+
required=True, help="智能体使用的模型服务API-KEY"
|
|
47
|
+
)
|
|
48
|
+
parser_create_agent.add_argument(
|
|
49
|
+
"-mdt", "--model-temperature",
|
|
50
|
+
type=float, required=False, default=0.0, help="智能体使用的模型温度"
|
|
51
|
+
)
|
|
52
|
+
parser_create_agent.add_argument(
|
|
53
|
+
"-ie", "--is-entrypoint",
|
|
54
|
+
type=bool, required=True, default=False, help="该智能体是否入口智能体"
|
|
55
|
+
)
|
|
56
|
+
parser_create_agent.add_argument(
|
|
57
|
+
"-um", "--use-memory",
|
|
58
|
+
type=bool, required=True, default=False, help="智能体是否使用长期记忆"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# create-skill 子命令
|
|
62
|
+
parser_create_skill_tool = subparsers.add_parser("create-skill", help="创建智能体skill")
|
|
63
|
+
parser_create_skill_tool.add_argument(
|
|
64
|
+
"-c", "--config",
|
|
65
|
+
required=True, help="智能体配置文件agent_config.yml"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# create-local-function-tools 子命令
|
|
69
|
+
parser_create_local_function_tool = subparsers.add_parser(
|
|
70
|
+
"create-local-function-tool",
|
|
71
|
+
help="创建智能体本地函数工具"
|
|
72
|
+
)
|
|
73
|
+
parser_create_local_function_tool.add_argument(
|
|
74
|
+
"-c", "--config",
|
|
75
|
+
required=True, help="智能体配置文件agent_config.yml"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# create-mcp-toolset 子命令
|
|
79
|
+
parser_create_mcp_tool = subparsers.add_parser("create-mcp-toolset", help="创建智能体mcp工具集")
|
|
80
|
+
parser_create_mcp_tool.add_argument(
|
|
81
|
+
"-mpn", "--mcp-name",
|
|
82
|
+
required=True, help="MCP工具集名"
|
|
83
|
+
)
|
|
84
|
+
parser_create_mcp_tool.add_argument(
|
|
85
|
+
"-mpu", "--mcp-url",
|
|
86
|
+
required=True, help="MCP工具集url"
|
|
87
|
+
)
|
|
88
|
+
parser_create_mcp_tool.add_argument(
|
|
89
|
+
"-mpst", "--mcp-server-type",
|
|
90
|
+
required=True, help="MCP工具集服务类型SSE/StreamableHTTP"
|
|
91
|
+
)
|
|
92
|
+
parser_create_mcp_tool.add_argument(
|
|
93
|
+
"-mpat", "--mcp-auth-token",
|
|
94
|
+
required=False, default="", help="MCP工具集auth token"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return parser
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def command_parser(parser: argparse.ArgumentParser):
|
|
101
|
+
# main_args_parser: argparse.ArgumentParser,
|
|
102
|
+
args = parser.parse_args()
|
|
103
|
+
if args.command == "create-agent":
|
|
104
|
+
Operator.create_agent(
|
|
105
|
+
AgentConfig(
|
|
106
|
+
agent_root_path="./",
|
|
107
|
+
agent_framework=args.agent_framework,
|
|
108
|
+
agent_name=args.agent_name,
|
|
109
|
+
description=args.agent_description,
|
|
110
|
+
instruction=args.agent_instruction,
|
|
111
|
+
provider=ModelProvider(
|
|
112
|
+
model_base_url=args.model_url,
|
|
113
|
+
model_name=args.model_name,
|
|
114
|
+
model_api_key=args.model_api_key,
|
|
115
|
+
model_temperature=args.model_temperature
|
|
116
|
+
),
|
|
117
|
+
is_entrypoint=args.is_entrypoint,
|
|
118
|
+
use_memory=args.use_memory,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
elif args.command == "create-skill":
|
|
122
|
+
Operator.create_skill_tool(args.config)
|
|
123
|
+
elif args.command == "create-local-function-tool":
|
|
124
|
+
Operator.create_local_function_tool(args.config)
|
|
125
|
+
elif args.command == "create-mcp-toolset":
|
|
126
|
+
Operator.create_mcp_toolset(
|
|
127
|
+
agent_name=args.agent_name,
|
|
128
|
+
agent_mcp_toolset_config=AgentMCPToolConfig(
|
|
129
|
+
name=args.mcp_name,
|
|
130
|
+
url=args.mcp_url,
|
|
131
|
+
server_type=args.mcp_server_type,
|
|
132
|
+
auth_token=args.mcp_auth_token
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
else:
|
|
137
|
+
parser.print_help()
|
|
138
|
+
sys.exit(1)
|
|
139
|
+
|
|
140
|
+
def main():
|
|
141
|
+
command_parser(commander())
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
def setup_tracing(endpoint_host: str, experiment_id: int):
|
|
2
|
+
from opentelemetry import trace
|
|
3
|
+
tracer = trace.get_tracer(__name__)
|
|
4
|
+
with tracer.start_as_current_span("test-span"):
|
|
5
|
+
print("trace test")
|
|
6
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
7
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
8
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
9
|
+
|
|
10
|
+
provider = TracerProvider()
|
|
11
|
+
|
|
12
|
+
exporter = OTLPSpanExporter(
|
|
13
|
+
endpoint=f"{endpoint_host}/v1/traces",
|
|
14
|
+
headers={"x-mlflow-experiment-id": str(experiment_id)}
|
|
15
|
+
)
|
|
16
|
+
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
17
|
+
trace.set_tracer_provider(provider)
|
agent_base/operator.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# !/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Router
|
|
6
|
+
|
|
7
|
+
Authors: fubo
|
|
8
|
+
Date: 2026/03/19 00:00:00
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
from .kernel.agent_config import AgentConfig, AgentMCPToolConfig
|
|
14
|
+
|
|
15
|
+
class Operator:
|
|
16
|
+
@staticmethod
|
|
17
|
+
def replace_placeholder(file_name: str, replacement_data: dict) -> str:
|
|
18
|
+
with open(file_name, "r") as fp:
|
|
19
|
+
content = fp.read()
|
|
20
|
+
for key in replacement_data:
|
|
21
|
+
content = content.replace(key, replacement_data[key])
|
|
22
|
+
return content
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def create_agent(agent_config: AgentConfig):
|
|
26
|
+
logging.info(f"Creating agent from config: {agent_config}")
|
|
27
|
+
|
|
28
|
+
# # 这里调用 google-adk 相关逻辑
|
|
29
|
+
agent_root_path = f"./{agent_config.agent_name}"
|
|
30
|
+
template_file_path = os.path.dirname(os.path.abspath(__file__)) + "/templates"
|
|
31
|
+
|
|
32
|
+
# 构建Agent文件夹
|
|
33
|
+
os.system(f"mkdir {agent_root_path}")
|
|
34
|
+
os.system(f"touch {agent_root_path}/.env")
|
|
35
|
+
os.system(f"mkdir -p {agent_root_path}/skills")
|
|
36
|
+
os.system(f"mkdir -p {agent_root_path}/local-functions")
|
|
37
|
+
os.system(f"mkdir -p {agent_root_path}/mcp-toolsets")
|
|
38
|
+
|
|
39
|
+
# 添加local-function的代码文件
|
|
40
|
+
os.system(f"touch {agent_root_path}/local-functions/function_toolset.py")
|
|
41
|
+
|
|
42
|
+
# 写入智能体代码
|
|
43
|
+
os.system(f'echo "from . import agent" >> {agent_root_path}/__init__.py')
|
|
44
|
+
agent_py_code_template_file = template_file_path + "/agent.py.template"
|
|
45
|
+
open(f"{agent_root_path}/agent.py", "w").write(
|
|
46
|
+
Operator.replace_placeholder(
|
|
47
|
+
agent_py_code_template_file,
|
|
48
|
+
{"#[OpentelemetryTraceLine] ": ""}
|
|
49
|
+
) if not(agent_config.trace_endpoint_host is None or agent_config.trace_endpoint_host == "") else Operator.replace_placeholder(
|
|
50
|
+
agent_py_code_template_file,
|
|
51
|
+
{}
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# os.system(f"cp {agent_py_code_template_file} {agent_root_path}/agent.py")
|
|
56
|
+
dotenv_template_file = template_file_path + "/env.template"
|
|
57
|
+
with open(f"{agent_root_path}/.env", "w") as fp:
|
|
58
|
+
fp.write(
|
|
59
|
+
Operator.replace_placeholder(
|
|
60
|
+
dotenv_template_file,
|
|
61
|
+
{
|
|
62
|
+
"[agent-framework]": agent_config.agent_framework,
|
|
63
|
+
"[agent-name]": agent_config.agent_name,
|
|
64
|
+
"[agent-description]": agent_config.description,
|
|
65
|
+
"[agent-instruction]": agent_config.instruction,
|
|
66
|
+
"[model-base-url]": agent_config.provider.model_base_url,
|
|
67
|
+
"[model-name]": agent_config.provider.model_name,
|
|
68
|
+
"[model-temperature]": str(agent_config.provider.model_temperature),
|
|
69
|
+
"[model-api-key]": agent_config.provider.model_api_key,
|
|
70
|
+
"[is-entrypoint]": agent_config.is_entrypoint,
|
|
71
|
+
"[use-memory]": agent_config.use_memory,
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def create_skill_tool(config_file):
|
|
78
|
+
"""创建技能和工具"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def create_local_function_tool(config_file):
|
|
83
|
+
"""创建本地函数工具"""
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def create_mcp_toolset(agent_name: str, agent_mcp_toolset_config: AgentMCPToolConfig):
|
|
87
|
+
"""为智能体创建MCP工具集配置文件"""
|
|
88
|
+
toolsets_path = f"./{agent_name}/mcp-toolsets"
|
|
89
|
+
logging.info(f"Add MCP Toolset{agent_mcp_toolset_config.name}.json to {toolsets_path}")
|
|
90
|
+
if not os.path.exists(toolsets_path):
|
|
91
|
+
logging.error(f"No such agent and agent mcp-toolsets {toolsets_path}")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
open(
|
|
95
|
+
f"{toolsets_path}/{agent_mcp_toolset_config.name}.json", "w"
|
|
96
|
+
).write(
|
|
97
|
+
json.dumps(agent_mcp_toolset_config.dict(), ensure_ascii=False)
|
|
98
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dotenv import dotenv_values
|
|
3
|
+
|
|
4
|
+
from agent_base import create_agent
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
file_path = os.path.dirname(os.path.abspath(__file__))
|
|
8
|
+
config = dotenv_values(file_path + "/.env")
|
|
9
|
+
|
|
10
|
+
root_agent = create_agent(
|
|
11
|
+
agent_root_path=file_path + "/../",
|
|
12
|
+
agent_framework=config.get("AGENT_FRAMEWORK"),
|
|
13
|
+
agent_name=config.get("AGENT_NAME"),
|
|
14
|
+
agent_description=config.get("AGENT_DESCRIPTION"),
|
|
15
|
+
agent_instruction=config.get("AGENT_INSTRUCTION"),
|
|
16
|
+
model_name=config.get("MODEL_NAME"),
|
|
17
|
+
model_base_url=config.get("MODEL_BASE_URL"),
|
|
18
|
+
model_api_key=config.get("MODEL_API_KEY"),
|
|
19
|
+
model_temperature=float(config.get("MODEL_TEMPERATURE")),
|
|
20
|
+
is_entrypoint=bool(config.get("IS_ENTRYPOINT")),
|
|
21
|
+
use_memory=bool(config.get("USE_MEMORY"))
|
|
22
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
AGENT_FRAMEWORK=[agent-framework]
|
|
2
|
+
AGENT_NAME=[agent-name]
|
|
3
|
+
AGENT_DESCRIPTION=[agent-description]
|
|
4
|
+
AGENT_INSTRUCTION=[agent-instruction]
|
|
5
|
+
MODEL_BASE_URL=[model-base-url]
|
|
6
|
+
MODEL_API_KEY=[model-api-key]
|
|
7
|
+
MODEL_NAME=[model-name]
|
|
8
|
+
MODEL_TEMPERATURE=[model-temperature]
|
|
9
|
+
IS_ENTRYPOINT=[is-entrypoint]
|
|
10
|
+
USE_MEMORY=[use-memory]
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-os-base
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: 基础智能体构建库
|
|
5
|
+
Author-email: fubo <fb_linux@163.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://codeup.aliyun.com/64650c96168f0a5963451dec/agi-next/agent-base/blob/master/README.md
|
|
8
|
+
Project-URL: Repository, https://codeup.aliyun.com/64650c96168f0a5963451dec/agi-next/agent-base.git
|
|
9
|
+
Keywords: agent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: google-adk[extensions]>=1.27.0
|
|
17
|
+
Requires-Dist: pydantic_yaml>=1.6.0
|
|
18
|
+
Requires-Dist: aiomysql
|
|
19
|
+
Requires-Dist: ag_ui_adk
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest>=6.0; extra == "test"
|
|
22
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# agent-base
|
|
26
|
+
|
|
27
|
+
`agent-base` 是一个用于快速搭建智能体项目脚手架的 Python 库,核心目标是:
|
|
28
|
+
|
|
29
|
+
- 通过 CLI 一键创建智能体工程目录
|
|
30
|
+
- 自动生成 `.env`、`agent.py`、技能目录、本地函数目录、MCP 工具集目录
|
|
31
|
+
- 基于 `google-adk` 统一加载模型、Skills、本地函数工具、远端 MCP 工具
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 项目结构
|
|
36
|
+
|
|
37
|
+
核心代码位于 `src/agent_base`:
|
|
38
|
+
|
|
39
|
+
- `main.py`:CLI 入口,解析命令并分发到 `Operator`
|
|
40
|
+
- `operator.py`:脚手架生成与文件落地逻辑
|
|
41
|
+
- `agent_config.py`:配置数据模型(Pydantic)
|
|
42
|
+
- `adk_agent.py`:智能体运行时封装,负责动态加载工具与技能
|
|
43
|
+
- `observability/tracing.py`:OpenTelemetry Trace 初始化
|
|
44
|
+
- `templates/*.template`:新建智能体时使用的代码和环境变量模板
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 依赖与版本
|
|
49
|
+
|
|
50
|
+
`pyproject.toml` 中定义:
|
|
51
|
+
|
|
52
|
+
- Python: `>=3.10`
|
|
53
|
+
- 核心依赖:
|
|
54
|
+
- `google-adk[extensions]>=1.27.0`
|
|
55
|
+
- `pydantic_yaml>=1.6.0`
|
|
56
|
+
- 命令行入口:
|
|
57
|
+
- `agent-base = agent_base.main:main`
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## CLI 功能说明
|
|
62
|
+
|
|
63
|
+
### 1) 创建智能体
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
agent-base -an <agent_name> create-agent \
|
|
67
|
+
-ad "<description>" \
|
|
68
|
+
-ai "<instruction>" \
|
|
69
|
+
-mdu "<model_base_url>" \
|
|
70
|
+
-mdn "<model_name>" \
|
|
71
|
+
-mdk "<model_api_key>" \
|
|
72
|
+
-mdt 0.0 \
|
|
73
|
+
-teh "<trace_endpoint_host>"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
执行后会在当前目录下生成:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
./<agent_name>/
|
|
80
|
+
├── .env
|
|
81
|
+
├── __init__.py
|
|
82
|
+
├── agent.py
|
|
83
|
+
├── skills/
|
|
84
|
+
├── local-functions/
|
|
85
|
+
│ └── function_toolset.py
|
|
86
|
+
└── mcp-toolsets/
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 2) 创建 MCP 工具集配置
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
agent-base -an <agent_name> create-mcp-toolset \
|
|
93
|
+
-mpn "<mcp_name>" \
|
|
94
|
+
-mpu "<mcp_url>" \
|
|
95
|
+
-mpst "SSE|StreamableHTTP" \
|
|
96
|
+
-mpat "<optional_token>"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
会在 `./<agent_name>/mcp-toolsets/` 下生成对应 JSON 配置文件。
|
|
100
|
+
|
|
101
|
+
### 3) 预留命令(尚未实现)
|
|
102
|
+
|
|
103
|
+
- `create-skill`
|
|
104
|
+
- `create-local-function-tool`
|
|
105
|
+
|
|
106
|
+
当前在 `operator.py` 中仍是占位实现(`pass` 或空函数体)。
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 运行时加载机制(ADKAgent)
|
|
111
|
+
|
|
112
|
+
`ADKAgent` 初始化时会把三类能力合并到 `tools`:
|
|
113
|
+
|
|
114
|
+
1. **本地函数工具**
|
|
115
|
+
扫描 `./<agent_name>/local-functions/*.py`,通过动态 import 抽取顶层函数并注册。
|
|
116
|
+
|
|
117
|
+
2. **Skills**
|
|
118
|
+
扫描 `./<agent_name>/skills/*`,使用 `google.adk.skills.load_skill_from_dir` 加载后封装为 `SkillToolset`。
|
|
119
|
+
|
|
120
|
+
3. **远端 MCP 工具**
|
|
121
|
+
扫描 `./<agent_name>/mcp-toolsets/*.json`,按 `server_type` 构建:
|
|
122
|
+
- `SSE` -> `SseServerParams`
|
|
123
|
+
- `StreamableHTTP` -> `StreamableHTTPServerParams`
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Trace 能力
|
|
128
|
+
|
|
129
|
+
当提供 `TRACE_ENDPOINT_HOST` 时,会调用 `setup_tracing(endpoint_host, experiment_id)`:
|
|
130
|
+
|
|
131
|
+
- 使用 OTLP HTTP exporter 上报到 `${endpoint_host}/v1/traces`
|
|
132
|
+
- 额外写入 header:`x-mlflow-experiment-id`
|
|
133
|
+
|
|
134
|
+
`experiment_id` 在示例中由当前时间戳(毫秒)生成。
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 示例代码分析(tests/test_agent)
|
|
139
|
+
|
|
140
|
+
你本地示例目录中的 `tests/test_agent/agent.py` 展示了最小可运行初始化流程:
|
|
141
|
+
|
|
142
|
+
1. 读取 `.env`(`dotenv`)
|
|
143
|
+
2. 初始化 tracing(可选)
|
|
144
|
+
3. 组装 `AgentConfig` + `ModelProvider`
|
|
145
|
+
4. 构造 `root_agent = ADKAgent(...)`
|
|
146
|
+
|
|
147
|
+
示例技能中包含:
|
|
148
|
+
|
|
149
|
+
- `skills/calculator`:通过 `scripts/calculate.py` 提供加减乘除脚本工具
|
|
150
|
+
- `skills/comment-news`:给出“先搜新闻,再客观评价”的技能流程
|
|
151
|
+
- `skills/tavily-search`:封装 Tavily CLI 的检索能力(含安装与命令范式)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 当前实现特点与注意事项
|
|
156
|
+
|
|
157
|
+
### 已具备
|
|
158
|
+
|
|
159
|
+
- 模板化创建智能体工程
|
|
160
|
+
- 统一配置模型参数与工具加载
|
|
161
|
+
- 可加载本地函数 + Skills + MCP toolsets
|
|
162
|
+
|
|
163
|
+
### 需要注意
|
|
164
|
+
|
|
165
|
+
- `operator.py` 使用 `os.system` 进行目录与文件创建,后续可考虑改为 `pathlib`/`shutil` 提高可维护性与安全性。
|
|
166
|
+
- `create-skill`、`create-local-function-tool` 还未完成。
|
|
167
|
+
- 示例 `.env` 中出现了真实样式的 API Key,建议立即替换并避免提交到版本库(应使用环境注入或密钥管理)。
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 快速开始(建议流程)
|
|
172
|
+
|
|
173
|
+
1. 安装依赖并安装本包(开发模式)
|
|
174
|
+
2. 通过 `agent-base ... create-agent` 生成智能体目录
|
|
175
|
+
3. 在 `skills/` 与 `local-functions/` 中补充能力
|
|
176
|
+
4. 根据需要添加 `mcp-toolsets/*.json`
|
|
177
|
+
5. 在生成的 `agent.py` 中实例化并运行智能体
|
|
178
|
+
|
|
179
|
+
如需扩展,建议优先补齐 `create-skill` 与 `create-local-function-tool` 的自动化生成能力,这样项目可以形成完整的“从 0 到可运行”脚手架闭环。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
agent_base/__init__.py,sha256=lX9dwUBc5qAsfT82h2eJOcK9DGCh2VJbfssVaSd7Llk,1788
|
|
2
|
+
agent_base/main.py,sha256=KKP5ef7RiZE9KtTTPU4xaoLSe4E8kYu9Kg7_9wD3WLs,4860
|
|
3
|
+
agent_base/operator.py,sha256=Ft_PM7z5ScypAgviNvPDKEeKoYhLxsR4rYYaOg6geS0,3924
|
|
4
|
+
agent_base/kernel/__init__.py,sha256=9kPXAo4PbVKdVMuM6pkylavV-cPB8kpHhIF-zIF-6Z8,490
|
|
5
|
+
agent_base/kernel/adk_agent.py,sha256=T05_botHoNudNwqQNfIvulE_JztHivV9IzXBjK1F20A,7968
|
|
6
|
+
agent_base/kernel/agent_config.py,sha256=rmIVmhoId9lFVRUdOFJLN3BIYcv2-FXAPbmzw3w30Ko,986
|
|
7
|
+
agent_base/observability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
agent_base/observability/tracing.py,sha256=v8L0nbKrIik2mKwOUFbO0UaNbFHkcgBPcdGyaAuJuMc,707
|
|
9
|
+
agent_base/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
agent_base/templates/agent.py.template,sha256=e5jjzuj0yMlKZseEdOWhM8XLCR51j-HafRkUTErzcxc,750
|
|
11
|
+
agent_base/templates/env.template,sha256=6Ock2w_UvE2DkS0tBYTtbIqUz8Kv0hQFdITk_EPO7Ks,311
|
|
12
|
+
agent_os_base-0.1.2.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
agent_os_base-0.1.2.dist-info/METADATA,sha256=DEmp9bQ6nseXQxdw0c7wYc9WS9-hdrPUcuBfHE_wdVE,5357
|
|
14
|
+
agent_os_base-0.1.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
15
|
+
agent_os_base-0.1.2.dist-info/entry_points.txt,sha256=Q1XCeNmmtWzlKAJN2walA5EaqyUbzr1fCVyP6gdDxHA,52
|
|
16
|
+
agent_os_base-0.1.2.dist-info/top_level.txt,sha256=poXpCRC5jAU_cOzLbf-90Fl43p4DAYyzu_jJgo6JpzI,11
|
|
17
|
+
agent_os_base-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agent_base
|