Separate agent tuning from model setup

This commit is contained in:
2026-07-07 13:31:20 +08:00
parent dada4aafcd
commit da262f4596
10 changed files with 518 additions and 76 deletions

View File

@@ -9,9 +9,19 @@ from app.core.dependencies import get_current_admin
from app.core.responses import api_success
from app.models.admin import Admin
from app.models.ai_config import ModelConfig, Prompt, SystemConfig
from app.schemas.admin import EnableModelRequest, ModelSaveRequest, PromptSaveRequest, SystemConfigSaveRequest
from app.models.knowledge import Knowledge
from app.schemas.admin import (
AgentDebugRequest,
EnableModelRequest,
ModelSaveRequest,
PromptSaveRequest,
SystemConfigSaveRequest,
)
from app.services.admin_service import OperationLogService
from app.services.feishu_service import FeishuKnowledgeService
from app.services.knowledge_service import KnowledgeScope
from app.services.model_service import ModelClientService
from app.services.rag_service import RagResult
router = APIRouter()
@@ -48,6 +58,37 @@ def reset_prompt(db: Session = Depends(get_db), current_admin: Admin = Depends(g
return api_success({"promptContent": prompt.prompt_content})
@router.post("/agent/debug")
def debug_agent(
payload: AgentDebugRequest,
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
model = db.get(ModelConfig, payload.modelId)
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)
prompt = _build_agent_debug_prompt(payload.promptContent, payload.question, chunks)
rag_result = RagResult(question=payload.question, knowledge_scopes=scopes, chunks=chunks, prompt=prompt)
result = ModelClientService.debug_model(
model,
rag_result,
{
"temperature": payload.temperature,
"top_p": payload.topP,
"top_k": payload.topK,
"presence_penalty": payload.presencePenalty,
"frequency_penalty": payload.frequencyPenalty,
"max_token": payload.maxToken,
},
)
OperationLogService.write(db, admin_id=current_admin.id, module="agent", action="debug", target_id=model.id)
db.commit()
return api_success(result)
@router.get("/model/list")
def list_models(db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin)) -> dict:
models = db.scalars(select(ModelConfig).order_by(ModelConfig.id.desc())).all()
@@ -235,6 +276,32 @@ def _model_dict(model: ModelConfig) -> dict:
}
def _agent_knowledge_scopes(db: Session, knowledge_ids: list[int]) -> list[KnowledgeScope]:
query = select(Knowledge).where(Knowledge.status == 1)
if knowledge_ids:
query = query.where(Knowledge.id.in_(knowledge_ids))
rows = db.scalars(query.order_by(Knowledge.id.asc())).all()
return [
KnowledgeScope(
id=item.id,
name=item.name,
feishu_space_id=item.feishu_space_id,
feishu_node_id=item.feishu_node_id,
)
for item in rows
]
def _build_agent_debug_prompt(prompt_content: str, question: str, chunks: list) -> str:
context = "\n\n".join(
f"[知识片段 {index}] 来源:{chunk.knowledge_name} / {chunk.title}\n{chunk.content}"
for index, chunk in enumerate(chunks, start=1)
)
if not context:
context = "未检索到相关知识片段。"
return f"{prompt_content.strip()}\n\n{context}\n\n用户问题:{question.strip()}"
def _config_dict(config: SystemConfig) -> dict:
return {
"id": config.id,

View File

@@ -51,6 +51,19 @@ class PromptSaveRequest(BaseModel):
promptContent: str = Field(min_length=1)
class AgentDebugRequest(BaseModel):
promptContent: str = Field(min_length=1)
modelId: int = Field(gt=0)
knowledgeIds: list[int] = Field(default_factory=list)
question: str = Field(min_length=1, max_length=2000)
temperature: float | None = Field(default=None, ge=0, le=2)
topP: float | None = Field(default=None, ge=0, le=1)
topK: int | None = Field(default=None, ge=1, le=1000)
presencePenalty: float | None = Field(default=None, ge=-2, le=2)
frequencyPenalty: float | None = Field(default=None, ge=-2, le=2)
maxToken: int | None = Field(default=1024, ge=1, le=100000)
class ModelSaveRequest(BaseModel):
provider: str = Field(min_length=1, max_length=50)
displayName: str | None = Field(default=None, max_length=100)

View File

@@ -59,6 +59,23 @@ class ModelClientService:
return {"ok": False, "message": str(exc), "answer": ""}
return {"ok": True, "message": "模型配置可用", "answer": answer[:500]}
@staticmethod
def debug_model(model: ModelConfig, rag_result: RagResult, overrides: dict[str, Any]) -> dict[str, Any]:
debug_model = _copy_model_with_overrides(model, overrides)
try:
answer = _call_configured_model(debug_model, rag_result, allow_no_hit=True)
except ExternalServiceError as exc:
return {"ok": False, "message": str(exc), "answer": "", "prompt": rag_result.prompt}
return {
"ok": True,
"message": "Agent 调试完成",
"answer": answer[:4000],
"prompt": rag_result.prompt,
"modelName": model.model_name,
"retrieveCount": len(rag_result.chunks),
"knowledgeIds": rag_result.knowledge_ids,
}
def _mock_answer(rag_result: RagResult) -> str:
if not rag_result.is_hit:
@@ -75,6 +92,35 @@ def _mock_answer(rag_result: RagResult) -> str:
)
def _copy_model_with_overrides(model: ModelConfig, overrides: dict[str, Any]) -> ModelConfig:
debug_model = ModelConfig(
provider=model.provider,
display_name=model.display_name,
api_type=model.api_type,
model_name=model.model_name,
base_url=model.base_url,
api_url=model.api_url,
api_key=model.api_key,
auth_type=model.auth_type,
api_version=model.api_version,
temperature=overrides.get("temperature"),
top_p=overrides.get("top_p"),
top_k=overrides.get("top_k"),
presence_penalty=overrides.get("presence_penalty"),
frequency_penalty=overrides.get("frequency_penalty"),
max_token=overrides.get("max_token"),
context_window=model.context_window,
stream_enabled=model.stream_enabled,
response_format=model.response_format,
extra_params=model.extra_params,
remark=model.remark,
timeout_second=model.timeout_second,
enabled=model.enabled,
)
debug_model.id = model.id
return debug_model
def _rough_token_count(text: str) -> int:
return max(1, len(text.strip()) // 2)