Cache Feishu retrieval data in Redis

This commit is contained in:
2026-07-08 17:39:04 +08:00
parent 70466a6a31
commit c68af6f806
3 changed files with 116 additions and 0 deletions

View File

@@ -21,6 +21,7 @@ from app.services.feishu_service import (
_token_cache_key,
)
from app.services.knowledge_service import KnowledgeScope
from app.services.redis_cache_service import redis_get_json, redis_get_text, redis_set_json, redis_set_text
if TYPE_CHECKING:
from app.services.rag_service import RetrievedChunk
@@ -199,43 +200,61 @@ class AsyncFeishuKnowledgeService:
@classmethod
async def _get_node_info_with_cache(cls, node_token: str, config: FeishuRetrievalConfig) -> dict:
cache_key = f"{config.app_id}:node_info:{node_token}"
redis_key = _redis_cache_key(config, "node", node_token)
now = time.time()
if cache_key in FeishuKnowledgeService._content_cache:
cached_data, ts = FeishuKnowledgeService._content_cache[cache_key]
if now - ts < FeishuKnowledgeService._CACHE_TTL:
return eval(cached_data) # noqa: S307
cached_info = await redis_get_json(redis_key)
if isinstance(cached_info, dict):
FeishuKnowledgeService._content_cache[cache_key] = (str(cached_info), now)
return cached_info
info = await _get_wiki_node_info_async(node_token, config)
FeishuKnowledgeService._content_cache[cache_key] = (str(info), now)
await redis_set_json(redis_key, info, FeishuKnowledgeService._CACHE_TTL)
return info
@classmethod
async def _get_document_content_with_cache(cls, doc_token: str, config: FeishuRetrievalConfig) -> str:
cache_key = f"{config.app_id}:doc:{doc_token}"
redis_key = _redis_cache_key(config, "doc", doc_token)
now = time.time()
if cache_key in FeishuKnowledgeService._content_cache:
content, ts = FeishuKnowledgeService._content_cache[cache_key]
if now - ts < FeishuKnowledgeService._CACHE_TTL:
return content
cached_content = await redis_get_text(redis_key)
if cached_content:
FeishuKnowledgeService._content_cache[cache_key] = (cached_content, now)
return cached_content
content = await _get_docx_content_async(doc_token, config)
FeishuKnowledgeService._content_cache[cache_key] = (content, now)
await redis_set_text(redis_key, content, FeishuKnowledgeService._CACHE_TTL)
return content
@classmethod
async def _get_file_content_with_cache(cls, file_token: str, config: FeishuRetrievalConfig) -> str:
cache_key = f"{config.app_id}:file:{file_token}"
redis_key = _redis_cache_key(config, "file", file_token)
now = time.time()
if cache_key in FeishuKnowledgeService._content_cache:
content, ts = FeishuKnowledgeService._content_cache[cache_key]
if now - ts < FeishuKnowledgeService._CACHE_TTL:
return content
cached_content = await redis_get_text(redis_key)
if cached_content:
FeishuKnowledgeService._content_cache[cache_key] = (cached_content, now)
return cached_content
content = await _get_file_content_async(file_token, config)
FeishuKnowledgeService._content_cache[cache_key] = (content, now)
await redis_set_text(redis_key, content, FeishuKnowledgeService._CACHE_TTL)
return content
@classmethod
@@ -246,25 +265,36 @@ class AsyncFeishuKnowledgeService:
config: FeishuRetrievalConfig,
) -> list[dict]:
cache_key = f"{config.app_id}:children:{space_id}:{node_token}"
redis_key = _redis_cache_key(config, "children", space_id, node_token)
now = time.time()
if cache_key in FeishuKnowledgeService._content_cache:
cached_data, ts = FeishuKnowledgeService._content_cache[cache_key]
if now - ts < FeishuKnowledgeService._CACHE_TTL:
return eval(cached_data) # noqa: S307
cached_children = await redis_get_json(redis_key)
if isinstance(cached_children, list):
FeishuKnowledgeService._content_cache[cache_key] = (str(cached_children), now)
return cached_children
children = await _get_wiki_children_async(space_id, node_token, config)
FeishuKnowledgeService._content_cache[cache_key] = (str(children), now)
await redis_set_json(redis_key, children, FeishuKnowledgeService._CACHE_TTL)
return children
async def _get_tenant_token_async(config: FeishuRetrievalConfig) -> str:
now = time.time()
cache_key = _token_cache_key(config.app_id, config.app_secret)
redis_key = _redis_cache_key(config, "tenant_token")
cached = _token_cache.get(cache_key)
if cached and cached["token"] and now < cached["expires_at"]:
return cached["token"]
cached_token = await redis_get_text(redis_key)
if cached_token:
_token_cache[cache_key] = {"token": cached_token, "expires_at": now + 300}
return cached_token
url = f"{FEISHU_OPEN_API}/open-apis/auth/v3/tenant_access_token/internal"
payload = {"app_id": config.app_id, "app_secret": config.app_secret}
@@ -283,6 +313,7 @@ async def _get_tenant_token_async(config: FeishuRetrievalConfig) -> str:
token = data["tenant_access_token"]
expire = data.get("expire", 7200)
_token_cache[cache_key] = {"token": token, "expires_at": now + expire - 300}
await redis_set_text(redis_key, token, max(60, int(expire) - 300))
return token
except httpx.HTTPError as exc:
raise ExternalServiceError(f"获取飞书Token网络错误{exc}", provider="feishu") from exc
@@ -386,3 +417,9 @@ async def _get_wiki_children_async(
break
return all_children
def _redis_cache_key(config: FeishuRetrievalConfig, *parts: str) -> str:
namespace = _token_cache_key(config.app_id, config.app_secret)
suffix = ":".join(str(part) for part in parts)
return f"ai_kb:feishu:{namespace}:{suffix}"

View File

@@ -0,0 +1,45 @@
from __future__ import annotations
import json
from typing import Any
from app.services.redis_client import get_redis_client
async def redis_get_text(key: str) -> str | None:
client = get_redis_client()
if client is None:
return None
try:
value = await client.get(key)
except Exception:
return None
return value if isinstance(value, str) else None
async def redis_set_text(key: str, value: str, ttl_seconds: int) -> None:
client = get_redis_client()
if client is None:
return
try:
await client.set(key, value, ex=ttl_seconds)
except Exception:
return
async def redis_get_json(key: str) -> Any | None:
value = await redis_get_text(key)
if not value:
return None
try:
return json.loads(value)
except json.JSONDecodeError:
return None
async def redis_set_json(key: str, value: Any, ttl_seconds: int) -> None:
try:
payload = json.dumps(value, ensure_ascii=False)
except (TypeError, ValueError):
return
await redis_set_text(key, payload, ttl_seconds)