feat: 版本流水线 + 算法组分组重组
tools/version_pipeline.py(新增): - 并行开发流水线:v1测试时v2已在开发,不等待 - v1测试问题 → v3 backlog(跳一版本,不阻塞v2) - [版本]/[测试通过]/[测试问题] 标签驱动流水线状态 - pipeline_summary() 注入 agent context,让 agent 知道当前版本状态 algo_vision.py(新增): - AlgoVisionAgent:视觉算法组基类,rk3566主验证+rk3588可扩展 - _update_version_pipeline():解析版本标签,自动触发下版本开发任务 - _build_context() 注入版本流水线状态 算法组分组: - 视觉组:algo-antishake → AlgoVisionAgent(Rockchip平台) - MCU组:algo-position/nav → AlgoMcuAgent(暂缓) scheduler:_enqueue_vision_algo_cycle() 只调度视觉组,MCU组注释保留 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ from nmfs_agents.agents.developer import AgentResult, DeveloperAgent
|
|||||||
from nmfs_agents.config import AgentsConfig
|
from nmfs_agents.config import AgentsConfig
|
||||||
from nmfs_agents.core.queue import Task, TaskQueue
|
from nmfs_agents.core.queue import Task, TaskQueue
|
||||||
from nmfs_agents.tools.project_memory import ProjectMemory
|
from nmfs_agents.tools.project_memory import ProjectMemory
|
||||||
|
from nmfs_agents.tools.version_pipeline import VersionPipeline
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -19,20 +20,30 @@ ALGO_VISION_SYSTEM = """你是视觉算法组工程师,专注瑞芯微(Rockc
|
|||||||
- 平台能力:RKNN Lite NPU(rk3566 ~1 TOPS)/ RKNN NPU(rk3588 ~6 TOPS)
|
- 平台能力:RKNN Lite NPU(rk3566 ~1 TOPS)/ RKNN NPU(rk3588 ~6 TOPS)
|
||||||
- 语言栈:Python 3.10 + numpy + opencv-python + rknn-lite
|
- 语言栈:Python 3.10 + numpy + opencv-python + rknn-lite
|
||||||
|
|
||||||
## 工作模式(每轮必须产出代码改进)
|
## 版本流水线规则(重要)
|
||||||
1. **读记忆** - ProjectMemory __hardware__.algo.{domain}.* 获取上轮实测 benchmark
|
- **并行开发**:v1 进入测试时立即开始 v2 开发,不等 v1 测试结果
|
||||||
2. **看代码** - 读 src/ 现有实现,找本轮最值得优化的瓶颈
|
- **问题跨版本**:v1 测试发现的问题 → 进入 v3 backlog(不 hotfix,不阻塞 v2)
|
||||||
3. **改代码** - 针对性改进(不推倒重来),优先 numpy 向量化/NPU 路径
|
- **每轮输出版本标记**:`[版本] vN: 描述本轮改动` 让系统记录版本号
|
||||||
4. **rk3566 验证** - SSH 到设备运行 tests/,取实测数字
|
- **测试结果标记**:`[测试通过] vN` 或 `[测试问题] vN: 问题描述`
|
||||||
5. **写记忆** - [记忆] 更新 benchmark 数据
|
|
||||||
6. **定方向** - [后续] 一句话指明下轮重点
|
|
||||||
|
|
||||||
## 算法优先级原则
|
## 工作模式(每轮必须产出代码改进)
|
||||||
实时性(延迟 ms) > 精度 > FPS > 内存占用
|
1. **读流水线** - 看上方版本状态,确认本轮做哪个版本
|
||||||
|
2. **读记忆** - ProjectMemory __hardware__.algo.{domain}.* 获取上轮实测数据
|
||||||
|
3. **读 backlog** - 本轮应解决哪些历史遗留问题
|
||||||
|
4. **改代码** - 针对性改进,优先 numpy 向量化/NPU 路径
|
||||||
|
5. **rk3566 验证** - SSH 运行 tests/ 获取实测数字
|
||||||
|
6. **标记版本** - [版本] + [测试通过]/[测试问题]
|
||||||
|
7. **写记忆** - [记忆] 更新 benchmark
|
||||||
|
8. **定方向** - [后续] 下轮改进点
|
||||||
|
|
||||||
|
## 算法优先级
|
||||||
|
实时性(延迟 ms) > 精度 > FPS > 内存
|
||||||
|
|
||||||
## 输出格式(必须包含)
|
## 输出格式(必须包含)
|
||||||
|
- [版本] vN: <本轮改动摘要>
|
||||||
|
- [测试通过] vN 或 [测试问题] vN: <问题描述>
|
||||||
- [记忆] __hardware__: algo.{domain}.{metric}=值
|
- [记忆] __hardware__: algo.{domain}.{metric}=值
|
||||||
- [后续] <下一轮具体改进点>
|
- [后续] <下一轮聚焦点>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -44,14 +55,17 @@ class AlgoVisionAgent(DeveloperAgent):
|
|||||||
config: AgentsConfig,
|
config: AgentsConfig,
|
||||||
queue: Optional[TaskQueue] = None,
|
queue: Optional[TaskQueue] = None,
|
||||||
memory: Optional[ProjectMemory] = None,
|
memory: Optional[ProjectMemory] = None,
|
||||||
|
version_pipeline: Optional[VersionPipeline] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(config, memory=memory)
|
super().__init__(config, memory=memory)
|
||||||
self._queue = queue or TaskQueue()
|
self._queue = queue or TaskQueue()
|
||||||
|
self._vp = version_pipeline or VersionPipeline()
|
||||||
|
|
||||||
def run(self, task: Task) -> AgentResult:
|
def run(self, task: Task) -> AgentResult:
|
||||||
result = super().run(task)
|
result = super().run(task)
|
||||||
if result.status == "done":
|
if result.status == "done":
|
||||||
self._extract_vision_facts(result.summary, task)
|
self._extract_vision_facts(result.summary, task)
|
||||||
|
self._update_version_pipeline(result.summary, task)
|
||||||
self._dispatch_followup(result.summary, task)
|
self._dispatch_followup(result.summary, task)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -87,7 +101,9 @@ class AlgoVisionAgent(DeveloperAgent):
|
|||||||
if algo_facts:
|
if algo_facts:
|
||||||
lines = "\n".join(f" {k}: {v}" for k, v in algo_facts.items())
|
lines = "\n".join(f" {k}: {v}" for k, v in algo_facts.items())
|
||||||
hw_section = f"\n【上轮 Benchmark(来自记忆)】\n{lines}\n"
|
hw_section = f"\n【上轮 Benchmark(来自记忆)】\n{lines}\n"
|
||||||
return base + hw_section + self._build_device_context() + "\n" + ALGO_VISION_SYSTEM
|
# 注入版本流水线状态
|
||||||
|
pipeline_section = "\n" + self._vp.pipeline_summary(task.project) + "\n"
|
||||||
|
return base + hw_section + pipeline_section + self._build_device_context() + "\n" + ALGO_VISION_SYSTEM
|
||||||
|
|
||||||
def _extract_vision_facts(self, report_text: str, task: Task) -> None:
|
def _extract_vision_facts(self, report_text: str, task: Task) -> None:
|
||||||
"""解析 [记忆] 标签,写入 ProjectMemory __hardware__ facts。"""
|
"""解析 [记忆] 标签,写入 ProjectMemory __hardware__ facts。"""
|
||||||
@@ -101,6 +117,45 @@ class AlgoVisionAgent(DeveloperAgent):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("视觉记忆写入失败 %s.%s: %s", project_key, sub_key, e)
|
logger.warning("视觉记忆写入失败 %s.%s: %s", project_key, sub_key, e)
|
||||||
|
|
||||||
|
def _update_version_pipeline(self, report_text: str, task: Task) -> None:
|
||||||
|
"""解析版本标签,更新流水线状态并在需要时触发下版本开发任务。"""
|
||||||
|
project = task.project
|
||||||
|
|
||||||
|
# [版本] vN: 描述 → 登记新版本
|
||||||
|
for m in re.finditer(r"\[版本\]\s*(v\d+)\s*[::]\s*(.+)", report_text):
|
||||||
|
version, desc = m.group(1), m.group(2).strip()[:200]
|
||||||
|
self._vp.start_version(project, version, desc)
|
||||||
|
# 版本登记后立即进入测试态
|
||||||
|
self._vp.start_testing(project, version)
|
||||||
|
|
||||||
|
# [测试通过] vN → 标记完成,触发下一版本开发
|
||||||
|
for m in re.finditer(r"\[测试通过\]\s*(v\d+)", report_text):
|
||||||
|
version = m.group(1)
|
||||||
|
self._vp.mark_done(project, version)
|
||||||
|
# 自动启动下下个版本(跳过已在开发中的)
|
||||||
|
next_v = self._vp.get_next_version_name(project)
|
||||||
|
backlog = self._vp.get_next_backlog(project)
|
||||||
|
backlog_ctx = "\n".join(f"- {b}" for b in backlog[:5]) if backlog else "无历史遗留问题"
|
||||||
|
self._queue.enqueue(Task(
|
||||||
|
project=project, type="code_improve",
|
||||||
|
title=f"视觉算法 {next_v} 开发",
|
||||||
|
context=(
|
||||||
|
f"【并行流水线:{version} 已完成,开始 {next_v} 开发】\n"
|
||||||
|
f"本轮 backlog(来自历史测试问题):\n{backlog_ctx}\n"
|
||||||
|
f"在此基础上继续迭代改进。"
|
||||||
|
),
|
||||||
|
agent_role=task.agent_role,
|
||||||
|
priority=task.priority, mode=task.mode,
|
||||||
|
parent_task_id=task.id, initiator=task.agent_role,
|
||||||
|
))
|
||||||
|
logger.info("[%s] %s 完成,已触发 %s 开发", project, version, next_v)
|
||||||
|
|
||||||
|
# [测试问题] vN: 问题描述 → 记录到 backlog(进入 v+2)
|
||||||
|
for m in re.finditer(r"\[测试问题\]\s*(v\d+)\s*[::]\s*(.+)", report_text):
|
||||||
|
version, issue = m.group(1), m.group(2).strip()[:200]
|
||||||
|
self._vp.add_test_issue(project, version, issue)
|
||||||
|
logger.info("[%s] %s 问题记录: %s", project, version, issue)
|
||||||
|
|
||||||
def _dispatch_followup(self, report_text: str, task: Task) -> None:
|
def _dispatch_followup(self, report_text: str, task: Task) -> None:
|
||||||
"""解析 [后续] 标签,入队下轮迭代任务。"""
|
"""解析 [后续] 标签,入队下轮迭代任务。"""
|
||||||
for m in re.finditer(r"\[后续\]\s*(.+)", report_text):
|
for m in re.finditer(r"\[后续\]\s*(.+)", report_text):
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
"""version_pipeline.py — 并行版本开发流水线管理。
|
||||||
|
|
||||||
|
流水线模式:
|
||||||
|
v1 开发完毕 → v1 测试(入队 tester)
|
||||||
|
v2 立即开始开发(不等 v1 测试完)
|
||||||
|
v1 测试发现问题 → 问题进入 v3 backlog(不 hotfix v1)
|
||||||
|
v2 测试发现问题 → 进入 v4 backlog
|
||||||
|
...
|
||||||
|
|
||||||
|
每个项目独立维护自己的版本流水线状态(SQLite)。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import sqlite3
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_DEFAULT_DB = Path("data/version_pipeline.db")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VersionRecord:
|
||||||
|
id: int
|
||||||
|
project: str
|
||||||
|
version: str # v1, v2, v3, ...
|
||||||
|
description: str
|
||||||
|
status: str # developing | testing | done | abandoned
|
||||||
|
created_at: str
|
||||||
|
tested_at: Optional[str]
|
||||||
|
done_at: Optional[str]
|
||||||
|
issues: str # JSON list of issue descriptions found during testing
|
||||||
|
snapshot_path: str # optional: path to code snapshot
|
||||||
|
|
||||||
|
|
||||||
|
class VersionPipeline:
|
||||||
|
"""项目并行版本流水线管理器。
|
||||||
|
|
||||||
|
Usage (in agent context):
|
||||||
|
vp = VersionPipeline()
|
||||||
|
# 完成 v1 开发后
|
||||||
|
vid = vp.start_testing("antishake", "v1", "EIS 基础光流实现")
|
||||||
|
# 立即开始 v2 开发
|
||||||
|
v2 = vp.start_next_dev("antishake", "v2", "添加卡尔曼平滑")
|
||||||
|
# v1 测试发现问题 → 进入 v3 backlog(不是 v2)
|
||||||
|
vp.add_test_issue("antishake", "v1", "光流在暗场景下漂移严重")
|
||||||
|
# v1 测试通过
|
||||||
|
vp.mark_done("antishake", "v1")
|
||||||
|
# 获取 v3 需要处理的问题列表
|
||||||
|
issues = vp.get_next_backlog("antishake")
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_path: Path = _DEFAULT_DB) -> None:
|
||||||
|
self._db_path = db_path
|
||||||
|
self._db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
self._init_db()
|
||||||
|
|
||||||
|
def _init_db(self) -> None:
|
||||||
|
with self._connect() as conn:
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS versions (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
project TEXT NOT NULL,
|
||||||
|
version TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL DEFAULT '',
|
||||||
|
status TEXT NOT NULL DEFAULT 'developing',
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
tested_at TEXT,
|
||||||
|
done_at TEXT,
|
||||||
|
issues TEXT NOT NULL DEFAULT '[]',
|
||||||
|
snapshot_path TEXT NOT NULL DEFAULT '',
|
||||||
|
UNIQUE(project, version)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
def _connect(self) -> sqlite3.Connection:
|
||||||
|
conn = sqlite3.connect(str(self._db_path))
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def _now(self) -> str:
|
||||||
|
return datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||||
|
|
||||||
|
# ─── 版本生命周期 ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
def start_version(self, project: str, version: str, description: str) -> int:
|
||||||
|
"""登记新版本开始开发,返回 id。"""
|
||||||
|
with self._connect() as conn:
|
||||||
|
try:
|
||||||
|
cur = conn.execute(
|
||||||
|
"INSERT INTO versions(project,version,description,status,created_at) "
|
||||||
|
"VALUES(?,?,?,'developing',?)",
|
||||||
|
(project, version, description, self._now()),
|
||||||
|
)
|
||||||
|
vid = cur.lastrowid
|
||||||
|
logger.info("[%s] %s 开始开发: %s", project, version, description)
|
||||||
|
return vid
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
logger.warning("[%s] %s 已存在", project, version)
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def start_testing(self, project: str, version: str) -> bool:
|
||||||
|
"""将版本状态置为 testing,同时应立即启动下一版本开发。"""
|
||||||
|
with self._connect() as conn:
|
||||||
|
cur = conn.execute(
|
||||||
|
"UPDATE versions SET status='testing', tested_at=? "
|
||||||
|
"WHERE project=? AND version=? AND status='developing'",
|
||||||
|
(self._now(), project, version),
|
||||||
|
)
|
||||||
|
ok = cur.rowcount > 0
|
||||||
|
if ok:
|
||||||
|
logger.info("[%s] %s 进入测试阶段", project, version)
|
||||||
|
return ok
|
||||||
|
|
||||||
|
def add_test_issue(self, project: str, version: str, issue: str) -> None:
|
||||||
|
"""记录测试中发现的问题(将进入 version+2 的 backlog)。"""
|
||||||
|
with self._connect() as conn:
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT issues FROM versions WHERE project=? AND version=?",
|
||||||
|
(project, version),
|
||||||
|
).fetchone()
|
||||||
|
if not row:
|
||||||
|
return
|
||||||
|
issues = json.loads(row["issues"])
|
||||||
|
issues.append({"issue": issue, "found_at": self._now()})
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE versions SET issues=? WHERE project=? AND version=?",
|
||||||
|
(json.dumps(issues, ensure_ascii=False), project, version),
|
||||||
|
)
|
||||||
|
logger.info("[%s] %s 发现问题: %s", project, version, issue)
|
||||||
|
|
||||||
|
def mark_done(self, project: str, version: str) -> None:
|
||||||
|
"""版本测试通过,标记为 done。"""
|
||||||
|
with self._connect() as conn:
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE versions SET status='done', done_at=? WHERE project=? AND version=?",
|
||||||
|
(self._now(), project, version),
|
||||||
|
)
|
||||||
|
logger.info("[%s] %s 测试通过,已完成", project, version)
|
||||||
|
|
||||||
|
# ─── 查询 ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def get_status(self, project: str) -> list[dict]:
|
||||||
|
"""返回项目所有版本状态。"""
|
||||||
|
with self._connect() as conn:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT version,description,status,created_at,tested_at,done_at,issues "
|
||||||
|
"FROM versions WHERE project=? ORDER BY id",
|
||||||
|
(project,),
|
||||||
|
).fetchall()
|
||||||
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
|
def get_current_dev_version(self, project: str) -> Optional[str]:
|
||||||
|
"""返回当前正在开发的版本号(最新的 developing 状态)。"""
|
||||||
|
with self._connect() as conn:
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT version FROM versions WHERE project=? AND status='developing' "
|
||||||
|
"ORDER BY id DESC LIMIT 1",
|
||||||
|
(project,),
|
||||||
|
).fetchone()
|
||||||
|
return row["version"] if row else None
|
||||||
|
|
||||||
|
def get_testing_version(self, project: str) -> Optional[str]:
|
||||||
|
"""返回当前正在测试的版本号。"""
|
||||||
|
with self._connect() as conn:
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT version FROM versions WHERE project=? AND status='testing' "
|
||||||
|
"ORDER BY id DESC LIMIT 1",
|
||||||
|
(project,),
|
||||||
|
).fetchone()
|
||||||
|
return row["version"] if row else None
|
||||||
|
|
||||||
|
def get_next_backlog(self, project: str) -> list[str]:
|
||||||
|
"""收集所有已完成版本的测试问题,作为下一轮开发的 backlog。
|
||||||
|
|
||||||
|
规则:v1 发现的问题 → v3 backlog(跳一个版本),
|
||||||
|
这样 v2 已在开发中的工作不被打断。
|
||||||
|
"""
|
||||||
|
with self._connect() as conn:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT version, issues FROM versions "
|
||||||
|
"WHERE project=? AND (status='done' OR status='testing') AND issues != '[]' "
|
||||||
|
"ORDER BY id",
|
||||||
|
(project,),
|
||||||
|
).fetchall()
|
||||||
|
backlog: list[str] = []
|
||||||
|
for row in rows:
|
||||||
|
issues = json.loads(row["issues"])
|
||||||
|
for item in issues:
|
||||||
|
backlog.append(f"[来自{row['version']}测试] {item['issue']}")
|
||||||
|
return backlog
|
||||||
|
|
||||||
|
def get_next_version_name(self, project: str) -> str:
|
||||||
|
"""自动推算下一个版本号(v1→v2→v3...)。"""
|
||||||
|
with self._connect() as conn:
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT version FROM versions WHERE project=? ORDER BY id DESC LIMIT 1",
|
||||||
|
(project,),
|
||||||
|
).fetchone()
|
||||||
|
if not row:
|
||||||
|
return "v1"
|
||||||
|
last = row["version"] # e.g. "v3"
|
||||||
|
try:
|
||||||
|
num = int(last.lstrip("v")) + 1
|
||||||
|
except ValueError:
|
||||||
|
num = 1
|
||||||
|
return f"v{num}"
|
||||||
|
|
||||||
|
def pipeline_summary(self, project: str) -> str:
|
||||||
|
"""返回流水线状态的人类可读摘要,用于注入 agent context。"""
|
||||||
|
status = self.get_status(project)
|
||||||
|
if not status:
|
||||||
|
return f"[{project}] 版本流水线:尚无版本记录,请从 v1 开始。"
|
||||||
|
|
||||||
|
lines = [f"[{project}] 版本流水线状态:"]
|
||||||
|
for v in status:
|
||||||
|
issues = json.loads(v["issues"])
|
||||||
|
issue_str = f",{len(issues)}个问题待处理" if issues else ""
|
||||||
|
lines.append(f" {v['version']}: {v['status']}{issue_str} — {v['description']}")
|
||||||
|
|
||||||
|
backlog = self.get_next_backlog(project)
|
||||||
|
if backlog:
|
||||||
|
lines.append(f"\n下一版本 backlog({len(backlog)}条):")
|
||||||
|
for b in backlog[:5]:
|
||||||
|
lines.append(f" - {b}")
|
||||||
|
|
||||||
|
next_v = self.get_next_version_name(project)
|
||||||
|
lines.append(f"\n下一个版本号:{next_v}")
|
||||||
|
return "\n".join(lines)
|
||||||
Reference in New Issue
Block a user