186 lines
6.9 KiB
Python
186 lines
6.9 KiB
Python
from google.adk.agents import LlmAgent
|
||
from google.adk.models.lite_llm import LiteLlm
|
||
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
|
||
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
|
||
from google.adk.tools.function_tool import FunctionTool
|
||
from mcp.client.stdio import StdioServerParameters
|
||
import os
|
||
import asyncio
|
||
from dotenv import load_dotenv
|
||
|
||
# Load environment variables from .env file
|
||
load_dotenv()
|
||
|
||
# --- 使用 vLLM 端点的智能体 ---
|
||
api_base_url = os.getenv("VLLM_API_BASE", "https://9router.aqroid.cn/v1")
|
||
model_name = os.getenv("VLLM_MODEL", "")
|
||
api_key = os.getenv("VLLM_API_KEY", "")
|
||
|
||
# Agent 可访问的工作目录
|
||
WORKSPACE_DIR = os.getenv("AGENT_WORKSPACE_DIR", r"D:\nzy\workspace_git")
|
||
|
||
# --- 文件系统 MCP 工具 ---
|
||
filesystem_mcp = McpToolset(
|
||
connection_params=StdioConnectionParams(
|
||
server_params=StdioServerParameters(
|
||
command="npx",
|
||
args=[
|
||
"-y",
|
||
"@modelcontextprotocol/server-filesystem",
|
||
os.path.abspath(WORKSPACE_DIR),
|
||
],
|
||
),
|
||
timeout=300000,
|
||
),
|
||
)
|
||
|
||
# --- 网页抓取 MCP 工具(Fetch)---
|
||
# 暂时注释:请求体过大导致 413 错误,后续需要时再启用
|
||
# fetch_mcp = McpToolset(
|
||
# connection_params=StdioConnectionParams(
|
||
# server_params=StdioServerParameters(
|
||
# command="npx",
|
||
# args=[
|
||
# "-y",
|
||
# "@modelcontextprotocol/server-fetch",
|
||
# ],
|
||
# ),
|
||
# timeout=300000,
|
||
# ),
|
||
# )
|
||
|
||
# --- Tavily 搜索 MCP 工具 ---
|
||
# 暂时注释:请求体过大导致 413 错误,后续需要时再启用
|
||
# tavily_api_key = os.getenv("TAVILY_API_KEY", "")
|
||
# tavily_mcp = McpToolset(
|
||
# connection_params=StdioConnectionParams(
|
||
# server_params=StdioServerParameters(
|
||
# command="npx",
|
||
# args=[
|
||
# "-y",
|
||
# "tavily-mcp@latest",
|
||
# ],
|
||
# env={
|
||
# "TAVILY_API_KEY": tavily_api_key,
|
||
# },
|
||
# ),
|
||
# timeout=300000,
|
||
# ),
|
||
# )
|
||
|
||
|
||
# --- 终端命令执行工具(Python 原生,绕开 MCP 通信问题)---
|
||
async def run_command(command: str, cwd: str = None, timeout: int = 300000000) -> str:
|
||
"""
|
||
在终端中执行一条命令,返回输出结果。
|
||
|
||
Args:
|
||
command: 要执行的命令,如 'npm run build'、'python -m pytest' 等
|
||
cwd: 命令执行的工作目录,默认使用 AGENT_WORKSPACE_DIR
|
||
timeout: 超时时间(秒),默认 300
|
||
|
||
Returns:
|
||
命令执行结果(stdout + stderr + 状态)
|
||
"""
|
||
if not command:
|
||
return "错误:命令不能为空"
|
||
|
||
work_dir = cwd or os.path.abspath(WORKSPACE_DIR)
|
||
if not os.path.isdir(work_dir):
|
||
return f"错误:工作目录不存在 {work_dir}"
|
||
|
||
try:
|
||
proc = await asyncio.create_subprocess_shell(
|
||
command,
|
||
cwd=work_dir,
|
||
stdout=asyncio.subprocess.PIPE,
|
||
stderr=asyncio.subprocess.PIPE,
|
||
)
|
||
stdout_bytes, stderr_bytes = await asyncio.wait_for(
|
||
proc.communicate(), timeout=timeout
|
||
)
|
||
except asyncio.TimeoutError:
|
||
proc.kill()
|
||
await proc.wait()
|
||
return f"命令执行超时({timeout}秒): {command}"
|
||
except Exception as e:
|
||
return f"命令执行出错: {e}"
|
||
|
||
stdout = stdout_bytes.decode("utf-8", errors="replace")
|
||
stderr = stderr_bytes.decode("utf-8", errors="replace")
|
||
|
||
parts = []
|
||
if stdout:
|
||
parts.append(f"[stdout]\n{stdout}")
|
||
if stderr:
|
||
parts.append(f"[stderr]\n{stderr}")
|
||
|
||
output = "\n".join(parts) if parts else "(无输出)"
|
||
|
||
max_len = 10000
|
||
if len(output) > max_len:
|
||
output = output[:max_len] + f"\n\n...(输出已截断,共 {len(output)} 字符)"
|
||
|
||
status = "成功" if proc.returncode == 0 else f"失败 (退出码 {proc.returncode})"
|
||
return f"命令执行{status}\n{output}"
|
||
|
||
|
||
# 注册为 ADK 工具
|
||
run_command_tool = FunctionTool(run_command)
|
||
|
||
|
||
root_agent = LlmAgent(
|
||
model=LiteLlm(
|
||
model=model_name,
|
||
api_base=api_base_url,
|
||
api_key=api_key if api_key else None,
|
||
custom_llm_provider="openai",
|
||
),
|
||
name="luna_agent",
|
||
description="全栈开发子 Agent(gpt-5.6-luna),可以读写文件、浏览目录、执行开发任务。",
|
||
instruction=(
|
||
"你是luna,一个使用gpt5.6-luna的前后端开发子agent\n"
|
||
"\n"
|
||
"## 记忆能力\n"
|
||
"- 你拥有长期记忆,之前和用户的对话中提到的项目信息、技术偏好、任务历史都会被记住\n"
|
||
"- 系统会自动从记忆中检索与当前任务相关的历史上下文,注入到对话中\n"
|
||
"- 重要的项目信息(技术栈、目录结构、编码规范等)会自动沉淀到记忆里\n"
|
||
"\n"
|
||
"## 工作流程\n"
|
||
"1. 先理解任务需求和项目上下文\n"
|
||
"2. 使用文件系统工具浏览项目结构、读取相关文件\n"
|
||
"3. 编写或修改代码\n"
|
||
"4. 使用 run_command 工具运行编译/构建/测试,确保代码可正常工作\n"
|
||
"5. 验证结果后,按指定格式报告完成情况\n"
|
||
"\n"
|
||
"## 工作边界\n"
|
||
"- 所有文件操作限定在分配的工作目录范围内\n"
|
||
"- 你拥有的工具:文件系统操作(读/写/列目录)、终端命令执行\n"
|
||
"- 你可以自主完成:代码编写、bug 修复、样式调整、接口修改、简单重构\n"
|
||
"- 遇到不熟悉的技术或 API,先查阅项目内的现有代码和文档参考\n"
|
||
"- 需要上报的情况:\n"
|
||
" • 架构设计或重大技术选型决策\n"
|
||
" • 依赖包版本不兼容导致的编译/运行时错误(需要升级/降级依赖时)\n"
|
||
" • 工具调用异常、环境配置问题、命令超时等非代码问题\n"
|
||
" • 超出你能力范围或不确定的问题\n"
|
||
"\n"
|
||
"## 编译/构建守则\n"
|
||
"- 写完代码后,优先运行编译或构建命令验证\n"
|
||
"- 编译报错时,先判断错误类型:\n"
|
||
" • 代码语法/逻辑错误 → 自行修复后重试\n"
|
||
" • 依赖缺失或版本不兼容 → 上报,由主控决定处理方式\n"
|
||
" • 环境/工具问题 → 上报\n"
|
||
"- 连续修复 3 次仍无法通过编译时,上报当前状态和所有错误信息\n"
|
||
"- 只有编译通过后才算任务完成\n"
|
||
"\n"
|
||
"## 报告格式\n"
|
||
"完成任务后,结构化报告:\n"
|
||
"**状态**:成功 / 部分完成 / 失败(需上报)\n"
|
||
"**修改的文件**:列出所有修改的文件路径\n"
|
||
"**变更摘要**:简述做了什么\n"
|
||
"**验证结果**:编译/测试是否通过,如有警告需列出\n"
|
||
"**需要主控关注**:如有需要上报的问题,详细说明"
|
||
),
|
||
tools=[filesystem_mcp, run_command_tool],
|
||
)
|