Files
Celyn 9633ee1f7b feat(task): 添加任务停止运行功能 (#79)
- 后端:新增 POST /modeling/{task_id}/cancel 取消接口
- 后端:实现 asyncio.Event 取消机制,支持中断 LLM 调用
- 前端:任务详情页添加"停止运行"按钮
- 修复:取消后避免调用 md_2_docx 的错误
2026-05-18 10:41:32 +08:00

220 lines
9.7 KiB
Python

"""代码手 Agent 模块,负责生成和执行 Python 代码完成建模任务。"""
import asyncio
from app.core.agents.agent import Agent
from app.config.setting import settings, ApiType
from app.utils.log_util import logger
from app.services.redis_manager import redis_manager
from app.schemas.response import SystemMessage, InterpreterMessage
from app.tools.base_interpreter import BaseCodeInterpreter
from app.core.llm.llm import LLM
from app.schemas.A2A import CoderToWriter
from app.core.prompts import CODER_PROMPT
from app.utils.common_utils import get_current_files
import json
from app.core.prompts import get_reflection_prompt
from app.core.functions import coder_tools, coder_tools_anthropic
# TODO: 时间等待过久,stop 进程
# TODO: 支持 cuda
# TODO: 引入创新方案:
class CoderAgent(Agent):
"""代码手 Agent,通过 LLM 生成代码并在解释器中执行,支持错误反思和重试。"""
def __init__(
self,
task_id: str,
model: LLM,
work_dir: str, # 工作目录
max_chat_turns: int | None = settings.MAX_CHAT_TURNS, # 最大聊天次数,None表示无限制
max_retries: int | None = settings.MAX_RETRIES, # 最大反思次数,None表示无限制
code_interpreter: BaseCodeInterpreter | None = None,
context_window: int = 128000,
cancel_event: asyncio.Event | None = None,
) -> None:
super().__init__(task_id, model, context_window, cancel_event=cancel_event)
self.work_dir = work_dir
self.max_chat_turns = max_chat_turns
self.current_chat_turns = 0
self.max_retries = max_retries
self.is_first_run = True
self.system_prompt = CODER_PROMPT
self.code_interpreter = code_interpreter
async def run(self, prompt: str, subtask_title: str) -> CoderToWriter: # type: ignore[reportIncompatibleMethodOverride]
"""执行代码手子任务,生成并运行代码。
Args:
prompt: 子任务描述。
subtask_title: 子任务标题,用于分段输出。
Returns:
CoderToWriter 对象,包含代码执行结果和生成的图片列表。
"""
logger.info(f"{self.__class__.__name__}:开始:执行子任务: {subtask_title}")
assert self.code_interpreter is not None, "code_interpreter 未初始化"
self.code_interpreter.add_section(subtask_title)
# 根据 api_type 选择 tools 格式
api_type = self.model.api_type
tools = coder_tools_anthropic if api_type == ApiType.ANTHROPIC else coder_tools
# 如果是第一次运行,则添加系统提示
if self.is_first_run:
logger.info("首次运行,添加系统提示和数据集文件信息")
self.is_first_run = False
await self.append_chat_history(
{"role": "system", "content": self.system_prompt}
)
# 当前数据集文件
await self.append_chat_history(
{
"role": "user",
"content": f"当前文件夹下的数据集文件{get_current_files(self.work_dir, 'data')}",
}
)
# 添加 sub_task
logger.info(f"添加子任务提示: {prompt}")
await self.append_chat_history({"role": "user", "content": prompt})
retry_count = 0
last_error_message = ""
while True:
if self.max_retries is not None and retry_count >= self.max_retries:
logger.error(f"超过最大尝试次数: {self.max_retries}")
await redis_manager.publish_message(
self.task_id,
SystemMessage(content="超过最大尝试次数", type="error"),
)
logger.warning(f"任务失败,超过最大尝试次数{self.max_retries}, 最后错误信息: {last_error_message}")
return CoderToWriter(
code_response=f"任务失败,超过最大尝试次数{self.max_retries}, 最后错误信息: {last_error_message}",
created_images=[])
if self.max_chat_turns is not None and self.current_chat_turns >= self.max_chat_turns:
logger.error(f"超过最大聊天次数: {self.max_chat_turns}")
await redis_manager.publish_message(
self.task_id,
SystemMessage(content="超过最大聊天次数", type="error"),
)
raise Exception(
f"Reached maximum number of chat turns ({self.max_chat_turns}). Task incomplete."
)
self.current_chat_turns += 1
logger.info(f"当前对话轮次: {self.current_chat_turns}")
try:
response = await self._chat(
history=self.chat_history,
tools=tools,
tool_choice="auto",
agent_name=self.__class__.__name__,
)
# 如果有工具调用
if response.tool_calls:
logger.info("检测到工具调用")
tool_call = response.tool_calls[0]
tool_id = tool_call.id
if tool_call.name == "execute_code":
logger.info(f"调用工具: {tool_call.name}")
await redis_manager.publish_message(
self.task_id,
SystemMessage(
content=f"代码手调用{tool_call.name}工具"
),
)
code = json.loads(tool_call.arguments)["code"]
await redis_manager.publish_message(
self.task_id,
InterpreterMessage(
input={"code": code},
),
)
# 更新对话历史 - 添加助手的响应
assistant_msg: dict = {"role": "assistant", "content": response.content}
if response.reasoning_content:
assistant_msg["reasoning_content"] = response.reasoning_content
if response.tool_calls:
assistant_msg["tool_calls"] = [
{
"id": tc.id,
"type": "function",
"function": {"name": tc.name, "arguments": tc.arguments},
}
for tc in response.tool_calls
]
await self.append_chat_history(assistant_msg)
# 执行工具调用
logger.info("执行工具调用")
(
text_to_gpt,
error_occurred,
error_message,
) = await self.code_interpreter.execute_code(code)
# 添加工具执行结果
if error_occurred:
# 即使发生错误也要添加tool响应
await self.append_chat_history(
{
"role": "tool",
"tool_call_id": tool_id,
"name": "execute_code",
"content": error_message,
}
)
logger.warning(f"代码执行错误: {error_message}")
retry_count += 1
logger.info(f"当前尝试次:{retry_count} / {self.max_retries}")
last_error_message = error_message
reflection_prompt = get_reflection_prompt(error_message, code)
await redis_manager.publish_message(
self.task_id,
SystemMessage(content="代码手反思纠正错误", type="error"),
)
await self.append_chat_history(
{"role": "user", "content": reflection_prompt}
)
continue
else:
# 成功执行的tool响应
await self.append_chat_history(
{
"role": "tool",
"tool_call_id": tool_id,
"name": "execute_code",
"content": text_to_gpt,
}
)
# 成功执行后继续循环,等待下一步指令
continue
else:
# 没有工具调用,表示任务完成
logger.info("没有工具调用,任务完成")
return CoderToWriter(
code_response=response.content,
created_images=await self.code_interpreter.get_created_images(
subtask_title
),
)
except Exception as e:
logger.error(f"执行过程中发生异常: {str(e)}")
retry_count += 1
last_error_message = str(e)
continue
logger.info(f"{self.__class__.__name__}:完成:执行子任务: {subtask_title}")