Merge pull request #991 from SuanmoSuanyangTechnology/feature/agent-tool_xjn
feat(citation)
This commit is contained in:
@@ -1298,3 +1298,46 @@ async def import_app(
|
|||||||
data={"app": app_schema.App.model_validate(result_app), "warnings": warnings},
|
data={"app": app_schema.App.model_validate(result_app), "warnings": warnings},
|
||||||
msg="应用导入成功" + (",但部分资源需手动配置" if warnings else "")
|
msg="应用导入成功" + (",但部分资源需手动配置" if warnings else "")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/citations/{document_id}/download", summary="下载引用文档原始文件")
|
||||||
|
async def download_citation_file(
|
||||||
|
document_id: uuid.UUID = Path(..., description="引用文档ID"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
下载引用文档的原始文件。
|
||||||
|
仅当应用功能特性 citation.allow_download=true 时,前端才会展示此下载链接。
|
||||||
|
路由本身不做权限校验,由业务层通过 allow_download 开关控制入口。
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from fastapi import HTTPException, status as http_status
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
from app.core.config import settings
|
||||||
|
from app.models.document_model import Document
|
||||||
|
from app.models.file_model import File as FileModel
|
||||||
|
|
||||||
|
doc = db.query(Document).filter(Document.id == document_id).first()
|
||||||
|
if not doc:
|
||||||
|
raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="文档不存在")
|
||||||
|
|
||||||
|
file_record = db.query(FileModel).filter(FileModel.id == doc.file_id).first()
|
||||||
|
if not file_record:
|
||||||
|
raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="原始文件不存在")
|
||||||
|
|
||||||
|
file_path = os.path.join(
|
||||||
|
settings.FILE_PATH,
|
||||||
|
str(file_record.kb_id),
|
||||||
|
str(file_record.parent_id),
|
||||||
|
f"{file_record.id}{file_record.file_ext}"
|
||||||
|
)
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="文件未找到")
|
||||||
|
|
||||||
|
encoded_name = quote(doc.file_name)
|
||||||
|
return FileResponse(
|
||||||
|
path=file_path,
|
||||||
|
filename=doc.file_name,
|
||||||
|
media_type="application/octet-stream",
|
||||||
|
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_name}"}
|
||||||
|
)
|
||||||
|
|||||||
@@ -132,11 +132,6 @@ class HttpErrorDefaultTemplate(BaseModel):
|
|||||||
description="Default HTTP headers returned on error",
|
description="Default HTTP headers returned on error",
|
||||||
)
|
)
|
||||||
|
|
||||||
files: list = Field(
|
|
||||||
default_factory=list,
|
|
||||||
description="Default files list returned on error",
|
|
||||||
)
|
|
||||||
|
|
||||||
output: str = Field(
|
output: str = Field(
|
||||||
default="SUCCESS",
|
default="SUCCESS",
|
||||||
description="HTTP response body",
|
description="HTTP response body",
|
||||||
@@ -251,13 +246,6 @@ class HttpRequestNodeConfig(BaseNodeConfig):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class HttpRequestDataProcessing(BaseModel):
|
|
||||||
request: str = Field(
|
|
||||||
default="",
|
|
||||||
description="Raw HTTP request format for debugging",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class HttpRequestNodeOutput(BaseModel):
|
class HttpRequestNodeOutput(BaseModel):
|
||||||
body: str = Field(
|
body: str = Field(
|
||||||
...,
|
...,
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ class TextToSpeechConfig(BaseModel):
|
|||||||
class CitationConfig(BaseModel):
|
class CitationConfig(BaseModel):
|
||||||
"""引用和归属配置"""
|
"""引用和归属配置"""
|
||||||
enabled: bool = Field(default=False)
|
enabled: bool = Field(default=False)
|
||||||
|
allow_download: bool = Field(default=False, description="是否允许下载引用文档")
|
||||||
|
|
||||||
|
|
||||||
class Citation(BaseModel):
|
class Citation(BaseModel):
|
||||||
@@ -207,6 +208,7 @@ class Citation(BaseModel):
|
|||||||
file_name: str
|
file_name: str
|
||||||
knowledge_id: str
|
knowledge_id: str
|
||||||
score: float
|
score: float
|
||||||
|
download_url: Optional[str] = Field(default=None, description="引用文档下载链接(allow_download 开启时返回)")
|
||||||
|
|
||||||
|
|
||||||
class WebSearchConfig(BaseModel):
|
class WebSearchConfig(BaseModel):
|
||||||
@@ -657,7 +659,7 @@ class DraftRunResponse(BaseModel):
|
|||||||
usage: Optional[Dict[str, Any]] = Field(default=None, description="Token 使用情况")
|
usage: Optional[Dict[str, Any]] = Field(default=None, description="Token 使用情况")
|
||||||
elapsed_time: Optional[float] = Field(default=None, description="耗时(秒)")
|
elapsed_time: Optional[float] = Field(default=None, description="耗时(秒)")
|
||||||
suggested_questions: List[str] = Field(default_factory=list, description="下一步建议问题")
|
suggested_questions: List[str] = Field(default_factory=list, description="下一步建议问题")
|
||||||
citations: List[CitationSource] = Field(default_factory=list, description="引用来源")
|
citations: List[Dict[str, Any]] = Field(default_factory=list, description="引用来源")
|
||||||
audio_url: Optional[str] = Field(default=None, description="TTS 语音URL")
|
audio_url: Optional[str] = Field(default=None, description="TTS 语音URL")
|
||||||
|
|
||||||
def model_dump(self, **kwargs):
|
def model_dump(self, **kwargs):
|
||||||
|
|||||||
@@ -475,11 +475,19 @@ class AgentRunService:
|
|||||||
features_config: Dict[str, Any],
|
features_config: Dict[str, Any],
|
||||||
citations: List[Citation]
|
citations: List[Citation]
|
||||||
) -> List[Any]:
|
) -> List[Any]:
|
||||||
"""根据 citation 开关决定是否返回引用来源"""
|
"""根据 citation 开关决定是否返回引用来源,并根据 allow_download 附加下载链接"""
|
||||||
citation_cfg = features_config.get("citation", {})
|
citation_cfg = features_config.get("citation", {})
|
||||||
if isinstance(citation_cfg, dict) and citation_cfg.get("enabled"):
|
if not (isinstance(citation_cfg, dict) and citation_cfg.get("enabled")):
|
||||||
return [cit.model_dump() for cit in citations]
|
return []
|
||||||
return []
|
allow_download = citation_cfg.get("allow_download", False)
|
||||||
|
result = []
|
||||||
|
for cit in citations:
|
||||||
|
item = cit.model_dump() if hasattr(cit, "model_dump") else dict(cit)
|
||||||
|
if allow_download and item.get("document_id"):
|
||||||
|
from app.core.config import settings
|
||||||
|
item["download_url"] = f"{settings.FILE_LOCAL_SERVER_URL}/apps/citations/{item['document_id']}/download"
|
||||||
|
result.append(item)
|
||||||
|
return result
|
||||||
|
|
||||||
async def run(
|
async def run(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -773,9 +773,16 @@ class WorkflowService:
|
|||||||
# 过滤 citations
|
# 过滤 citations
|
||||||
citations = result.get("citations", [])
|
citations = result.get("citations", [])
|
||||||
citation_cfg = feature_configs.get("citation", {})
|
citation_cfg = feature_configs.get("citation", {})
|
||||||
filtered_citations = (
|
if isinstance(citation_cfg, dict) and citation_cfg.get("enabled"):
|
||||||
citations if isinstance(citation_cfg, dict) and citation_cfg.get("enabled") else []
|
allow_download = citation_cfg.get("allow_download", False)
|
||||||
)
|
if allow_download:
|
||||||
|
from app.core.config import settings
|
||||||
|
for c in citations:
|
||||||
|
if c.get("document_id"):
|
||||||
|
c["download_url"] = f"{settings.FILE_LOCAL_SERVER_URL}/apps/citations/{c['document_id']}/download"
|
||||||
|
filtered_citations = citations
|
||||||
|
else:
|
||||||
|
filtered_citations = []
|
||||||
assistant_meta = {"usage": token_usage, "audio_url": None}
|
assistant_meta = {"usage": token_usage, "audio_url": None}
|
||||||
if filtered_citations:
|
if filtered_citations:
|
||||||
assistant_meta["citations"] = filtered_citations
|
assistant_meta["citations"] = filtered_citations
|
||||||
@@ -975,9 +982,16 @@ class WorkflowService:
|
|||||||
# 过滤 citations
|
# 过滤 citations
|
||||||
citations = event.get("data", {}).get("citations", [])
|
citations = event.get("data", {}).get("citations", [])
|
||||||
citation_cfg = feature_configs.get("citation", {})
|
citation_cfg = feature_configs.get("citation", {})
|
||||||
filtered_citations = (
|
if isinstance(citation_cfg, dict) and citation_cfg.get("enabled"):
|
||||||
citations if isinstance(citation_cfg, dict) and citation_cfg.get("enabled") else []
|
allow_download = citation_cfg.get("allow_download", False)
|
||||||
)
|
if allow_download:
|
||||||
|
from app.core.config import settings
|
||||||
|
for c in citations:
|
||||||
|
if c.get("document_id"):
|
||||||
|
c["download_url"] = f"{settings.FILE_LOCAL_SERVER_URL}/apps/citations/{c['document_id']}/download"
|
||||||
|
filtered_citations = citations
|
||||||
|
else:
|
||||||
|
filtered_citations = []
|
||||||
assistant_meta = {"usage": token_usage, "audio_url": None}
|
assistant_meta = {"usage": token_usage, "audio_url": None}
|
||||||
if filtered_citations:
|
if filtered_citations:
|
||||||
assistant_meta["citations"] = filtered_citations
|
assistant_meta["citations"] = filtered_citations
|
||||||
|
|||||||
Reference in New Issue
Block a user