Cache Feishu retrieval data in Redis
This commit is contained in:
@@ -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}"
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user