154 lines
5.3 KiB
Python
154 lines
5.3 KiB
Python
"""
|
||
封装 ADK Agent 调用
|
||
将 Dev Agent 的执行包装为可被任务管理器调用的异步函数
|
||
"""
|
||
import os
|
||
import sys
|
||
|
||
# 确保项目根目录在 path 里
|
||
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||
if PROJECT_ROOT not in sys.path:
|
||
sys.path.insert(0, PROJECT_ROOT)
|
||
|
||
from google.adk.runners import Runner
|
||
from google.adk.sessions import InMemorySessionService
|
||
from google.genai import types
|
||
|
||
|
||
class AgentRunner:
|
||
def __init__(self):
|
||
# 延迟导入 agent,避免循环导入
|
||
from agents.my_agent.agent import root_agent
|
||
self.agent = root_agent
|
||
self.session_service = InMemorySessionService()
|
||
self._runner: Runner = None
|
||
|
||
def _get_runner(self) -> Runner:
|
||
if self._runner is None:
|
||
self._runner = Runner(
|
||
agent=self.agent,
|
||
app_name="dev_agent_server",
|
||
session_service=self.session_service,
|
||
auto_create_session=True,
|
||
)
|
||
return self._runner
|
||
|
||
async def run_task(self, task: dict) -> dict:
|
||
"""
|
||
执行一个开发任务,返回结构化结果
|
||
|
||
Args:
|
||
task: 任务字典,包含 description, project_path, requirements 等
|
||
|
||
Returns:
|
||
结构化的任务结果
|
||
"""
|
||
task_id = task["id"]
|
||
description = task["description"]
|
||
project_path = task.get("project_path", "")
|
||
requirements = task.get("requirements", "")
|
||
|
||
# 构建给 agent 的提示词
|
||
prompt = self._build_prompt(description, project_path, requirements)
|
||
|
||
print(f"[AgentRunner] 执行任务 {task_id}: {description[:60]}...")
|
||
|
||
runner = self._get_runner()
|
||
session_id = f"task_{task_id}"
|
||
user_id = "task_manager"
|
||
|
||
all_text = [] # 收集所有 model 消息中的文本
|
||
tool_calls = [] # 收集所有 function_call
|
||
final_text = [] # 最终响应的文本
|
||
event_count = 0
|
||
error_msg = None
|
||
|
||
try:
|
||
async for event in runner.run_async(
|
||
user_id=user_id,
|
||
session_id=session_id,
|
||
new_message=types.Content(
|
||
role="user",
|
||
parts=[types.Part(text=prompt)],
|
||
),
|
||
):
|
||
event_count += 1
|
||
|
||
# 跳过没有 content 的事件
|
||
if not event.content:
|
||
continue
|
||
|
||
# 只收集 model 角色的消息(不是 user / function_response 等)
|
||
content = event.content
|
||
role = getattr(content, "role", "")
|
||
if role != "model":
|
||
continue
|
||
|
||
# 遍历 parts 收集文本和 function_call
|
||
parts = getattr(content, "parts", [])
|
||
for part in parts:
|
||
# 文本
|
||
if hasattr(part, "text") and part.text:
|
||
all_text.append(part.text)
|
||
# 最终回复(没有 function_call 的 model 消息)
|
||
if event.is_final_response():
|
||
final_text.append(part.text)
|
||
|
||
# function_call
|
||
if hasattr(part, "function_call") and part.function_call:
|
||
fc = part.function_call
|
||
tool_calls.append({
|
||
"name": fc.name,
|
||
"args": dict(fc.args) if hasattr(fc, "args") else {},
|
||
})
|
||
|
||
result_text = "\n".join(final_text) if final_text else "\n".join(all_text)
|
||
status = "success" if result_text.strip() else "empty"
|
||
|
||
except Exception as e:
|
||
error_msg = f"{type(e).__name__}: {e}"
|
||
result_text = f"任务执行出错:{error_msg}"
|
||
status = "error"
|
||
|
||
return {
|
||
"summary": self._extract_summary(result_text),
|
||
"full_response": result_text,
|
||
"tool_calls_count": len(tool_calls),
|
||
"tool_calls_sample": tool_calls[:10],
|
||
"event_count": event_count,
|
||
"status": status,
|
||
"error": error_msg,
|
||
}
|
||
|
||
def _build_prompt(self, description: str, project_path: str, requirements: str) -> str:
|
||
"""构建给 agent 的任务指令"""
|
||
parts = [
|
||
"你需要完成以下开发任务:",
|
||
"",
|
||
f"**任务描述:**{description}",
|
||
]
|
||
if project_path:
|
||
parts.append(f"**项目路径:**{project_path}")
|
||
if requirements:
|
||
parts.append(f"**额外要求:**{requirements}")
|
||
parts.extend([
|
||
"",
|
||
"请按照你的工作流程执行:",
|
||
"1. 浏览项目结构,理解上下文",
|
||
"2. 编写或修改代码",
|
||
"3. 运行编译/构建验证",
|
||
"4. 完成后,按照你规定的报告格式输出结果",
|
||
"",
|
||
"请开始执行。",
|
||
])
|
||
return "\n".join(parts)
|
||
|
||
def _extract_summary(self, text: str) -> str:
|
||
"""从 agent 回复中提取摘要"""
|
||
# 简单处理:取前 1000 字符作为摘要
|
||
if not text:
|
||
return "(无响应)"
|
||
if len(text) <= 1000:
|
||
return text
|
||
return text[:1000] + "...(已截断)"
|