feat: 切换Agent自主知识工具检索链路
This commit is contained in:
203
ai_knowledge_base_v2/apps/backend/app/api/admin_agent_records.py
Normal file
203
ai_knowledge_base_v2/apps/backend/app/api/admin_agent_records.py
Normal file
@@ -0,0 +1,203 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.dependencies import get_current_admin
|
||||
from app.core.responses import api_success
|
||||
from app.models.admin import Admin
|
||||
from app.models.knowledge import (
|
||||
HumanAttentionHistory,
|
||||
HumanAttentionRecord,
|
||||
KnowledgeRetrievalCandidate,
|
||||
KnowledgeRetrievalLog,
|
||||
)
|
||||
from app.schemas.knowledge import AttentionUpdateRequest
|
||||
from app.services.admin_service import OperationLogService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/retrieval-log/list")
|
||||
def retrieval_logs(
|
||||
sessionId: int | None = Query(default=None),
|
||||
knowledgeId: int | None = Query(default=None),
|
||||
knowledgeCalled: bool | None = Query(default=None),
|
||||
statusValue: str = Query(default="", alias="status"),
|
||||
attentionCreated: bool | None = Query(default=None),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
query = select(KnowledgeRetrievalLog).order_by(KnowledgeRetrievalLog.id.desc()).limit(300)
|
||||
if sessionId is not None:
|
||||
query = query.where(KnowledgeRetrievalLog.session_id == sessionId)
|
||||
if knowledgeId is not None:
|
||||
query = query.where(KnowledgeRetrievalLog.selected_knowledge_ids.like(f"%{knowledgeId}%"))
|
||||
if knowledgeCalled is not None:
|
||||
query = query.where(KnowledgeRetrievalLog.knowledge_called == (1 if knowledgeCalled else 0))
|
||||
if statusValue:
|
||||
query = query.where(KnowledgeRetrievalLog.status == statusValue)
|
||||
if attentionCreated is not None:
|
||||
query = query.where(KnowledgeRetrievalLog.attention_created == (1 if attentionCreated else 0))
|
||||
return api_success([_retrieval_dict(item, detail=False) for item in db.scalars(query).all()])
|
||||
|
||||
|
||||
@router.get("/retrieval-log/{log_id}")
|
||||
def retrieval_detail(
|
||||
log_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
log = db.get(KnowledgeRetrievalLog, log_id)
|
||||
if log is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="检索日志不存在")
|
||||
candidates = db.scalars(
|
||||
select(KnowledgeRetrievalCandidate)
|
||||
.where(KnowledgeRetrievalCandidate.retrieval_log_id == log.id)
|
||||
.order_by(KnowledgeRetrievalCandidate.selected.desc(), KnowledgeRetrievalCandidate.id)
|
||||
).all()
|
||||
data = _retrieval_dict(log, detail=True)
|
||||
data["candidates"] = [
|
||||
{
|
||||
"id": item.id,
|
||||
"knowledgeId": item.knowledge_id,
|
||||
"versionId": item.version_id,
|
||||
"chunkId": item.chunk_id,
|
||||
"sectionId": item.section_id,
|
||||
"lexicalScore": item.lexical_score,
|
||||
"rerankScore": item.rerank_score,
|
||||
"selected": item.selected == 1,
|
||||
"discardReason": item.discard_reason,
|
||||
}
|
||||
for item in candidates
|
||||
]
|
||||
return api_success(data)
|
||||
|
||||
|
||||
@router.get("/attention/list")
|
||||
def attention_list(
|
||||
priority: str = Query(default=""),
|
||||
statusValue: str = Query(default="", alias="status"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
query = select(HumanAttentionRecord).order_by(HumanAttentionRecord.id.desc()).limit(300)
|
||||
if priority:
|
||||
query = query.where(HumanAttentionRecord.priority == priority)
|
||||
if statusValue:
|
||||
query = query.where(HumanAttentionRecord.status == statusValue)
|
||||
return api_success([_attention_dict(item) for item in db.scalars(query).all()])
|
||||
|
||||
|
||||
@router.put("/attention/{attention_id}")
|
||||
def update_attention(
|
||||
attention_id: int,
|
||||
payload: AttentionUpdateRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
item = db.get(HumanAttentionRecord, attention_id)
|
||||
if item is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="人工关注记录不存在")
|
||||
previous = item.status
|
||||
item.status = payload.status
|
||||
item.handler_id = current_admin.id
|
||||
item.handler_note = payload.note
|
||||
db.add_all(
|
||||
[
|
||||
item,
|
||||
HumanAttentionHistory(
|
||||
attention_id=item.id,
|
||||
from_status=previous,
|
||||
to_status=payload.status,
|
||||
note=payload.note,
|
||||
operated_by=current_admin.id,
|
||||
),
|
||||
]
|
||||
)
|
||||
OperationLogService.write(db, admin_id=current_admin.id, module="attention", action="update", target_id=item.id)
|
||||
db.commit()
|
||||
return api_success(_attention_dict(item))
|
||||
|
||||
|
||||
@router.delete("/attention/{attention_id}")
|
||||
def delete_attention(
|
||||
attention_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
item = db.get(HumanAttentionRecord, attention_id)
|
||||
if item is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="人工关注记录不存在")
|
||||
histories = db.scalars(
|
||||
select(HumanAttentionHistory).where(HumanAttentionHistory.attention_id == item.id)
|
||||
).all()
|
||||
for history in histories:
|
||||
db.delete(history)
|
||||
db.delete(item)
|
||||
OperationLogService.write(db, admin_id=current_admin.id, module="attention", action="delete", target_id=attention_id)
|
||||
db.commit()
|
||||
return api_success()
|
||||
|
||||
|
||||
def _retrieval_dict(item: KnowledgeRetrievalLog, *, detail: bool) -> dict:
|
||||
data = {
|
||||
"id": item.id,
|
||||
"sessionId": item.session_id,
|
||||
"messageId": item.message_id,
|
||||
"userId": item.user_id,
|
||||
"question": item.question,
|
||||
"knowledgeCalled": item.knowledge_called == 1,
|
||||
"selectedKnowledgeIds": item.selected_knowledge_ids,
|
||||
"queryTerms": _json(item.query_terms),
|
||||
"finalSectionIds": item.final_section_ids,
|
||||
"status": item.status,
|
||||
"toolCostMs": item.tool_cost_ms,
|
||||
"totalCostMs": item.total_cost_ms,
|
||||
"errorMessage": item.error_message,
|
||||
"attentionCreated": item.attention_created == 1,
|
||||
"createdAt": item.created_at,
|
||||
}
|
||||
if detail:
|
||||
data.update(
|
||||
{
|
||||
"catalogSnapshot": _json(item.catalog_snapshot),
|
||||
"catalogHash": item.catalog_hash,
|
||||
"safetyRuleVersion": item.safety_rule_version,
|
||||
"toolTrace": _json(item.tool_trace),
|
||||
"discardedConflicts": _json(item.discarded_conflicts),
|
||||
"finalAnswer": item.final_answer,
|
||||
}
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
def _attention_dict(item: HumanAttentionRecord) -> dict:
|
||||
return {
|
||||
"id": item.id,
|
||||
"sessionId": item.session_id,
|
||||
"messageId": item.message_id,
|
||||
"userId": item.user_id,
|
||||
"triggerMessage": item.trigger_message,
|
||||
"problemSummary": item.problem_summary,
|
||||
"triggerReason": item.trigger_reason,
|
||||
"priority": item.priority,
|
||||
"status": item.status,
|
||||
"handlerId": item.handler_id,
|
||||
"handlerNote": item.handler_note,
|
||||
"createdAt": item.created_at,
|
||||
"updatedAt": item.updated_at,
|
||||
}
|
||||
|
||||
|
||||
def _json(raw: str | None):
|
||||
if not raw:
|
||||
return None
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return raw
|
||||
@@ -291,6 +291,31 @@ def migration_summary(
|
||||
return api_success({"total": len(rows), "counts": counts, "items": [_knowledge_detail(item) for item in rows]})
|
||||
|
||||
|
||||
@router.post("/knowledge-migration/run")
|
||||
async def run_migration(
|
||||
knowledgeId: int | None = Query(default=None),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
_require_super_admin(current_admin)
|
||||
query = select(Knowledge).where(Knowledge.lifecycle_status == "active")
|
||||
if knowledgeId is not None:
|
||||
query = query.where(Knowledge.id == knowledgeId)
|
||||
rows = db.scalars(query.order_by(Knowledge.id)).all()
|
||||
results = []
|
||||
for knowledge in rows:
|
||||
try:
|
||||
job = await KnowledgePipelineService.synchronize(
|
||||
db, knowledge, admin_id=current_admin.id, mode="review"
|
||||
)
|
||||
results.append({"knowledgeId": knowledge.id, "ok": True, "job": _job_dict(job)})
|
||||
except Exception as exc:
|
||||
results.append({"knowledgeId": knowledge.id, "ok": False, "error": str(exc)})
|
||||
OperationLogService.write(db, admin_id=current_admin.id, module="knowledge_migration", action="run", target_id=knowledgeId)
|
||||
db.commit()
|
||||
return api_success({"total": len(results), "success": sum(1 for item in results if item["ok"]), "results": results})
|
||||
|
||||
|
||||
def _require_super_admin(admin: Admin) -> None:
|
||||
if admin.role is not None and admin.role.code != "super_admin":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="仅超级管理员可以执行此操作")
|
||||
|
||||
@@ -19,6 +19,7 @@ from app.schemas.admin import (
|
||||
)
|
||||
from app.services.admin_service import OperationLogService
|
||||
from app.services.feishu_service import FeishuKnowledgeService
|
||||
from app.services.knowledge_agent_service import KnowledgeAgentService
|
||||
from app.services.knowledge_service import KnowledgeScope
|
||||
from app.services.model_service import ModelClientService
|
||||
from app.services.rag_service import RagResult
|
||||
@@ -60,7 +61,7 @@ def reset_prompt(db: Session = Depends(get_db), current_admin: Admin = Depends(g
|
||||
|
||||
|
||||
@router.post("/agent/debug")
|
||||
def debug_agent(
|
||||
async def debug_agent(
|
||||
payload: AgentDebugRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
@@ -69,10 +70,21 @@ def debug_agent(
|
||||
if model is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="模型不存在")
|
||||
|
||||
scopes = _agent_knowledge_scopes(db, payload.knowledgeIds)
|
||||
chunks = FeishuKnowledgeService.retrieve(payload.question, scopes, db)
|
||||
prompt = _build_agent_debug_prompt(payload.promptContent, payload.question, chunks)
|
||||
rag_result = RagResult(question=payload.question, knowledge_scopes=scopes, chunks=chunks, prompt=prompt)
|
||||
rag_result = await KnowledgeAgentService.build_result(
|
||||
db,
|
||||
question=payload.question,
|
||||
version_overrides=payload.knowledgeVersions,
|
||||
preview_knowledge_ids=payload.knowledgeIds,
|
||||
)
|
||||
rag_result = RagResult(
|
||||
question=rag_result.question,
|
||||
knowledge_scopes=rag_result.knowledge_scopes,
|
||||
chunks=rag_result.chunks,
|
||||
prompt=f"{payload.promptContent.strip()}\n\n{rag_result.prompt}",
|
||||
allow_general_knowledge=rag_result.allow_general_knowledge,
|
||||
retrieval_log_id=rag_result.retrieval_log_id,
|
||||
tool_trace=rag_result.tool_trace,
|
||||
)
|
||||
result = ModelClientService.debug_model(
|
||||
model,
|
||||
rag_result,
|
||||
@@ -85,6 +97,8 @@ def debug_agent(
|
||||
"max_token": payload.maxToken,
|
||||
},
|
||||
)
|
||||
result["retrievalTrace"] = rag_result.tool_trace or []
|
||||
result["retrievalLogId"] = rag_result.retrieval_log_id
|
||||
OperationLogService.write(db, admin_id=current_admin.id, module="agent", action="debug", target_id=model.id)
|
||||
db.commit()
|
||||
return api_success(result)
|
||||
|
||||
@@ -4,6 +4,7 @@ from fastapi import APIRouter
|
||||
|
||||
from app.api import (
|
||||
admin_auth,
|
||||
admin_agent_records,
|
||||
admin_dashboard,
|
||||
admin_knowledge,
|
||||
admin_knowledge_lifecycle,
|
||||
@@ -22,6 +23,7 @@ api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
||||
api_router.include_router(user.router, prefix="/user", tags=["user"])
|
||||
api_router.include_router(chat.router, prefix="/chat", tags=["chat"])
|
||||
api_router.include_router(admin_auth.router, prefix="/admin", tags=["admin"])
|
||||
api_router.include_router(admin_agent_records.router, prefix="/admin", tags=["admin-agent-records"])
|
||||
api_router.include_router(admin_dashboard.router, prefix="/admin", tags=["admin"])
|
||||
api_router.include_router(admin_users.router, prefix="/admin", tags=["admin"])
|
||||
api_router.include_router(admin_knowledge.router, prefix="/admin", tags=["admin"])
|
||||
|
||||
Reference in New Issue
Block a user