--- name: cockpit-agent-dev description: "Cockpit agent v1 DSPY + v2 AgentExecutor patterns, pitfalls, tool-loop fixes." --- # Cockpit Agent 开发规范 ## 架构 - v1: `cockpit_chat.dspy` — 会话 agent,LLM tool-loop 架构(**已删除**,连同旧界面 sd_cockpit/index.ui) - v2: `cockpit_chat_v2.dspy` — AgentExecutor 驱动,pipeline-core/service v2 架构(当前唯一,前端 index.ui 的 AgentIO url 指向它) **只保留 v2**。改 cockpit 逻辑只改 `cockpit_chat_v2.dspy` + AgentExecutor,不要再去改已删除的 v1(cockpit_chat.dspy)。历史遗留过两个入口:`index.ui`(AgentIO→v2)与 `sd_cockpit/index.ui`(TextFiles→v1),后者已删。 - NDJSON 流式响应:每行 `{"widgettype":"...","options":{...}}\n` - 前端 AgentIO widget 通过 `HttpResponseStream.handle_chunk` 逐行解析 ## tool-loop 关键模式 ### 1. LLM 消息数组不污染 ```python # ❌ 错误:LLM 看到自己输出的 tool_call JSON,下轮模仿 msgs.append({"role":"assistant","content":raw}) # ✅ 正确:用清理后的文本 msgs.append({"role":"assistant","content":f"已调用 {tool}"}) ``` ### 2. 对话历史过滤 加载历史时跳过 `{"action":"tool_call"` 开头的消息,防止旧 tool_call 污染新对话。 ### 3. 项目上下文持久化 - `_save_ctx` / `_load_ctx` 使用 `pipeline_agent_settings` 表 - `iteration_id` 字段借用于存储 `project_id`(SDLC conversation 表中无 project_id) - `get_user()` 返回 None 时跳过所有 DB 持久化,仅内存中维持 ### 4. LLM 文本分类模式 当精确匹配失败时,用独立 LLM 调用做分类,不污染主 agent 上下文: ```python async def _call_llm_raw(prompt, temp=0.0): # 独立获取 model,独立 API 调用,30s 超时 ``` ### 5. reply 文本清理 `_parse` 中用正则剥离 `{"action":"tool_call",...}` JSON 片段后再返回给前端。 ## Widget 系统 ```python def _w_text(t): return {"widgettype":"Text","options":{"text":t,"css":"agent-text","halign":"left"}} def _w_card(title, body, kind): ... # VBox with border-left color def _w_progress(text): ... # orange progress text def _w_md(t): ... # MdWidget for markdown replies ``` ## v2 AgentExecutor 集成 cockpit 已支持 v2 执行引擎。端点:`cockpit_chat_v2.dspy` 切换方式:改 `index.ui` 中 AgentIO 的 `url` 指向 `cockpit_chat_v2.dspy` ```json {"url": "/pipeline-sdlc/api/cockpit_chat_v2.dspy"} ``` v2 使用 `pipeline-core.agent_config.AgentConfig` + `pipeline-service.agent_loop_v2.AgentExecutor`。 ## deepseek-v4-pro 工具调用问题 该模型不遵循 system prompt 中的「必须先调工具」指令,会直接 reply。 解决方案:代码级 auto-inject。 详见 `pipeline-agent-architecture` 技能 `references/v2-auto-inject.md` 关键要点: - 工具别名表:LLM 会编造 `get_tasks`、`get_task_detail` 等名称 - 最小化 prompt:删除冗长规则,工具列表放前面 - auto-inject 限制:最多推 3 次(`_auto_push_count`),防止死循环 - 失败调用不计数:`未知工具` 和 `ERROR` 结果不增加 `_tool_call_count` ## 用户意图识别(停止 / 补充 / 新任务) **不要粗暴 abort 旧请求。** 用户新消息可能是补充信息,应识别意图: - **停止类**(停止/取消/停/stop/cancel)→ 后端 `cockpit_chat_v2.dspy` 检测关键词,立即返回「已停止当前任务」 - **补充/新任务** → 作为新对话轮次处理,不打断旧请求 实现:`cockpit_chat_v2.dspy` 中 send_message 入口加 `stop_keywords` 检测,匹配则直接 yield MdWidget 返回。 ## Pipeline 浏览器登录 CDP 浏览器测试 pipeline 需先登录: 1. 导航到 `https://pipeline.opencomputing.cn/rbac/user/login.ui` 2. 填入 username/password,点击表单 Submit 按钮 3. 或用 `fetch('/rbac/user/up_login.dspy', {method:'POST', body:'username=admin&password=admin123&_webbricks_=1'})` POST 4. 登录成功后 session 生效,可导航到 `/pipeline-sdlc` 渲染 cockpit `password_encode()` 在 `ahserver/globalEnv.py`,用 RC4 + 配置 key。登录路径必须带 `/user/` 前缀。 **登录踩坑**:rbac 登录读的是 `pipeline.users`(`get_module_dbname('rbac')` 固定返回 `pipeline`),不是 `sage.users`——两个库各有独立 users 表,改错库密码不生效。测试无密码时可重置: ```bash mysql pipeline -e "UPDATE users SET password='' WHERE username='admin'" # password_encode 输出可用服务器 python 跑 globalEnv.password_encode('test123') 得到 ``` 前端登录按钮是 DIV(textContent=='Submit'),不是 `