0.1.11 ai修复与全屏
This commit is contained in:
@@ -1,25 +1,78 @@
|
||||
import os
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Tuple, Type
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from pydantic_settings.sources import (
|
||||
DotEnvSettingsSource,
|
||||
EnvSettingsSource,
|
||||
PydanticBaseSettingsSource,
|
||||
)
|
||||
|
||||
|
||||
def _repo_root() -> Path:
|
||||
# services/ingest_service/app/core/config.py -> .../MNOTE
|
||||
return Path(__file__).resolve().parents[4]
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""FastAPI 配置,统一读取 .env 或系统环境变量"""
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore")
|
||||
"""Ingest Service 配置(优先读取仓库根目录的 .env.local/.env)。"""
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=(
|
||||
str(_repo_root() / ".env.local"),
|
||||
str(_repo_root() / ".env"),
|
||||
),
|
||||
env_file_encoding="utf-8",
|
||||
case_sensitive=False,
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
# 服务基础
|
||||
app_name: str = Field("AIMNOTE Ingest Service", env="INGEST_APP_NAME")
|
||||
environment: str = Field("development", env="INGEST_ENV")
|
||||
api_prefix: str = "/api"
|
||||
supabase_url: str = Field("http://127.0.0.1:54321", env="SUPABASE_URL")
|
||||
supabase_key: str = Field("", env="SUPABASE_SERVICE_ROLE_KEY")
|
||||
|
||||
# Supabase(通过 Kong: http://127.0.0.1:18000)
|
||||
supabase_url: str = Field("http://127.0.0.1:18000", env="SUPABASE_URL")
|
||||
supabase_service_role_key: str = Field("", env="SUPABASE_SERVICE_ROLE_KEY")
|
||||
|
||||
# LightRAG(独立服务 7777)
|
||||
lightrag_url: str = Field("http://127.0.0.1:7777", env="LIGHTRAG_URL")
|
||||
lightrag_api_key: str = Field("", env="LIGHTRAG_API_KEY")
|
||||
ollama_base_url: str = Field("http://127.0.0.1:11434/v1", env="OLLAMA_BASE_URL")
|
||||
ollama_api_key: str = Field("ollama", env="OLLAMA_API_KEY")
|
||||
embeddings_model: str = Field("qwen3-embedding:8b", env="EMBEDDING_MODEL")
|
||||
llm_model: str = Field("qwen3:32b", env="DEFAULT_LLM_MODEL")
|
||||
rerank_model: str = Field("qwen3-reranker-4b", env="RERANK_MODEL")
|
||||
|
||||
# 自动入库(rag_index_sources -> LightRAG)
|
||||
auto_index_enabled: bool = Field(True, env="AUTO_INDEX_ENABLED")
|
||||
auto_index_interval_seconds: int = Field(5, env="AUTO_INDEX_INTERVAL_SECONDS")
|
||||
auto_index_batch_size: int = Field(5, env="AUTO_INDEX_BATCH_SIZE")
|
||||
auto_index_max_attempts: int = Field(6, env="AUTO_INDEX_MAX_ATTEMPTS")
|
||||
|
||||
# 删除宽限期(用于“可撤销删除”):例如用户误删后 10 分钟内可恢复,宽限期内不清理 OCR / Storage / LightRAG 资源
|
||||
delete_grace_seconds: int = Field(600, env="DELETE_GRACE_SECONDS")
|
||||
|
||||
# MinerU(用于 OCR / PDF 结构化解析)
|
||||
mineru_enabled: bool = Field(True, env="MINERU_ENABLED")
|
||||
mineru_endpoint: str = Field("", env="MINERU_ENDPOINT")
|
||||
|
||||
@classmethod
|
||||
def settings_customise_sources(
|
||||
cls,
|
||||
settings_cls: Type[BaseSettings],
|
||||
init_settings: PydanticBaseSettingsSource,
|
||||
env_settings: EnvSettingsSource,
|
||||
dotenv_settings: DotEnvSettingsSource,
|
||||
file_secret_settings: PydanticBaseSettingsSource,
|
||||
) -> Tuple[PydanticBaseSettingsSource, ...]:
|
||||
# 关键点:优先使用 .env.local/.env 覆盖系统环境变量,避免被机器上的全局 env 污染。
|
||||
return (
|
||||
dotenv_settings,
|
||||
env_settings,
|
||||
init_settings,
|
||||
file_secret_settings,
|
||||
)
|
||||
|
||||
|
||||
@lru_cache()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import asyncio
|
||||
from fastapi import FastAPI
|
||||
|
||||
from app.api.routes import router as api_router
|
||||
from app.core.config import get_settings
|
||||
from app.core.logging import setup_logging
|
||||
from app.services.runtime import scheduler, worker
|
||||
from app.services.runtime import auto_indexer, scheduler, worker
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
@@ -17,6 +16,17 @@ app.include_router(api_router, prefix=settings.api_prefix)
|
||||
@app.on_event("startup")
|
||||
async def _startup() -> None:
|
||||
scheduler.start()
|
||||
if getattr(settings, "auto_index_enabled", False):
|
||||
try:
|
||||
await auto_indexer.startup()
|
||||
except Exception: # noqa: BLE001
|
||||
# 启动阶段不阻塞服务:自动入库失败会在后续 tick 中继续重试/记录
|
||||
pass
|
||||
scheduler.add_interval_job(
|
||||
job_id="auto_index_tick",
|
||||
func=auto_indexer.tick,
|
||||
seconds=int(getattr(settings, "auto_index_interval_seconds", 5)),
|
||||
)
|
||||
await worker.start()
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from app.core.config import get_settings
|
||||
from app.services.auto_indexer import AutoIndexService
|
||||
from app.services.job_store import InMemoryJobStore
|
||||
from app.services.mineru_client import MinerUClient
|
||||
from app.services.scheduler import SchedulerManager
|
||||
from app.services.supabase_rest import SupabaseRestAsyncClient
|
||||
from app.services.tasks import IngestWorker
|
||||
from app.services.webhooks import LightRAGWebhook
|
||||
|
||||
@@ -11,3 +14,8 @@ scheduler = SchedulerManager()
|
||||
webhook = LightRAGWebhook()
|
||||
worker = IngestWorker(job_store=job_store, webhook=webhook)
|
||||
|
||||
# 自动入库依赖:Supabase + MinerU + LightRAG
|
||||
supabase = SupabaseRestAsyncClient()
|
||||
mineru = MinerUClient()
|
||||
auto_indexer = AutoIndexService(supabase=supabase, webhook=webhook, mineru=mineru)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
@@ -42,3 +42,22 @@ class LightRAGWebhook:
|
||||
track_id = data.get("track_id") if isinstance(data, dict) else None
|
||||
logger.info("LightRAG 入库请求已提交(file_source=%s, track_id=%s)", file_source, track_id)
|
||||
return track_id
|
||||
|
||||
async def delete_documents(self, doc_ids: List[str]) -> Dict[str, Any]:
|
||||
"""请求 LightRAG 删除文档(delete_document 为后台任务)。
|
||||
|
||||
返回服务端响应(通常包含 status: deletion_started/busy/not_allowed)。
|
||||
"""
|
||||
if not doc_ids:
|
||||
return {"status": "not_allowed", "message": "doc_ids 为空", "doc_id": ""}
|
||||
|
||||
payload = {
|
||||
"doc_ids": doc_ids,
|
||||
"delete_file": False,
|
||||
"delete_llm_cache": False,
|
||||
}
|
||||
async with httpx.AsyncClient(timeout=60.0, headers=self._headers()) as client:
|
||||
resp = await client.request("DELETE", f"{self.base_url}/documents/delete_document", json=payload)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return data if isinstance(data, dict) else {"raw": data}
|
||||
|
||||
Reference in New Issue
Block a user