mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 07:02:00 +00:00
f0e97f614f
做项 3 时发现的同一类缺口:agent 的查询快照带着 SQL 检查结果(数据源查询工具对照数据目录查出的问题,checks),质询工件接口 原样带出——sql_excerpt 是从这条 SQL 摘的片段(按遮罩列筛选的那个条件,例如 buyer_phone = '13900000001'),for_model 是给模型 的改法、也照抄了片段。事件和工件里的 content.sql 已经换掉遮罩值,旁边的片段没换,等于从旁边把值露出来;证据面板对报告快照 本来就只给约定的几个字段、不给 for_model。 - inquiry_artifact 里的 checks 经 _masked_checks:字段照证据面板的 _query_checks(不交 for_model),sql_excerpt 照查询清单的 SQL 换(_scrub_sql,和遮罩列做等值比较的短的数、日期这样的字也认得出) - 界面不读 checks,只是接口上不再带原值 - 补单元测试和端到端测试(把 SQL 检查换成会摘出遮罩值片段的假检查)。做过去掉实现、测试变红的验证
69 lines
3.7 KiB
Python
69 lines
3.7 KiB
Python
"""审批质询:审批人针对待审报告里的一句话提问,只读 agent 现查现答(规则和做法见 engine/inquiry.py)。
|
||
|
||
- POST /api/runs/{run_id}/inquiries:{report, unit, quote?, question, approval_id?} → {inquiry, recorded, note}
|
||
- GET /api/runs/{run_id}/inquiries?report=&unit=:{items: [Inquiry]},封存前后的都算,按先后
|
||
- GET /api/runs/{run_id}/inquiries/artifacts/{artifact_id}:质询引用过的工件 {id, content, masked[, mask_note]}——
|
||
查询快照(queries[].artifact)照回答时的办法遮好:遮罩列(含起了别名、套了函数的)整列遮,别的列里整格是遮罩值的
|
||
格子也遮,masked 是有格子整格被遮掉的列名(界面把这几列整列画成「已遮罩」);一格文字里夹着遮罩值的(备注里写着
|
||
手机号),只把那一截换成「已遮罩」,这一列不记进 masked。content.sql 就是事件里记下的那条(遮罩值已换掉),两处
|
||
一字不差;content.checks(SQL 检查结果)的字段照证据面板,片段 sql_excerpt 里的遮罩值照 SQL 换掉。回答文档
|
||
(answer.doc_artifact)组装时就遮好了,原样给;回答里引用的报告指标(doc.catalog 里 m: 条目的 artifact,口径卡)
|
||
也从这里取,界面一律按 doc.catalog[alias].artifact 取。没写进运行记录的回答(recorded 为假)在本进程里留一小时,
|
||
这段时间里它引用的工件照样能取;之后、或者服务重启过,回 404。别的工件一律 404
|
||
|
||
不受理:409 {detail, code}(inquiry_not_open / inquiry_busy / inquiry_disabled);输入不合法 422 {detail, field},
|
||
field 指明是哪一项(前后端约定的形状,不是 FastAPI 自己那种校验错误数组,所以请求体自己解析)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from fastapi import APIRouter, Header, HTTPException, Request
|
||
from fastapi.responses import JSONResponse
|
||
|
||
from app.api.coded import CodedHTTPException
|
||
from app.api.runs import actor_of
|
||
from app.engine import inquiry
|
||
from app.engine.inquiry import InquiryRefused
|
||
|
||
router = APIRouter(prefix="/api/runs", tags=["inquiry"])
|
||
|
||
|
||
def _refused(e: InquiryRefused) -> Any:
|
||
"""不受理 → HTTP 响应。422 照约定只回 {detail, field};其余带机读码的回 {detail, code}。"""
|
||
if e.status == 422:
|
||
return JSONResponse(status_code=422, content={"detail": e.message, "field": e.field})
|
||
if e.code:
|
||
raise CodedHTTPException(e.status, e.message, e.code)
|
||
raise HTTPException(e.status, e.message)
|
||
|
||
|
||
@router.post("/{run_id}/inquiries", response_model=None)
|
||
async def ask_inquiry(run_id: str, request: Request, x_actor: str | None = Header(default=None)) -> Any:
|
||
"""问一个问题。回答要等 agent 查完库(最长约为设置里的时长上限再加半分钟收尾),接口一直等到回答完才返回。"""
|
||
try:
|
||
body = await request.json()
|
||
except (ValueError, UnicodeDecodeError):
|
||
body = None
|
||
try:
|
||
return await inquiry.ask(run_id, inquiry.parse_ask(body), actor=actor_of(x_actor))
|
||
except InquiryRefused as e:
|
||
return _refused(e)
|
||
|
||
|
||
@router.get("/{run_id}/inquiries", response_model=None)
|
||
async def list_inquiries(run_id: str, report: str | None = None, unit: str | None = None) -> Any:
|
||
try:
|
||
return {"items": await inquiry.list_inquiries(run_id, report=report or None, unit=unit or None)}
|
||
except InquiryRefused as e:
|
||
return _refused(e)
|
||
|
||
|
||
@router.get("/{run_id}/inquiries/artifacts/{artifact_id}", response_model=None)
|
||
async def inquiry_artifact(run_id: str, artifact_id: str) -> Any:
|
||
try:
|
||
return await inquiry.inquiry_artifact(run_id, artifact_id)
|
||
except InquiryRefused as e:
|
||
return _refused(e)
|
||
|