Support Feishu file node retrieval
This commit is contained in:
@@ -128,6 +128,24 @@ def _feishu_post(path: str, payload: dict | None = None, config: FeishuRetrieval
|
||||
return data
|
||||
|
||||
|
||||
def _feishu_download(path: str, config: FeishuRetrievalConfig) -> bytes:
|
||||
token = _get_tenant_token(config)
|
||||
url = f"{FEISHU_OPEN_API}{path}"
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
try:
|
||||
resp = httpx.get(url, headers=headers, timeout=config.timeout_seconds)
|
||||
resp.raise_for_status()
|
||||
return resp.content
|
||||
except httpx.HTTPStatusError as exc:
|
||||
raise ExternalServiceError(
|
||||
f"飞书文件下载失败 [{path}]:{exc.response.status_code} {_response_error_detail(exc.response)}",
|
||||
provider="feishu",
|
||||
) from exc
|
||||
except httpx.HTTPError as exc:
|
||||
raise ExternalServiceError(f"飞书文件下载网络错误 [{path}]:{exc}", provider="feishu") from exc
|
||||
|
||||
|
||||
def _response_error_detail(response: httpx.Response) -> str:
|
||||
try:
|
||||
body = response.json()
|
||||
@@ -220,6 +238,17 @@ def _get_docx_content(doc_token: str, config: FeishuRetrievalConfig) -> str:
|
||||
return _parse_docx_content(items)
|
||||
|
||||
|
||||
def _get_file_content(file_token: str, config: FeishuRetrievalConfig) -> str:
|
||||
"""下载普通文件节点内容,主要用于 Markdown/TXT 知识库文件。"""
|
||||
content = _feishu_download(f"/open-apis/drive/v1/files/{file_token}/download", config)
|
||||
for encoding in ("utf-8-sig", "utf-8", "gb18030"):
|
||||
try:
|
||||
return content.decode(encoding).strip()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
raise ExternalServiceError("飞书文件不是可解析的文本内容", provider="feishu")
|
||||
|
||||
|
||||
def _get_wiki_node_info(node_token: str, config: FeishuRetrievalConfig) -> dict:
|
||||
"""获取 Wiki 节点信息,返回 space_id 和 obj_token"""
|
||||
data = _feishu_get("/open-apis/wiki/v2/spaces/get_node", params={"token": node_token}, config=config)
|
||||
@@ -378,6 +407,11 @@ class FeishuKnowledgeService:
|
||||
provider="feishu",
|
||||
)
|
||||
return []
|
||||
if load_errors:
|
||||
raise ExternalServiceError(
|
||||
"部分飞书知识库读取失败:" + ";".join(load_errors[:3]),
|
||||
provider="feishu",
|
||||
)
|
||||
|
||||
# 关键词匹配评分(简单但够用的检索方式)
|
||||
scored_chunks = cls._score_and_rank(question, all_chunks)
|
||||
@@ -410,14 +444,17 @@ class FeishuKnowledgeService:
|
||||
# 1. 判断 node 类型:先查 wiki 节点信息
|
||||
try:
|
||||
node_info = cls._get_node_info_with_cache(node_id, config)
|
||||
except ExternalServiceError:
|
||||
except ExternalServiceError as node_exc:
|
||||
# 可能是普通 docx token,直接尝试读取
|
||||
try:
|
||||
content = cls._get_document_content_with_cache(node_id, config)
|
||||
title = node_id
|
||||
return [(title, content, f"{base_url}/docx/{node_id}")]
|
||||
except ExternalServiceError:
|
||||
return []
|
||||
except ExternalServiceError as docx_exc:
|
||||
raise ExternalServiceError(
|
||||
f"飞书节点读取失败:{node_exc};按普通 docx token 读取也失败:{docx_exc}",
|
||||
provider="feishu",
|
||||
) from docx_exc
|
||||
|
||||
obj_type = node_info.get("obj_type", "")
|
||||
obj_token = node_info.get("obj_token", "")
|
||||
@@ -429,6 +466,10 @@ class FeishuKnowledgeService:
|
||||
content = cls._get_document_content_with_cache(obj_token, config)
|
||||
source_url = f"{base_url}/wiki/{node_id}"
|
||||
return [(title, content, source_url)]
|
||||
if obj_type == "file":
|
||||
content = cls._get_file_content_with_cache(obj_token, config)
|
||||
source_url = f"{base_url}/wiki/{node_id}"
|
||||
return [(title, content, source_url)]
|
||||
|
||||
# 如果节点是文件夹,获取子节点
|
||||
children = cls._get_wiki_children_with_cache(space_id, node_id, config)
|
||||
@@ -447,6 +488,13 @@ class FeishuKnowledgeService:
|
||||
documents.append((child_title, content, source_url))
|
||||
except ExternalServiceError:
|
||||
continue
|
||||
elif child_obj_type == "file" and child_obj_token:
|
||||
try:
|
||||
content = cls._get_file_content_with_cache(child_obj_token, config)
|
||||
source_url = f"{base_url}/wiki/{child_node_token}"
|
||||
documents.append((child_title, content, source_url))
|
||||
except ExternalServiceError:
|
||||
continue
|
||||
|
||||
# 也读取节点本身的文档(如果有)
|
||||
if obj_type == "docx" and obj_token:
|
||||
@@ -489,6 +537,21 @@ class FeishuKnowledgeService:
|
||||
cls._content_cache[cache_key] = (content, now)
|
||||
return content
|
||||
|
||||
@classmethod
|
||||
def _get_file_content_with_cache(cls, file_token: str, config: FeishuRetrievalConfig) -> str:
|
||||
"""带缓存读取普通文件内容"""
|
||||
cache_key = f"{config.app_id}:file:{file_token}"
|
||||
now = time.time()
|
||||
|
||||
if cache_key in cls._content_cache:
|
||||
content, ts = cls._content_cache[cache_key]
|
||||
if now - ts < cls._CACHE_TTL:
|
||||
return content
|
||||
|
||||
content = _get_file_content(file_token, config)
|
||||
cls._content_cache[cache_key] = (content, now)
|
||||
return content
|
||||
|
||||
@classmethod
|
||||
def _get_wiki_children_with_cache(
|
||||
cls,
|
||||
|
||||
Reference in New Issue
Block a user