diff --git a/a2a_client.py b/a2a_client.py new file mode 100644 index 0000000..e9143a1 --- /dev/null +++ b/a2a_client.py @@ -0,0 +1,66 @@ +""" +A2A 客户端测试脚本 +通过 A2A 协议调用本地的 Dev Agent 服务。 + +使用方式: + python a2a_client.py "你好,介绍一下你自己" + python a2a_client.py --context my_session "你好" # 指定 context_id(会话) +""" +import asyncio +import sys +import argparse + +from a2a.client import create_client +from a2a.types.a2a_pb2 import SendMessageRequest, Message, Part, Role + + +A2A_URL = "http://127.0.0.1:8001" + + +async def main(): + parser = argparse.ArgumentParser(description="A2A 客户端测试") + parser.add_argument("message", help="发送给 agent 的消息") + parser.add_argument("--context", "-c", default="", help="context_id,用于会话续传") + args = parser.parse_args() + + print(f"连接到: {A2A_URL}") + if args.context: + print(f"会话: {args.context}") + print(f"发送: {args.message}") + print("-" * 40) + + client = await create_client(A2A_URL) + try: + # 构建请求 + request = SendMessageRequest( + message=Message( + role=Role.ROLE_USER, + parts=[Part(text=args.message)], + ), + ) + if args.context: + request.message.context_id = args.context + + # 发送消息(流式返回) + full_text = "" + context_id = "" + async for response in client.send_message(request): + # 流式响应里可能有 message、status_update 等 + if response.HasField("message"): + msg = response.message + if msg.context_id: + context_id = msg.context_id + for part in msg.parts: + if part.text: + full_text += part.text + + print("Agent 回复:") + print(full_text) + if context_id: + print(f"\n(context_id: {context_id})") + finally: + await client.close() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/a2a_server.py b/a2a_server.py index 354ce0d..3e55cd8 100644 --- a/a2a_server.py +++ b/a2a_server.py @@ -26,24 +26,62 @@ os.environ["PYTHONUTF8"] = "1" import uvicorn from google.adk.a2a.utils.agent_to_a2a import to_a2a -from my_agent.agent import root_agent +from google.adk.runners import Runner +from google.adk.sessions.sqlite_session_service import SqliteSessionService +from google.adk.memory.in_memory_memory_service import InMemoryMemoryService +from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService +from my_agent.app import dev_app # 配置 -HOST = os.getenv("A2A_SERVER_HOST", "0.0.0.0") +HOST = os.getenv("A2A_SERVER_HOST", "0.0.0.0") # 监听地址 PORT = int(os.getenv("A2A_SERVER_PORT", "8001")) +# Agent Card 中对外公布的地址(客户端用这个来连接,不能用 0.0.0.0) +PUBLIC_HOST = os.getenv("A2A_PUBLIC_HOST", "127.0.0.1") + +# 数据目录 +DATA_DIR = os.path.join(PROJECT_ROOT, "data") +os.makedirs(DATA_DIR, exist_ok=True) + +# --- Session 服务:SQLite 持久化(重启不丢失对话历史)--- +session_service = SqliteSessionService( + db_path=os.path.join(DATA_DIR, "sessions.db") +) + +# --- Memory 服务:长期记忆(先用内存版,后续可换 Chroma 等向量库)--- +memory_service = InMemoryMemoryService() + +# --- Artifact 服务:工件存储(大文件等)--- +artifact_service = InMemoryArtifactService() + +# --- 构建 Runner:会话 + 记忆 + 压缩 一体化 --- +runner = Runner( + app=dev_app, + session_service=session_service, + memory_service=memory_service, + artifact_service=artifact_service, + auto_create_session=True, +) # 用 ADK 官方工具把 agent 转成 A2A 服务 -# 会自动生成 agent card,暴露 /a2a/{agent_name} 端点 -a2a_app = to_a2a(root_agent, port=PORT) +# 传入自定义 runner,启用 SQLite 持久化 + 上下文压缩 + 记忆 +a2a_app = to_a2a( + agent=dev_app.root_agent, + host=PUBLIC_HOST, # agent card 里用的对外地址 + port=PORT, + runner=runner, +) def main(): print("=" * 60) print("Dev Agent A2A Server 启动中...") print(f" 监听地址: http://{HOST}:{PORT}") - print(f" A2A 端点: http://{HOST}:{PORT}/a2a/{root_agent.name}") - print(f" Agent 卡片: http://{HOST}:{PORT}/.well-known/agent-card.json") + print(f" 对外地址: http://{PUBLIC_HOST}:{PORT}") + print(f" A2A 端点: http://{PUBLIC_HOST}:{PORT}/") + print(f" Agent 卡片: http://{PUBLIC_HOST}:{PORT}/.well-known/agent-card.json") + print(f" 会话持久化: SQLite ({DATA_DIR}/sessions.db)") + print(f" 上下文压缩: 每 20 轮自动摘要") print("=" * 60) uvicorn.run(a2a_app, host=HOST, port=PORT, log_level="info") diff --git a/api_server.py b/api_server.py new file mode 100644 index 0000000..562a7b9 --- /dev/null +++ b/api_server.py @@ -0,0 +1,120 @@ +""" +Dev Agent API Server +使用 ADK 官方 ApiServer 构建 REST API 服务,支持: +- REST API 调用 agent(/run、/run_sse) +- 会话管理(创建/获取/删除,SQLite 持久化) +- Swagger UI 交互式文档(/docs) +- 上下文自动压缩 +- 长期记忆(InMemory,后续可换向量库) + +启动方式: + python api_server.py + +主要端点: + GET /list-apps 列出所有 agent + POST /run 同步运行 agent + POST /run_sse 流式运行 agent(SSE) + GET /apps/{app}/users/{user}/sessions/{session} 获取会话 + POST /apps/{app}/users/{user}/sessions/{session} 创建会话 + GET /docs Swagger UI +""" +import os +import sys + +# 确保项目根目录在 path 里 +PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) +if PROJECT_ROOT not in sys.path: + sys.path.insert(0, PROJECT_ROOT) + +from dotenv import load_dotenv +load_dotenv(os.path.join(PROJECT_ROOT, "my_agent", ".env")) + +# 强制 UTF-8 +os.environ["PYTHONUTF8"] = "1" + +import uvicorn +from google.adk.cli.api_server import ApiServer +from google.adk.cli.utils.base_agent_loader import BaseAgentLoader +from google.adk.sessions.sqlite_session_service import SqliteSessionService +from google.adk.memory.in_memory_memory_service import InMemoryMemoryService +from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService +from google.adk.auth.credential_service.in_memory_credential_service import InMemoryCredentialService +from google.adk.evaluation.in_memory_eval_sets_manager import InMemoryEvalSetsManager +from google.adk.evaluation.local_eval_set_results_manager import LocalEvalSetResultsManager +from my_agent.app import dev_app + + +# 配置 +HOST = os.getenv("API_SERVER_HOST", "0.0.0.0") +PORT = int(os.getenv("API_SERVER_PORT", "8001")) + +# 数据目录 +DATA_DIR = os.path.join(PROJECT_ROOT, "data") +os.makedirs(DATA_DIR, exist_ok=True) + + +class DevAgentLoader(BaseAgentLoader): + """自定义 agent 加载器,直接返回我们的 App 对象(带 compaction 配置)""" + + def load_agent(self, agent_name: str): + if agent_name == dev_app.name: + return dev_app + raise ValueError(f"Agent not found: {agent_name}") + + def list_agents(self) -> list[str]: + return [dev_app.name] + + +def create_api_server() -> ApiServer: + """构造 ApiServer 实例""" + # 会话服务:SQLite 持久化 + session_service = SqliteSessionService( + db_path=os.path.join(DATA_DIR, "sessions.db") + ) + + # 记忆服务:长期记忆(先用内存版) + memory_service = InMemoryMemoryService() + + # 工件服务 + artifact_service = InMemoryArtifactService() + + # 认证服务(暂不需要,内存版占位) + credential_service = InMemoryCredentialService() + + # 评测集管理(暂不需要,占位) + eval_sets_manager = InMemoryEvalSetsManager() + eval_set_results_manager = LocalEvalSetResultsManager(agents_dir=DATA_DIR) + + return ApiServer( + agent_loader=DevAgentLoader(), + session_service=session_service, + memory_service=memory_service, + artifact_service=artifact_service, + credential_service=credential_service, + eval_sets_manager=eval_sets_manager, + eval_set_results_manager=eval_set_results_manager, + agents_dir=os.path.join(PROJECT_ROOT, "my_agent"), + auto_create_session=True, + ) + + +def main(): + api_server = create_api_server() + fastapi_app = api_server.get_fast_api_app() + + print("=" * 60) + print("Dev Agent API Server 启动中...") + print(f" 监听地址: http://{HOST}:{PORT}") + print(f" Swagger UI: http://{HOST}:{PORT}/docs") + print(f" 同步运行: POST http://{HOST}:{PORT}/run") + print(f" 流式运行: POST http://{HOST}:{PORT}/run_sse") + print(f" 列出agent: GET http://{HOST}:{PORT}/list-apps") + print(f" 会话持久化: SQLite ({DATA_DIR}/sessions.db)") + print(f" 上下文压缩: 已启用") + print("=" * 60) + + uvicorn.run(fastapi_app, host=HOST, port=PORT, log_level="info") + + +if __name__ == "__main__": + main() diff --git a/chat.py b/chat.py new file mode 100644 index 0000000..0fdb261 --- /dev/null +++ b/chat.py @@ -0,0 +1,169 @@ +""" +Dev Agent 命令行交互工具 +使用配置好的 Runner(SQLite 会话持久化 + Memory + 上下文压缩), +退出后再次进入同一个 session_id 可以继续对话。 + +使用方式: + python chat.py # 新会话,自动生成 session_id + python chat.py --session my_session # 指定 session_id + python chat.py --list # 列出所有会话 + python chat.py --delete my_session # 删除某个会话 +""" +import os +import sys +import asyncio +import argparse + +# 确保项目根目录在 path 里 +PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) +if PROJECT_ROOT not in sys.path: + sys.path.insert(0, PROJECT_ROOT) + +from dotenv import load_dotenv +load_dotenv(os.path.join(PROJECT_ROOT, "my_agent", ".env")) + +# 强制 UTF-8 +os.environ["PYTHONUTF8"] = "1" + +from google.adk.runners import Runner +from google.adk.sessions.sqlite_session_service import SqliteSessionService +from google.adk.memory.in_memory_memory_service import InMemoryMemoryService +from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService +from google.genai import types +from my_agent.app import dev_app + + +# 数据目录 +DATA_DIR = os.path.join(PROJECT_ROOT, "data") +os.makedirs(DATA_DIR, exist_ok=True) + +# 同一个数据库,A2A server 和 CLI 共享 +DB_PATH = os.path.join(DATA_DIR, "sessions.db") + +USER_ID = "local_user" + + +def get_runner() -> Runner: + """创建带 SQLite 会话持久化的 Runner""" + session_service = SqliteSessionService(db_path=DB_PATH) + memory_service = InMemoryMemoryService() + artifact_service = InMemoryArtifactService() + + return Runner( + app=dev_app, + session_service=session_service, + memory_service=memory_service, + artifact_service=artifact_service, + auto_create_session=True, + ) + + +async def list_sessions(): + """列出所有会话""" + runner = get_runner() + response = await runner.session_service.list_sessions( + app_name=dev_app.name, + user_id=USER_ID, + ) + sessions = response.sessions + if not sessions: + print("(暂无会话)") + return + + print(f"共 {len(sessions)} 个会话:\n") + for s in sessions: + # 取第一条用户消息作为摘要 + preview = "" + for e in s.events: + if e.content and e.content.parts and e.author == "user": + text = e.content.parts[0].text[:50] + preview = f" → {text}" + break + print(f" [{s.id}] {preview}") + print(f" 更新时间: {s.last_update_time}") + + +async def delete_session(session_id: str): + """删除指定会话""" + runner = get_runner() + try: + await runner.session_service.delete_session( + app_name=dev_app.name, + user_id=USER_ID, + session_id=session_id, + ) + print(f"会话 [{session_id}] 已删除") + except Exception as e: + print(f"删除失败: {e}") + + +async def chat(session_id: str | None = None): + """交互式对话""" + runner = get_runner() + + # 如果没有指定 session_id,自动创建 + if not session_id: + session = await runner.session_service.create_session( + app_name=dev_app.name, + user_id=USER_ID, + ) + session_id = session.id + print(f"新会话已创建,session_id: {session_id}") + print(f"下次可用 `python chat.py --session {session_id}` 继续\n") + + print(f"=== Dev Agent 对话 ===") + print(f"Session: {session_id}") + print(f"输入消息开始对话,输入 quit / exit 退出\n") + + while True: + try: + user_input = input("你: ").strip() + except (EOFError, KeyboardInterrupt): + print("\n再见!") + break + + if not user_input: + continue + if user_input.lower() in ("quit", "exit", "退出"): + print("再见!") + break + + print("花花: ", end="", flush=True) + + try: + full_response = "" + async for event in runner.run_async( + user_id=USER_ID, + session_id=session_id, + new_message=types.Content(parts=[types.Part(text=user_input)]), + ): + if event.is_final_response(): + # 最终回复 + for part in event.content.parts: + if hasattr(part, "text") and part.text: + print(part.text, end="", flush=True) + full_response += part.text + print() + except Exception as e: + print(f"\n[出错] {e}") + + print() + + +def main(): + parser = argparse.ArgumentParser(description="Dev Agent 命令行交互工具") + parser.add_argument("--session", "-s", help="会话 ID,指定后继续该会话") + parser.add_argument("--list", "-l", action="store_true", help="列出所有会话") + parser.add_argument("--delete", "-d", help="删除指定会话") + args = parser.parse_args() + + if args.list: + asyncio.run(list_sessions()) + elif args.delete: + asyncio.run(delete_session(args.delete)) + else: + asyncio.run(chat(args.session)) + + +if __name__ == "__main__": + main() diff --git a/mcp_dev_agent/README.md b/mcp_dev_agent/README.md new file mode 100644 index 0000000..b6f6e77 --- /dev/null +++ b/mcp_dev_agent/README.md @@ -0,0 +1,59 @@ +# Dev Agent MCP Server + +将 Dev Agent 封装为 MCP 工具,供 CodeBuddy / Cursor / Windsurf 等 MCP 客户端调用。 + +## 功能 + +- **run_dev_agent** — 调用 Dev Agent 执行开发子任务 + - 支持文件读写、终端命令执行、编译验证 + - 支持指定 `session_id` 进行多轮对话 + +## 配置方法 + +### CodeBuddy + +在 CodeBuddy 的 MCP 配置中添加: + +```json +{ + "mcpServers": { + "dev-agent": { + "command": "python", + "args": ["d:/nzy/workspace_python/agent/mcp_dev_agent/server.py"], + "env": { + "DEV_AGENT_API_URL": "http://127.0.0.1:8001", + "DEV_AGENT_APP_NAME": "dev_agent", + "DEV_AGENT_USER_ID": "codebuddy" + } + } + } +} +``` + +### 环境变量 + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `DEV_AGENT_API_URL` | `http://127.0.0.1:8001` | Dev Agent API Server 地址 | +| `DEV_AGENT_APP_NAME` | `dev_agent` | Agent 名称 | +| `DEV_AGENT_USER_ID` | `codebuddy` | 用户 ID(用于会话隔离) | + +## 使用前提 + +1. 先启动 Dev Agent API Server: + ```bash + python api_server.py + ``` + +2. 配置 MCP server(见上方配置方法) + +3. 重启 CodeBuddy / IDE + +## 工具参数 + +### run_dev_agent + +| 参数 | 必填 | 说明 | +|------|------|------| +| `task` | ✅ | 任务描述,越详细越好 | +| `session_id` | ❌ | 会话 ID,不传则用 `default`。用于多轮对话续聊 | diff --git a/mcp_dev_agent/server.py b/mcp_dev_agent/server.py new file mode 100644 index 0000000..939b75e --- /dev/null +++ b/mcp_dev_agent/server.py @@ -0,0 +1,183 @@ +""" +Dev Agent MCP Server +将 Dev Agent 封装为 MCP 工具,供 CodeBuddy 等 MCP 客户端调用。 + +功能: +- run_dev_agent: 提交任务给 Dev Agent 执行,返回执行结果 + - 支持指定 session_id 进行多轮对话 + - 自动提取最终回复文本 + +启动方式(MCP 配置): + { + "mcpServers": { + "dev-agent": { + "command": "python", + "args": ["d:/nzy/workspace_python/agent/mcp_dev_agent/server.py"], + "env": { + "DEV_AGENT_API_URL": "http://127.0.0.1:8001", + "DEV_AGENT_APP_NAME": "dev_agent", + "DEV_AGENT_USER_ID": "codebuddy" + } + } + } + } +""" +import os +import json +import asyncio +import sys +from typing import Any + +import httpx +from mcp.server import Server +from mcp.types import Tool, TextContent +from mcp.server.stdio import stdio_server + + +# 配置 +DEV_AGENT_API_URL = os.getenv("DEV_AGENT_API_URL", "http://127.0.0.1:8001") +DEV_AGENT_APP_NAME = os.getenv("DEV_AGENT_APP_NAME", "dev_agent") +DEV_AGENT_USER_ID = os.getenv("DEV_AGENT_USER_ID", "codebuddy") + +# MCP Server +server = Server("dev-agent-mcp") + + +@server.list_tools() +async def list_tools() -> list[Tool]: + """列出可用工具""" + return [ + Tool( + name="run_dev_agent", + description=( + "调用 Dev Agent(全栈开发子 Agent)执行开发任务。\n" + "Dev Agent 可以读写文件、运行终端命令、执行编译/构建/测试。\n" + "适用于:代码编写、bug 修复、项目搭建、编译验证等开发子任务。\n" + "传参说明:task 是任务描述,session_id 可选,不传则创建新会话。" + ), + inputSchema={ + "type": "object", + "properties": { + "task": { + "type": "string", + "description": "要 Dev Agent 执行的任务描述,越详细越好", + }, + "session_id": { + "type": "string", + "description": "可选,会话 ID,用于多轮对话/续聊", + }, + }, + "required": ["task"], + }, + ), + ] + + +@server.call_tool() +async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]: + """调用工具""" + if name == "run_dev_agent": + return await _run_dev_agent(arguments) + else: + raise ValueError(f"Unknown tool: {name}") + + +async def _run_dev_agent(args: dict[str, Any]) -> list[TextContent]: + """调用 Dev Agent API 执行任务""" + task = args.get("task", "").strip() + session_id = args.get("session_id") or "default" + + if not task: + return [TextContent(type="text", text="错误:task 不能为空")] + + # 构造请求 + payload = { + "appName": DEV_AGENT_APP_NAME, + "userId": DEV_AGENT_USER_ID, + "sessionId": session_id, + "newMessage": { + "role": "user", + "parts": [{"text": task}], + }, + } + + try: + async with httpx.AsyncClient(timeout=600.0) as client: # 10 分钟超时 + response = await client.post( + f"{DEV_AGENT_API_URL}/run", + json=payload, + headers={"Content-Type": "application/json"}, + ) + + if response.status_code != 200: + return [TextContent( + type="text", + text=f"调用 Dev Agent 失败(HTTP {response.status_code}):\n{response.text[:500]}" + )] + + events = response.json() + except httpx.ConnectError: + return [TextContent( + type="text", + text=f"无法连接到 Dev Agent API Server({DEV_AGENT_API_URL})\n" + f"请确认 api_server.py 是否已启动。" + )] + except Exception as e: + return [TextContent(type="text", text=f"调用 Dev Agent 出错: {e}")] + + # 从事件列表中提取最终回复 + result_text = _extract_final_response(events, session_id) + return [TextContent(type="text", text=result_text)] + + +def _extract_final_response(events: list[dict], session_id: str) -> str: + """从事件列表中提取 agent 的最终文本回复""" + if not events: + return "(无返回事件)" + + final_text_parts = [] + + for event in events: + content = event.get("content", {}) + role = content.get("role", "") + parts = content.get("parts", []) + author = event.get("author", "") + + if role == "model" and author == DEV_AGENT_APP_NAME: + for part in parts: + if "text" in part: + final_text_parts.append(part["text"]) + + response = "\n".join(final_text_parts).strip() + + if not response: + # 如果没有找到最终回复,返回事件摘要 + summary = f"共 {len(events)} 个事件\n" + for e in events[-5:]: + content = e.get("content", {}) + parts = content.get("parts", []) + role = content.get("role", "") + author = e.get("author", "") + part_types = [list(p.keys())[0] for p in parts] + summary += f" - [{role}] {author}: {part_types}\n" + response = f"(未提取到最终文本回复)\n{summary}" + + # 附上 session_id 方便续聊 + response += f"\n\n---\nsession_id: {session_id}" + return response + + +async def main(): + """stdio 模式启动 MCP server""" + async with stdio_server() as (read_stream, write_stream): + await server.run(read_stream, write_stream, None) + + +if __name__ == "__main__": + # 确保 Windows 下 stdio 用二进制模式 + if sys.platform == "win32": + import msvcrt + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + + asyncio.run(main()) diff --git a/my_agent/agent.py b/my_agent/agent.py index e6541c7..20a3fb0 100644 --- a/my_agent/agent.py +++ b/my_agent/agent.py @@ -3,6 +3,7 @@ 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 google.adk.tools import preload_memory from mcp.client.stdio import StdioServerParameters import os import asyncio @@ -30,13 +31,47 @@ filesystem_mcp = McpToolset( os.path.abspath(WORKSPACE_DIR), ], ), - timeout=30.0, + 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 = 300) -> str: +async def run_command(command: str, cwd: str = None, timeout: int = 300000) -> str: """ 在终端中执行一条命令,返回输出结果。 @@ -94,6 +129,20 @@ async def run_command(command: str, cwd: str = None, timeout: int = 300) -> str: # 注册为 ADK 工具 run_command_tool = FunctionTool(run_command) + +# --- 记忆自动保存回调 --- +async def auto_save_memory_callback(callback_context): + """ + 每轮对话结束后,自动将会话保存到长期记忆中。 + memory_service 未配置时静默忽略。 + """ + try: + await callback_context.add_session_to_memory() + except (ValueError, AttributeError): + # memory_service 未配置或不可用时跳过 + pass + + root_agent = LlmAgent( model=LiteLlm( model=model_name, @@ -112,6 +161,11 @@ root_agent = LlmAgent( "生活里接地气、热心肠;不矫情,能扛项目压力,也会温柔安抚焦虑的开发同事\n" "由主控调度执行具体的开发任务。\n" "\n" + "## 记忆能力\n" + "- 你拥有长期记忆,之前和用户的对话中提到的项目信息、技术偏好、任务历史都会被记住\n" + "- 系统会自动从记忆中检索与当前任务相关的历史上下文,注入到对话中\n" + "- 重要的项目信息(技术栈、目录结构、编码规范等)会自动沉淀到记忆里\n" + "\n" "## 工作流程\n" "1. 先理解任务需求和项目上下文\n" "2. 使用文件系统工具浏览项目结构、读取相关文件\n" @@ -123,6 +177,7 @@ root_agent = LlmAgent( "- 所有文件操作限定在分配的工作目录范围内\n" "- 你拥有的工具:文件系统操作(读/写/列目录)、终端命令执行\n" "- 你可以自主完成:代码编写、bug 修复、样式调整、接口修改、简单重构\n" + "- 遇到不熟悉的技术或 API,先查阅项目内的现有代码和文档参考\n" "- 需要上报的情况:\n" " • 架构设计或重大技术选型决策\n" " • 依赖包版本不兼容导致的编译/运行时错误(需要升级/降级依赖时)\n" @@ -146,5 +201,6 @@ root_agent = LlmAgent( "**验证结果**:编译/测试是否通过,如有警告需列出\n" "**需要主控关注**:如有需要上报的问题,详细说明" ), - tools=[filesystem_mcp, run_command_tool], + tools=[filesystem_mcp, run_command_tool, preload_memory], + after_agent_callback=auto_save_memory_callback, ) diff --git a/my_agent/app.py b/my_agent/app.py new file mode 100644 index 0000000..61cc41d --- /dev/null +++ b/my_agent/app.py @@ -0,0 +1,23 @@ +""" +Dev Agent App 配置 +使用 ADK App 包装 agent,配置上下文压缩、插件等。 +""" +from google.adk.apps import App +from google.adk.apps._configs import EventsCompactionConfig # 实验性 API +from my_agent.agent import root_agent + + +# 上下文压缩配置(长对话自动摘要,防止爆 context window) +compaction_config = EventsCompactionConfig( + compaction_interval=20, # 每 20 个用户轮次压缩一次 + overlap_size=3, # 重叠 3 轮,保持连续性 + token_threshold=50000, # token 超 50k 紧急压缩 + event_retention_size=30, # 压缩时保留最近 30 条原始事件 +) + +# App 容器:管理 agent + 压缩配置 +dev_app = App( + name="dev_agent", + root_agent=root_agent, + events_compaction_config=compaction_config, +)