fix: restore chat citations and tool details
This commit is contained in:
@@ -194,16 +194,70 @@ def _result_summary(result):
|
||||
return f"待确认操作 #{action.get('id', '—')}" if isinstance(action, dict) else "待确认操作已准备"
|
||||
if "found" in result:
|
||||
return f"找到 {len(result.get('citations') or [])} 条引用" if result["found"] else "未找到相关资料"
|
||||
if isinstance(result.get("results"), list) and "count" in result:
|
||||
rows = sum(len(item.get("rows") or []) for item in result["results"] if isinstance(item, dict))
|
||||
return f"返回 {rows} 项评分(共 {result['count']} 个项目)"
|
||||
for key in ("courses", "versions", "projects", "scores", "batches", "items"):
|
||||
if isinstance(result.get(key), list):
|
||||
return f"返回 {len(result[key])} 条{key}记录"
|
||||
return "工具已返回结果"
|
||||
|
||||
|
||||
def _safe_result(result):
|
||||
"""Persist a small structural preview; original student text stays in citations or business records."""
|
||||
def _safe_citation(item):
|
||||
if not isinstance(item, dict):
|
||||
return None
|
||||
return {key: value[:limit] if isinstance(value, str) else value for key, limit in
|
||||
(("title", 200), ("quote", 500), ("url", 500), ("image_url", 500))
|
||||
if isinstance((value := item.get(key)), str)} | {
|
||||
key: item[key] for key in ("chunk_id", "document_id", "page", "project_id")
|
||||
if isinstance(item.get(key), int)}
|
||||
|
||||
|
||||
def _answer_citations(items):
|
||||
"""Keep each distinct source passage, including later tool calls and pages cited by the answer."""
|
||||
seen = set()
|
||||
citations = []
|
||||
for item in items:
|
||||
safe = _safe_citation(item)
|
||||
if not safe:
|
||||
continue
|
||||
key = (safe.get("document_id"), safe.get("chunk_id"), safe.get("title"),
|
||||
safe.get("page"), safe.get("quote"), safe.get("url"))
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
citations.append(safe)
|
||||
return citations
|
||||
|
||||
|
||||
def _safe_result(result, name=""):
|
||||
"""Persist bounded useful details without credentials or unrelated source text."""
|
||||
if not isinstance(result, dict):
|
||||
return {"summary": "工具已返回结果"}
|
||||
if name in {"search_course_material", "search_project_material"}:
|
||||
return {"found": bool(result.get("found")),
|
||||
"citations": [citation for item in (result.get("citations") or [])[:6]
|
||||
if (citation := _safe_citation(item))]}
|
||||
if name == "get_scores":
|
||||
preview = {key: result.get(key) for key in ("batch_id", "status_filter", "count", "project_offset",
|
||||
"criterion_offset", "next_project_offset", "next_criterion_offset")}
|
||||
preview["results"] = []
|
||||
for project in (result.get("results") or [])[:1]:
|
||||
if not isinstance(project, dict):
|
||||
continue
|
||||
row = {key: project.get(key) for key in ("project_id", "student_no", "name", "totals", "url", "total_rows")}
|
||||
row["rows"] = []
|
||||
for score in (project.get("rows") or [])[:6]:
|
||||
if not isinstance(score, dict):
|
||||
continue
|
||||
entry = {key: score.get(key) for key in ("criterion_id", "criterion_name", "max_score", "score_id",
|
||||
"model_score", "teacher_score", "final_score", "status")}
|
||||
entry["error"] = str(score.get("error") or "")[:300]
|
||||
entry["evidence"] = [{"document": str(ev.get("document") or "")[:200],
|
||||
"page": ev.get("page"), "quote": str(ev.get("quote") or "")[:160]}
|
||||
for ev in (score.get("evidence") or [])[:2] if isinstance(ev, dict)]
|
||||
row["rows"].append(entry)
|
||||
preview["results"].append(row)
|
||||
return preview
|
||||
allowed_text = {"status", "source", "format", "kind", "category"}
|
||||
allowed_number = {"id", "course_version_id", "batch_id", "project_id", "score_id",
|
||||
"task_id", "page", "count", "total", "progress_done", "progress_total"}
|
||||
@@ -258,11 +312,12 @@ def _run_agent(factory, conversation_id, run_id, user_text):
|
||||
content = reply.get("content")
|
||||
if not isinstance(content, str) or not content.strip():
|
||||
raise ModelError("会话模型未返回文字或工具调用")
|
||||
answer_citations = _answer_citations(citations)
|
||||
db.add(ChatMessage(conversation_id=conversation_id, role="assistant", content=content,
|
||||
citations=citations[:20], tool_calls=used))
|
||||
citations=answer_citations, tool_calls=used))
|
||||
run.status, run.finished_at = "completed", now()
|
||||
conversation.updated_at = now()
|
||||
answer = {"content": content, "citations": citations[:20], "tool_calls": used}
|
||||
answer = {"content": content, "citations": answer_citations, "tool_calls": used}
|
||||
done = {"run_id": run.id}
|
||||
run.events = [*(run.events or []), {"event": "message", "data": answer},
|
||||
{"event": "done", "data": done}][-140:]
|
||||
@@ -293,12 +348,12 @@ def _run_agent(factory, conversation_id, run_id, user_text):
|
||||
if isinstance(result, dict):
|
||||
citations.extend(result.get("citations", []))
|
||||
output = json.dumps(result, ensure_ascii=False, default=str)
|
||||
truncated = len(output) > 12000
|
||||
if truncated:
|
||||
output = json.dumps({"truncated": True, "message": "结果过长,请缩小查询范围"}, ensure_ascii=False)
|
||||
if len(output) > 12000:
|
||||
output = json.dumps({"notice": "结果已按结构压缩;可使用工具分页参数继续查询",
|
||||
**_safe_result(result, name)}, ensure_ascii=False, default=str)
|
||||
messages.append({"role": "tool", "tool_call_id": call_id, "content": output})
|
||||
entry = {**entry, "status": "completed", "summary": "结果过长,已截断" if truncated else _result_summary(result),
|
||||
"result": {"truncated": True} if truncated else _safe_result(result)}
|
||||
entry = {**entry, "status": "completed", "summary": _result_summary(result),
|
||||
"result": _safe_result(result, name)}
|
||||
except Exception as exc:
|
||||
# Tool errors are model-visible, but do not grant broader access or leak provider responses.
|
||||
db.rollback()
|
||||
|
||||
Reference in New Issue
Block a user