- communication.py: raise/resolve/escalate/list_for,问题类型→冒泡路径,处理方=role+agentid - questions.py: 向后兼容层,委托 communication - agent_loop.py: 角色提问(need_info)/PM退回(review_reject)/故障(fault_report)走 raise_problem; _build_qna_section 按 handler 路由注入;PM approved 只答结 review_reject - sdlc_ability.py: list_questions/answer_question/escalate_question 按 handler 路由 - models: pipeline_agent_questions 加 from_agentid/problem_type/escalation_path/escalation_pos
98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
"""问题回路 — 向后兼容层,委托 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 []
|