"""问题回路 — 向后兼容层,委托 communication.py 通用冒泡引擎。 历史状态 pending/forwarded/answered 收敛为通用模型: - 问题类型(pipeline_problem_types)定义冒泡路径(escalation_path) - 问题沿路径冒泡:解决→answered(停),没解决→pos+1(沿路径走),尽头=人兜底 - 归属不再靠 status 无差别路由,而靠 current_handler(escalation_path+pos) 本文件保留旧签名供 DSPY(env.question_*) 与既有调用方使用,内部全部走 communication.py。 """ import json import logging from sqlor.dbpools import DBPools from .communication import ( raise_problem, resolve_problem, forward_to_customer, 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: """角色 agent / PM 提问。按 from_role 推断问题类型,走通用冒泡引擎。""" # 审核退回(PM) vs 故障报告(cockpit/pm) vs 缺信息(角色): ptype = "review_reject" if from_role == "pm" else ( "fault_report" if from_role == "cockpit" else "need_info") return await raise_problem( "", ptype, from_role, question, task_id=task_id, tenant_id=tenant_id, context=context) async def answer_question(question_id: str, answer: str, answered_by: str = "", answer_source: str = "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): """主 agent 答不了 → 沿路径冒泡给客户(人)。""" return await forward_to_customer(question_id) 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] if hasattr(rec, '__dict__'): d = {k: getattr(rec, k) for k in dir(rec) if not k.startswith('_')} else: d = dict(rec) 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: """客户视角:列出当前该「客户(人)」处理的问题。 旧签名兼容 status 过滤;无 status 时列客户待处理(pending)。 """ rows = await list_problems_for("customer", 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 []