From ff02533cb647dae971e7964e9b030c44eb1ebebe Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sun, 16 Aug 2026 21:25:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(team-communication):=20=E5=9B=A2=E9=98=9F?= =?UTF-8?q?=E6=B2=9F=E9=80=9A=E9=85=8D=E7=BD=AE=20=E2=80=94=20=E5=9B=A2?= =?UTF-8?q?=E9=98=9F=E8=A7=92=E8=89=B2(+agentid)=20+=20=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B(=E5=86=92=E6=B3=A1=E8=B7=AF=E5=BE=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增通用问题冒泡的数据模型与配置 CRUD: - models/json: pipeline_team_roles(角色+agentid,多agent区分) + pipeline_problem_types(escalation_path) - pipeline_core/init.py: _simple_crud 通用 CRUD + 6 个 create/update/delete 注册 - wwwroot/api: 6 个 dspy 端点 - scripts/load_path.py: RBAC 路径 - skills_library/all/team-communication/SKILL.md: 团队沟通技能 --- json/pipeline_problem_types.json | 27 +++++ json/pipeline_team_roles.json | 27 +++++ models/pipeline_problem_types.json | 87 ++++++++++++++ models/pipeline_team_roles.json | 106 ++++++++++++++++++ pipeline_core/init.py | 74 ++++++++++++ scripts/load_path.py | 17 +++ .../all/team-communication/SKILL.md | 66 +++++++++++ .../api/pipeline_problem_types_create.dspy | 5 + .../api/pipeline_problem_types_delete.dspy | 5 + .../api/pipeline_problem_types_update.dspy | 5 + wwwroot/api/pipeline_team_roles_create.dspy | 5 + wwwroot/api/pipeline_team_roles_delete.dspy | 5 + wwwroot/api/pipeline_team_roles_update.dspy | 5 + 13 files changed, 434 insertions(+) create mode 100644 json/pipeline_problem_types.json create mode 100644 json/pipeline_team_roles.json create mode 100644 models/pipeline_problem_types.json create mode 100644 models/pipeline_team_roles.json create mode 100644 skills_library/all/team-communication/SKILL.md create mode 100644 wwwroot/api/pipeline_problem_types_create.dspy create mode 100644 wwwroot/api/pipeline_problem_types_delete.dspy create mode 100644 wwwroot/api/pipeline_problem_types_update.dspy create mode 100644 wwwroot/api/pipeline_team_roles_create.dspy create mode 100644 wwwroot/api/pipeline_team_roles_delete.dspy create mode 100644 wwwroot/api/pipeline_team_roles_update.dspy diff --git a/json/pipeline_problem_types.json b/json/pipeline_problem_types.json new file mode 100644 index 0000000..75041f1 --- /dev/null +++ b/json/pipeline_problem_types.json @@ -0,0 +1,27 @@ +{ + "tblname": "pipeline_problem_types", + "title": "问题类型", + "params": { + "sortby": "name", + "browserfields": { + "exclouded": ["id", "description", "escalation_path"], + "cwidth": {} + }, + "editexclouded": [ + "id", "created_at" + ], + "editable": { + "new_data_url": "{{entire_url('../api/pipeline_problem_types_create.dspy')}}", + "update_data_url": "{{entire_url('../api/pipeline_problem_types_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/pipeline_problem_types_delete.dspy')}}" + }, + "confidential_fields": [], + "subtables": [ + { + "field": "pipeline_id", + "title": "所属产线", + "subtable": "pipelines" + } + ] + } +} diff --git a/json/pipeline_team_roles.json b/json/pipeline_team_roles.json new file mode 100644 index 0000000..18b3de9 --- /dev/null +++ b/json/pipeline_team_roles.json @@ -0,0 +1,27 @@ +{ + "tblname": "pipeline_team_roles", + "title": "团队角色", + "params": { + "sortby": "role_name", + "browserfields": { + "exclouded": ["id", "description"], + "cwidth": {} + }, + "editexclouded": [ + "id", "created_at" + ], + "editable": { + "new_data_url": "{{entire_url('../api/pipeline_team_roles_create.dspy')}}", + "update_data_url": "{{entire_url('../api/pipeline_team_roles_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/pipeline_team_roles_delete.dspy')}}" + }, + "confidential_fields": [], + "subtables": [ + { + "field": "pipeline_id", + "title": "所属产线", + "subtable": "pipelines" + } + ] + } +} diff --git a/models/pipeline_problem_types.json b/models/pipeline_problem_types.json new file mode 100644 index 0000000..a4e30f3 --- /dev/null +++ b/models/pipeline_problem_types.json @@ -0,0 +1,87 @@ +{ + "summary": [ + { + "name": "pipeline_problem_types", + "title": "问题类型表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no", + "uitype": "hidden" + }, + { + "name": "pipeline_id", + "title": "所属产线", + "type": "str", + "length": 32, + "nullable": "no", + "uitype": "select" + }, + { + "name": "name", + "title": "问题类型标识", + "type": "str", + "length": 50, + "nullable": "no", + "uitype": "text" + }, + { + "name": "title", + "title": "显示名", + "type": "str", + "length": 100, + "uitype": "text" + }, + { + "name": "description", + "title": "说明", + "type": "text", + "uitype": "textarea" + }, + { + "name": "escalation_path", + "title": "冒泡路径(JSON角色数组,如[\"main_agent\",\"customer\"])", + "type": "text", + "uitype": "textarea" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "uitype": "timestamp" + } + ], + "indexes": [ + { + "name": "idx_problemtype_pipeline", + "idxtype": "index", + "idxfields": [ + "pipeline_id" + ] + }, + { + "name": "idx_problemtype_name", + "idxtype": "unique", + "idxfields": [ + "pipeline_id", + "name" + ] + } + ], + "codes": [ + { + "field": "pipeline_id", + "table": "pipelines", + "valuefield": "id", + "textfield": "name" + } + ] +} diff --git a/models/pipeline_team_roles.json b/models/pipeline_team_roles.json new file mode 100644 index 0000000..fdcc020 --- /dev/null +++ b/models/pipeline_team_roles.json @@ -0,0 +1,106 @@ +{ + "summary": [ + { + "name": "pipeline_team_roles", + "title": "团队角色表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no", + "uitype": "hidden" + }, + { + "name": "pipeline_id", + "title": "所属产线", + "type": "str", + "length": 32, + "nullable": "no", + "uitype": "select" + }, + { + "name": "role_name", + "title": "角色标识", + "type": "str", + "length": 50, + "nullable": "no", + "uitype": "text" + }, + { + "name": "agentid", + "title": "Agent标识(同角色多agent区分,人可空)", + "type": "str", + "length": 64, + "nullable": "yes", + "uitype": "text" + }, + { + "name": "role_type", + "title": "角色类型", + "type": "str", + "length": 20, + "nullable": "no", + "default": "agent", + "uitype": "select" + }, + { + "name": "display_name", + "title": "显示名", + "type": "str", + "length": 100, + "uitype": "text" + }, + { + "name": "description", + "title": "职责说明", + "type": "text", + "uitype": "textarea" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "uitype": "timestamp" + } + ], + "indexes": [ + { + "name": "idx_teamrole_pipeline", + "idxtype": "index", + "idxfields": [ + "pipeline_id" + ] + }, + { + "name": "idx_teamrole_name", + "idxtype": "unique", + "idxfields": [ + "pipeline_id", + "role_name", + "agentid" + ] + } + ], + "codes": [ + { + "field": "pipeline_id", + "table": "pipelines", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "role_type", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='team_role_type'" + } + ] +} diff --git a/pipeline_core/init.py b/pipeline_core/init.py index 74c9550..ac39972 100644 --- a/pipeline_core/init.py +++ b/pipeline_core/init.py @@ -297,6 +297,67 @@ async def delete_llm(params_kw): return json.dumps(result, ensure_ascii=False, default=str) +async def _simple_crud(table, op, params_kw): + """团队沟通配置表通用 CRUD(table: 表名, op: create/update/delete)。""" + result = {'success': False, 'message': ''} + try: + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + data = params_kw.copy() + data.pop('page', None) + data.pop('rows', None) + data.pop('data_filter', None) + if op == 'create': + data['id'] = getID() + await sor.C(table, data) + result['id'] = data['id'] + result['success'] = True + result['message'] = '创建成功' + elif op == 'update': + rid = data.pop('id', None) + if not rid: + result['message'] = '缺少id' + else: + await sor.U(table, {**data, 'id': rid}) + result['success'] = True + result['message'] = '更新成功' + else: + rid = params_kw.get('id') + if not rid: + result['message'] = '缺少id' + else: + await sor.D(table, {'id': rid}) + result['success'] = True + result['message'] = '删除成功' + except Exception as e: + result['message'] = str(e) + return json.dumps(result, ensure_ascii=False, default=str) + + +async def create_pipeline_team_role(params_kw): + return await _simple_crud('pipeline_team_roles', 'create', params_kw) + + +async def update_pipeline_team_role(params_kw): + return await _simple_crud('pipeline_team_roles', 'update', params_kw) + + +async def delete_pipeline_team_role(params_kw): + return await _simple_crud('pipeline_team_roles', 'delete', params_kw) + + +async def create_pipeline_problem_type(params_kw): + return await _simple_crud('pipeline_problem_types', 'create', params_kw) + + +async def update_pipeline_problem_type(params_kw): + return await _simple_crud('pipeline_problem_types', 'update', params_kw) + + +async def delete_pipeline_problem_type(params_kw): + return await _simple_crud('pipeline_problem_types', 'delete', params_kw) + + def load_pipeline_core(): """注册函数到 ServerEnv""" env = ServerEnv() @@ -325,6 +386,19 @@ def load_pipeline_core(): env.update_pipeline_versions = update_pipeline_version env.delete_pipeline_version = delete_pipeline_version env.delete_pipeline_versions = delete_pipeline_version + # Team communication (团队沟通:团队角色 + 问题类型) + env.create_pipeline_team_role = create_pipeline_team_role + env.create_pipeline_team_roles = create_pipeline_team_role + env.update_pipeline_team_role = update_pipeline_team_role + env.update_pipeline_team_roles = update_pipeline_team_role + env.delete_pipeline_team_role = delete_pipeline_team_role + env.delete_pipeline_team_roles = delete_pipeline_team_role + env.create_pipeline_problem_type = create_pipeline_problem_type + env.create_pipeline_problem_types = create_pipeline_problem_type + env.update_pipeline_problem_type = update_pipeline_problem_type + env.update_pipeline_problem_types = update_pipeline_problem_type + env.delete_pipeline_problem_type = delete_pipeline_problem_type + env.delete_pipeline_problem_types = delete_pipeline_problem_type # Publish env.publish_pipeline = publish_pipeline debug(f'[{MODULE_NAME}] module loaded') diff --git a/scripts/load_path.py b/scripts/load_path.py index d2c4241..9538a6e 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -45,6 +45,23 @@ PATHS_LOGINED = [ f"/{MOD}/api/pipelines_create.dspy", f"/{MOD}/api/pipelines_update.dspy", f"/{MOD}/api/pipelines_delete.dspy", + # Team communication (团队沟通:团队角色 + 问题类型 CRUD) + f"/{MOD}/pipeline_team_roles/index.ui", + f"/{MOD}/pipeline_team_roles/get_pipeline_team_roles.dspy", + f"/{MOD}/pipeline_team_roles/add_pipeline_team_roles.dspy", + f"/{MOD}/pipeline_team_roles/update_pipeline_team_roles.dspy", + f"/{MOD}/pipeline_team_roles/delete_pipeline_team_roles.dspy", + f"/{MOD}/pipeline_problem_types/index.ui", + f"/{MOD}/pipeline_problem_types/get_pipeline_problem_types.dspy", + f"/{MOD}/pipeline_problem_types/add_pipeline_problem_types.dspy", + f"/{MOD}/pipeline_problem_types/update_pipeline_problem_types.dspy", + f"/{MOD}/pipeline_problem_types/delete_pipeline_problem_types.dspy", + f"/{MOD}/api/pipeline_team_roles_create.dspy", + f"/{MOD}/api/pipeline_team_roles_update.dspy", + f"/{MOD}/api/pipeline_team_roles_delete.dspy", + f"/{MOD}/api/pipeline_problem_types_create.dspy", + f"/{MOD}/api/pipeline_problem_types_update.dspy", + f"/{MOD}/api/pipeline_problem_types_delete.dspy", ] diff --git a/skills_library/all/team-communication/SKILL.md b/skills_library/all/team-communication/SKILL.md new file mode 100644 index 0000000..f5e7a3e --- /dev/null +++ b/skills_library/all/team-communication/SKILL.md @@ -0,0 +1,66 @@ +--- +name: team-communication +description: 团队沟通(通用问题冒泡)——问题按类型沿冒泡路径在团队角色间流转,解决即停、未解决继续冒泡、尽头由人兜底。触发:agent 发现问题、角色提问、PM 退回、故障上报、客户提问等。 +--- + +# 团队沟通(通用问题冒泡) + +## 核心概念 + +- **团队** = 一组不同角色的 agent + 人(`pipeline_team_roles` 表,每个成员 = role_name + agentid)。 + - 多 agent 存在:同一角色可有多个 agent,必须用 `agentid` 区分到具体处理方。 + - `role_type`:`agent`(AI agent)/ `human`(人,如客户)。 +- **问题类型**(`pipeline_problem_types`)= 一条冒泡路径 `escalation_path`(处理方序列)。 +- **冒泡路径条目**:`"role_name"`(该角色任意 agent/人)或 `{"role":"x","agentid":"y"}`(指定 agent)。 + +## 冒泡规则(核心) + +``` +提出问题 raise → 沿路径第一个处理方开始 + ↓ +处理方判断: + 解决了 → resolve(status=answered,停止冒泡,任务恢复) + 没解决 → escalate(escalation_pos+1,沿路径继续走到下一个处理方) + ↓ +路径尽头 = 最后一个处理方(通常是"人" customer,兜底) +``` + +## 工具 + +| 工具 | 作用 | +|---|---| +| `list_questions` | 列出「当前该我(主agent)处理」的问题(不混入退回给角色 agent 的) | +| `answer_question` | 回答/解决 → 停止冒泡,任务恢复 | +| `escalate_question` | 答不了 → 沿冒泡路径转下一个处理方(通常转客户) | +| `diagnose_project` | 「待回答问题」计数 = 当前该主 agent 处理的问题数 | + +后端引擎(`pipeline_service/communication.py`):`raise_problem` / `resolve_problem` / `escalate_problem` / `list_problems_for`。 + +## 处理流程(发现「待回答问题」> 0 时必须执行) + +1. `list_questions` 列出所有「待我处理」的 pending 问题,不要只报统计数字。 +2. 对每个问题: + - 你能解决 → `answer_question` 直接回答(任务自动恢复,冒泡停止)。 + - 你答不了 → `escalate_question` 沿冒泡路径转下一个处理方(客户),等对方回答后再闭环。 + +## SDLC 产线默认问题类型(sdlc_general) + +| 问题类型 | 冒泡路径 | 说明 | +|---|---|---| +| `need_info` | `[main_agent, customer]` | 角色 agent 缺信息 → 主 agent 答 / 转客户 | +| `review_reject` | `[被退角色, pm]` | PM 退回 → 被退角色响应 / 兜回 pm | +| `fault_report` | `[main_agent, customer]` | 任务失败报障 → 主 agent / 客户人工介入 | + +## 多 agent 路由规则 + +- 处理方 `agentid` 为空 = 该角色任意 agent 可处理。 +- 处理方 `agentid` 指定 = 只有该 agent 能处理(同角色其他 agent 看不到)。 +- 角色 agent 重新认领任务时,只注入「本角色/本 agent」应响应的 pending 退回意见(`get_task_qa(include_pending_for=role, include_pending_agentid=agentid)`)。 + +## 相关文件 + +- `pipeline_service/communication.py` — 通用冒泡引擎(raise/resolve/escalate/list_for) +- `pipeline_service/questions.py` — 向后兼容层(agent_ask/answer_question/forward_question 委托 communication) +- `pipeline_service/agent_loop.py` — 角色提问(need_info)、PM 退回(review_reject)、故障(fault_report)、PM 审核通过答结 +- `pipeline_service/sdlc_ability.py` — cockpit 工具 list_questions/answer_question/escalate_question +- `pipeline-core/models/pipeline_team_roles.json` + `pipeline_problem_types.json` — 团队角色 + 问题类型定义 diff --git a/wwwroot/api/pipeline_problem_types_create.dspy b/wwwroot/api/pipeline_problem_types_create.dspy new file mode 100644 index 0000000..b8ef0f7 --- /dev/null +++ b/wwwroot/api/pipeline_problem_types_create.dspy @@ -0,0 +1,5 @@ +func = create_pipeline_problem_type +if func is None: + return json.dumps({'status': 'error', 'message': 'function not found'}) +result = await func(params_kw) +return result diff --git a/wwwroot/api/pipeline_problem_types_delete.dspy b/wwwroot/api/pipeline_problem_types_delete.dspy new file mode 100644 index 0000000..d61c204 --- /dev/null +++ b/wwwroot/api/pipeline_problem_types_delete.dspy @@ -0,0 +1,5 @@ +func = delete_pipeline_problem_type +if func is None: + return json.dumps({'status': 'error', 'message': 'function not found'}) +result = await func(params_kw) +return result diff --git a/wwwroot/api/pipeline_problem_types_update.dspy b/wwwroot/api/pipeline_problem_types_update.dspy new file mode 100644 index 0000000..8235682 --- /dev/null +++ b/wwwroot/api/pipeline_problem_types_update.dspy @@ -0,0 +1,5 @@ +func = update_pipeline_problem_type +if func is None: + return json.dumps({'status': 'error', 'message': 'function not found'}) +result = await func(params_kw) +return result diff --git a/wwwroot/api/pipeline_team_roles_create.dspy b/wwwroot/api/pipeline_team_roles_create.dspy new file mode 100644 index 0000000..2d63b9c --- /dev/null +++ b/wwwroot/api/pipeline_team_roles_create.dspy @@ -0,0 +1,5 @@ +func = create_pipeline_team_role +if func is None: + return json.dumps({'status': 'error', 'message': 'function not found'}) +result = await func(params_kw) +return result diff --git a/wwwroot/api/pipeline_team_roles_delete.dspy b/wwwroot/api/pipeline_team_roles_delete.dspy new file mode 100644 index 0000000..8d8f5db --- /dev/null +++ b/wwwroot/api/pipeline_team_roles_delete.dspy @@ -0,0 +1,5 @@ +func = delete_pipeline_team_role +if func is None: + return json.dumps({'status': 'error', 'message': 'function not found'}) +result = await func(params_kw) +return result diff --git a/wwwroot/api/pipeline_team_roles_update.dspy b/wwwroot/api/pipeline_team_roles_update.dspy new file mode 100644 index 0000000..01192b8 --- /dev/null +++ b/wwwroot/api/pipeline_team_roles_update.dspy @@ -0,0 +1,5 @@ +func = update_pipeline_team_role +if func is None: + return json.dumps({'status': 'error', 'message': 'function not found'}) +result = await func(params_kw) +return result