ymq bf819f3b79 refactor(agent_loop): 状态机迁移走 task 能力 + 角色规范化 agent.{role}
- 角色规范: agent 角色 agent.{role}(_normalize_role 补前缀+ROLE_ALIASES/ROLE_CHAIN/SDL_ROLES 改规范名),
  人角色 {orgtype}.{role}(默认 owner.superuser)
- role_agent_run/pm_review_run/handle_failed_task 硬编码状态 SQL 改 task_capability 工具
  (submit/approve/reject/complete/mark_failed/retry,CAS+审计)
- communication/questions/sdlc_ability 默认 handler 角色规范化(main_agent→agent.main_agent, customer→owner.superuser)
- 通用助手路径(AgentExecutor)不动,保持 hermes 式自由对话
2026-08-16 23:51:55 +08:00

102 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""问题回路 — 向后兼容层,委托 communication.py 通用冒泡引擎。
规范(团队角色/问题类型/冒泡路径)在 team-communication skill 里;
本模块只保留旧签名供 DSPY(env.question_*) 与既有调用方使用,内部走 communication.py。
状态只有 pending/answered归属不再靠 status而靠 current_handler_role/agentid 两列。
"""
import json
import logging
from sqlor.dbpools import DBPools
from .communication import (
raise_problem,
resolve_problem,
escalate_problem,
list_problems_for,
get_task_qa,
)
DBNAME = "pipeline"
logger = logging.getLogger("pipeline.questions")
def _get_db():
db = DBPools()
if not db.databases:
from appPublic.jsonConfig import getConfig
config = getConfig()
if config.databases:
db.databases = config.databases
return db, DBNAME
async def agent_ask(tenant_id: str, task_id: str, from_role: str, question: str,
context: dict = None) -> str:
"""旧签名:按 from_role 推断问题类型 + 首处理方,走通用冒泡引擎。
新代码请直接调 communication.raise_problem 显式指定首处理方。
"""
r = (from_role or "").lower()
if "pm" in r:
ptype, hr = "review_reject", "agent.develop"
elif "main_agent" in r or "cockpit" in r:
ptype, hr = "fault_report", "agent.main_agent"
else:
ptype, hr = "need_info", "agent.main_agent"
return await raise_problem(ptype, question, from_role, "",
tenant_id=tenant_id, task_id=task_id,
context=context, first_handler_role=hr)
async def answer_question(question_id: str, answer: str, answered_by: str = "",
answer_source: str = "agent.main_agent", resume: bool = True):
"""回答问题(解决 → 停止冒泡,任务恢复 submitted"""
return await resolve_problem(question_id, answer, answered_by, answer_source,
resume_task=resume)
async def forward_question(question_id: str, next_handler_role: str = "owner.superuser",
next_handler_agentid: str = ""):
"""答不了 → 沿冒泡路径转给下一个处理方(默认业主/客户)。"""
return await escalate_problem(question_id, next_handler_role, next_handler_agentid,
by_role="agent.main_agent")
async def get_question(question_id: str):
"""取单条问题记录(含 context 解析)。"""
db, dbname = _get_db()
async with db.sqlorContext(dbname) as sor:
recs = await sor.R('pipeline_agent_questions', {'id': question_id})
if not recs:
return None
rec = recs[0]
try:
d = dict(rec)
except Exception:
d = {}
ctx = d.get('context')
if ctx and isinstance(ctx, str):
try:
d['context'] = json.loads(ctx)
except (json.JSONDecodeError, TypeError):
pass
return d
async def list_questions(tenant_id: str = None, task_id: str = None,
status: str = None, limit: int = 50) -> list:
"""客户视角:列出当前该「客户(人)」处理的问题。"""
rows = await list_problems_for("owner.superuser", tenant_id=tenant_id,
task_id=task_id, limit=limit)
if status:
rows = [r for r in rows if (r.get('status') or '') == status]
return rows
async def get_task_qna(task_id: str) -> list:
"""取任务的全部已回答问答旧返回结构list供角色 agent 注入 prompt。"""
qa = await get_task_qa(task_id)
return qa.get('answered', []) if isinstance(qa, dict) else []