mirror of
https://github.com/alibaba/higress.git
synced 2026-06-08 20:27:31 +08:00
feat: implement hgctl agent module (#3267)
This commit is contained in:
51
hgctl/pkg/manifests/agent/template/agent.tmpl
Normal file
51
hgctl/pkg/manifests/agent/template/agent.tmpl
Normal file
@@ -0,0 +1,51 @@
|
||||
from typing import Literal
|
||||
from agentscope.agent import ReActAgent
|
||||
from agentscope.formatter import FormatterBase
|
||||
from agentscope.memory import LongTermMemoryBase, MemoryBase
|
||||
from agentscope.model import ChatModelBase
|
||||
from agentscope.plan import PlanNotebook
|
||||
from agentscope.rag import KnowledgeBase
|
||||
from agentscope.tool import Toolkit
|
||||
from agentscope.tts import TTSModelBase
|
||||
|
||||
|
||||
class Agent(ReActAgent):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: str,
|
||||
model: ChatModelBase,
|
||||
formatter: FormatterBase,
|
||||
toolkit: Toolkit | None = None,
|
||||
memory: MemoryBase | None = None,
|
||||
long_term_memory: LongTermMemoryBase | None = None,
|
||||
long_term_memory_mode: (
|
||||
Literal["agent_control"] | Literal["static_control"] | Literal["both"]
|
||||
) = "both",
|
||||
enable_meta_tool: bool = False,
|
||||
parallel_tool_calls: bool = False,
|
||||
knowledge: KnowledgeBase | list[KnowledgeBase] | None = None,
|
||||
enable_rewrite_query: bool = True,
|
||||
plan_notebook: PlanNotebook | None = None,
|
||||
print_hint_msg: bool = False,
|
||||
max_iters: int = 10,
|
||||
tts_model: TTSModelBase | None = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name,
|
||||
sys_prompt,
|
||||
model,
|
||||
formatter,
|
||||
toolkit,
|
||||
memory,
|
||||
long_term_memory,
|
||||
long_term_memory_mode,
|
||||
enable_meta_tool,
|
||||
parallel_tool_calls,
|
||||
knowledge,
|
||||
enable_rewrite_query,
|
||||
plan_notebook,
|
||||
print_hint_msg,
|
||||
max_iters,
|
||||
tts_model,
|
||||
)
|
||||
95
hgctl/pkg/manifests/agent/template/agentrun.tmpl
Normal file
95
hgctl/pkg/manifests/agent/template/agentrun.tmpl
Normal file
@@ -0,0 +1,95 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
import os
|
||||
import sys
|
||||
|
||||
from agentscope.agent import ReActAgent
|
||||
from agentscope.memory import InMemoryMemory
|
||||
from agentscope.message import Msg
|
||||
from agentscope.pipeline._functional import stream_printing_messages
|
||||
from agentscope.agent import ReActAgent
|
||||
from agentscope.model import DashScopeChatModel
|
||||
from agentscope.formatter import DashScopeChatFormatter
|
||||
|
||||
from agentrun.integration.agentscope import model, sandbox_toolset, toolset
|
||||
from agentrun.sandbox import TemplateType
|
||||
from agentrun.server import AgentRequest, AgentRunServer
|
||||
from agentrun.utils.log import logger
|
||||
|
||||
from agent import Agent
|
||||
from toolkit import toolkit, init_toolkit_sync
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "python"))
|
||||
|
||||
MODEL_NAME = "{{ .ChatModel }}"
|
||||
SANDBOX_NAME = os.getenv("AGENTRUN_SANDBOX_NAME")
|
||||
|
||||
if not MODEL_NAME:
|
||||
raise ValueError("请将 MODEL_NAME 替换为您已经创建的模型名称")
|
||||
|
||||
code_interpreter_tools = []
|
||||
if SANDBOX_NAME and not SANDBOX_NAME.startswith("<"):
|
||||
code_interpreter_tools = sandbox_toolset(
|
||||
template_name=SANDBOX_NAME,
|
||||
template_type=TemplateType.CODE_INTERPRETER,
|
||||
sandbox_idle_timeout_seconds=300,
|
||||
)
|
||||
else:
|
||||
logger.warning("SANDBOX_NAME 未设置或未替换,跳过加载沙箱工具。")
|
||||
|
||||
def load_sys_prompt(prompt_file_name="prompt.md"):
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
prompt_path = os.path.join(script_dir, prompt_file_name)
|
||||
|
||||
with open(prompt_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
agent = Agent(
|
||||
name="{{ .AgentName }}",
|
||||
model=model(MODEL_NAME), # type: ignore
|
||||
sys_prompt=load_sys_prompt(),
|
||||
toolkit=toolkit,
|
||||
memory=InMemoryMemory(),
|
||||
formatter=DashScopeChatFormatter(),
|
||||
)
|
||||
|
||||
|
||||
async def invoke_agent(request: AgentRequest):
|
||||
try:
|
||||
content = request.messages[0].content
|
||||
input_msg = Msg(
|
||||
name="user_message",
|
||||
content=content, # type: ignore
|
||||
role="user",
|
||||
)
|
||||
|
||||
async for msg, _ in stream_printing_messages(
|
||||
agents=[agent],
|
||||
coroutine_task=agent(input_msg),
|
||||
):
|
||||
text = msg.get_text_content()
|
||||
if text:
|
||||
yield text
|
||||
|
||||
except Exception:
|
||||
logger.exception("调用出错")
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
init_toolkit_sync()
|
||||
|
||||
AgentRunServer(invoke_agent=invoke_agent).start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
"""
|
||||
curl 127.0.0.1:9000/openai/v1/chat/completions -XPOST \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"messages": [{"role": "user", "content": "写一段代码,查询现在是几点?"}],
|
||||
"stream":true
|
||||
}'
|
||||
"""
|
||||
81
hgctl/pkg/manifests/agent/template/agentrun_s.tmpl
Normal file
81
hgctl/pkg/manifests/agent/template/agentrun_s.tmpl
Normal file
@@ -0,0 +1,81 @@
|
||||
edition: 3.0.0
|
||||
name: agentrun-app
|
||||
access: "{{ .AccessKey }}"
|
||||
|
||||
resources:
|
||||
hgctl-agent2:
|
||||
component: agentrun
|
||||
props:
|
||||
region: "{{ .Region }}"
|
||||
|
||||
# ============= 新规范:agent 配置 =============
|
||||
agent:
|
||||
# 基本信息
|
||||
name: "{{ .AgentName }}"
|
||||
description: "{{ .AgentDesc }}"
|
||||
|
||||
# 代码配置(直接指定路径,支持目录或 zip 文件,或使用 OSS 代码包)
|
||||
code:
|
||||
src: .
|
||||
# ossBucketName: funagent-agent-quickstart-langchain-demo-code
|
||||
# ossObjectName: agentrun-quickstart-code.zip
|
||||
language: python3.12
|
||||
command:
|
||||
- python3
|
||||
- agentrun_main.py
|
||||
|
||||
# 容器配置(使用容器模式时配置此项)
|
||||
# customContainerConfig:
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/my-app:latest
|
||||
# command:
|
||||
# - python3
|
||||
# - app.py
|
||||
# port: 9000
|
||||
|
||||
# 资源配置
|
||||
cpu: 2.0
|
||||
memory: 4096
|
||||
diskSize: {{ .DiskSize }} # 可选,默认 512 MB
|
||||
timeout: {{ .Timeout }} # 可选,默认 600 秒
|
||||
|
||||
# 端口和并发
|
||||
port: {{ .Port }}
|
||||
instanceConcurrency: 100
|
||||
|
||||
# 网络配置 - 仅公网访问
|
||||
internetAccess: true
|
||||
|
||||
# VPC 配置(需要 VPC 内网访问时配置)
|
||||
# vpcConfig:
|
||||
# vpcId: vpc-xxx
|
||||
# vSwitchIds: [vsw-xxx] # 支持单个或多个
|
||||
# securityGroupId: sg-xxx
|
||||
# internetAccess: true # 同时配置 vpcConfig 和 internetAccess 表示内外网都可访问
|
||||
|
||||
# 环境变量,需要填写以下环境变量使用,推荐使用无明文AK方式,在下方填写授信给FC,包含AliyunAgentRunFullAccess的执行角色
|
||||
environmentVariables:
|
||||
AGENTRUN_ACCESS_KEY_ID: "{{ .GlobalConfig.AlibabaCloudAccessKeyID }}"
|
||||
AGENTRUN_ACCESS_KEY_SECRET: "{{ .GlobalConfig.AlibabaCloudAccessKeySecret }}"
|
||||
AGENTRUN_ACCOUNT_ID: "{{ .GlobalConfig.AgentRunAccountID }}"
|
||||
AGENTRUN_REGION: "{{ .GlobalConfig.AgentRunRegion }}"
|
||||
|
||||
# 执行角色,填写此角色,无需填写上方AK、SK敏感凭据的环境变量,角色需要授信给FC,包含AliyunAgentRunFullAccess
|
||||
# role: acs:ram::1160216277279558:role/AliyunFCDefaultRole
|
||||
|
||||
# 日志配置
|
||||
# logConfig:
|
||||
# project: ws-testhz
|
||||
# logstore: acs-ecs-system
|
||||
|
||||
# 端点配置
|
||||
endpoints:
|
||||
- name: prod
|
||||
|
||||
version: LATEST
|
||||
description: "生产环境端点"
|
||||
|
||||
# 灰度发布示例
|
||||
# - name: gray
|
||||
# version: 2
|
||||
# description: "灰度环境端点"
|
||||
# weight: 0.2 # 20% 流量到版本 2
|
||||
122
hgctl/pkg/manifests/agent/template/agentscope.tmpl
Normal file
122
hgctl/pkg/manifests/agent/template/agentscope.tmpl
Normal file
@@ -0,0 +1,122 @@
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
from agentscope_runtime.engine import AgentApp
|
||||
from agentscope_runtime.engine.schemas.agent_schemas import AgentRequest
|
||||
|
||||
from agentscope.model import {{ .Provider }}Model
|
||||
from agentscope.formatter import {{ .Provider }}Formatter
|
||||
|
||||
from agentscope_runtime.adapters.agentscope.memory import AgentScopeSessionHistoryMemory
|
||||
from agentscope_runtime.engine.services.agent_state import InMemoryStateService
|
||||
from agentscope_runtime.engine.services.session_history import InMemorySessionHistoryService
|
||||
|
||||
from agentscope.pipeline import stream_printing_messages
|
||||
|
||||
from agentscope_runtime.engine.deployers.local_deployer import LocalDeployManager
|
||||
from agentscope_runtime.engine.deployers.utils.deployment_modes import DeploymentMode
|
||||
|
||||
from agent import Agent
|
||||
from toolkit import toolkit, init_toolkit_sync
|
||||
|
||||
app = AgentApp(
|
||||
app_name="{{.AppName}}",
|
||||
app_description="{{.AppDescription}}",
|
||||
)
|
||||
|
||||
def load_sys_prompt(prompt_file_name="prompt.md"):
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
prompt_path = os.path.join(script_dir, prompt_file_name)
|
||||
|
||||
with open(prompt_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
@app.init
|
||||
async def init_func(self):
|
||||
"""初始化状态和会话服务"""
|
||||
self.state_service = InMemoryStateService()
|
||||
self.session_service = InMemorySessionHistoryService()
|
||||
await self.state_service.start()
|
||||
await self.session_service.start()
|
||||
|
||||
|
||||
@app.shutdown
|
||||
async def shutdown_func(self):
|
||||
"""清理服务"""
|
||||
await self.state_service.stop()
|
||||
await self.session_service.stop()
|
||||
|
||||
@app.query(framework="agentscope")
|
||||
async def query_func(self, msgs, request: AgentRequest, **kwargs):
|
||||
session_id = request.session_id
|
||||
user_id = request.user_id
|
||||
|
||||
# 恢复 Agent 状态
|
||||
state = await self.state_service.export_state(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
# ---- 创建 Agent ----
|
||||
agent = Agent(
|
||||
name="{{.AgentName}}",
|
||||
model={{ .Provider }}Model(
|
||||
"{{.ChatModel}}",
|
||||
api_key=os.getenv("{{.APIKeyEnvVar}}"),
|
||||
stream={{.EnableStreaming | boolToPython}},
|
||||
),
|
||||
sys_prompt=load_sys_prompt(),
|
||||
toolkit=toolkit,
|
||||
memory=AgentScopeSessionHistoryMemory(
|
||||
service=self.session_service,
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
),
|
||||
formatter={{ .Provider }}Formatter(),
|
||||
)
|
||||
agent.set_console_output_enabled(enabled=False)
|
||||
|
||||
# 恢复状态
|
||||
if state:
|
||||
agent.load_state_dict(state)
|
||||
|
||||
# ---- 流式输出 ----
|
||||
async for msg, last in stream_printing_messages(
|
||||
agents=[agent],
|
||||
coroutine_task=agent(msgs),
|
||||
):
|
||||
yield msg, last
|
||||
|
||||
# ---- 保存 Agent 状态 ----
|
||||
state = agent.state_dict()
|
||||
await self.state_service.save_state(
|
||||
user_id=user_id,
|
||||
session_id=session_id,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
"""以独立进程模式部署应用"""
|
||||
deployment_info = await app.deploy(
|
||||
LocalDeployManager(host="{{.HostBinding}}", port={{.DeploymentPort}}),
|
||||
mode=DeploymentMode.DETACHED_PROCESS,
|
||||
)
|
||||
url = deployment_info['url']
|
||||
print(f"✅ 部署成功:{url}")
|
||||
print(f"📍 部署 ID:{deployment_info['deploy_id']}")
|
||||
print(
|
||||
f"""
|
||||
Check health: curl {url}/health
|
||||
Shutdown: curl -X POST {url}/admin/shutdown
|
||||
"""
|
||||
)
|
||||
print(f"🌟 You can deploy it to Higress by using: hgctl agent add {url}")
|
||||
|
||||
return deployment_info
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_toolkit_sync()
|
||||
asyncio.run(main())
|
||||
69
hgctl/pkg/manifests/agent/template/toolkit.tmpl
Normal file
69
hgctl/pkg/manifests/agent/template/toolkit.tmpl
Normal file
@@ -0,0 +1,69 @@
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
from agentscope.tool import Toolkit
|
||||
from agentscope.tool import execute_shell_command
|
||||
from agentscope.tool import view_text_file
|
||||
from agentscope.tool import write_text_file
|
||||
from agentscope.tool import insert_text_file
|
||||
from agentscope.tool import dashscope_text_to_image
|
||||
from agentscope.tool import dashscope_text_to_audio
|
||||
from agentscope.tool import dashscope_image_to_text
|
||||
from agentscope.tool import openai_text_to_image
|
||||
from agentscope.tool import openai_text_to_audio
|
||||
from agentscope.tool import openai_edit_image
|
||||
from agentscope.tool import openai_create_image_variation
|
||||
from agentscope.tool import openai_image_to_text
|
||||
from agentscope.tool import openai_audio_to_text
|
||||
from agentscope.tool import execute_python_code
|
||||
from agentscope.mcp import HttpStatelessClient
|
||||
|
||||
toolkit = Toolkit()
|
||||
|
||||
|
||||
def _register_tools():
|
||||
{{range .AvailableTools}}
|
||||
toolkit.register_tool_function({{.}})
|
||||
{{else}}
|
||||
pass
|
||||
{{end}}
|
||||
|
||||
|
||||
def init_toolkit_sync():
|
||||
_register_tools()
|
||||
asyncio.run(register_all_MCP(toolkit))
|
||||
|
||||
|
||||
async def init_toolkit_async():
|
||||
_register_tools()
|
||||
await register_all_MCP(toolkit)
|
||||
|
||||
|
||||
async def register_single_MCP(toolkit: Toolkit, mcp_config):
|
||||
"""注册单个MCP服务器"""
|
||||
headers = mcp_config.get("Headers") or None
|
||||
|
||||
api_client = HttpStatelessClient(
|
||||
name=mcp_config["Name"],
|
||||
transport=mcp_config["Transport"],
|
||||
url=mcp_config["URL"],
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
await toolkit.register_mcp_client(api_client)
|
||||
|
||||
|
||||
async def register_all_MCP(toolkit: Toolkit):
|
||||
"""注册所有配置的MCP服务器"""
|
||||
{{- range .MCPServers }}
|
||||
await register_single_MCP(toolkit, {
|
||||
"Name": "{{ .Name }}",
|
||||
"URL": "{{ .URL }}",
|
||||
"Transport": "{{ .Transport }}",
|
||||
"Headers": {
|
||||
{{- range $key, $value := .Headers }}
|
||||
"{{ $key }}": "{{ $value }}",
|
||||
{{- end }}
|
||||
}
|
||||
})
|
||||
{{- end }}
|
||||
Reference in New Issue
Block a user