Support Feishu file node retrieval

This commit is contained in:
2026-07-08 12:29:21 +08:00
parent 7811e3ed1f
commit 925ef0238d

View File

@@ -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,