refactor: replace print() with structured logging across backend
Replace all print()/stderr logging with Python logging module using logger = logging.getLogger(__name__) pattern for consistent log levels and formatting. Extract score conversion to shared score_utils module. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+28
-24
@@ -1,11 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
聊天对话API
|
聊天对话API
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field
|
||||||
from typing import List, Optional, Dict, Any
|
from typing import List, Optional, Dict, Any, Literal
|
||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
@@ -17,6 +19,8 @@ from ..rag.chains import create_rag_chain
|
|||||||
from ..rag.conversation_chains import create_conversation_chain
|
from ..rag.conversation_chains import create_conversation_chain
|
||||||
from ..llm.siliconflow import get_llm_client
|
from ..llm.siliconflow import get_llm_client
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/chat", tags=["聊天"])
|
router = APIRouter(prefix="/chat", tags=["聊天"])
|
||||||
|
|
||||||
|
|
||||||
@@ -33,11 +37,11 @@ def get_user_id_by_username(db: Session, username: str) -> int:
|
|||||||
|
|
||||||
class ChatRequest(BaseModel):
|
class ChatRequest(BaseModel):
|
||||||
"""聊天请求模型"""
|
"""聊天请求模型"""
|
||||||
message: str
|
message: str = Field(..., min_length=1, max_length=5000, description="用户消息")
|
||||||
session_id: Optional[int] = None
|
session_id: Optional[int] = None
|
||||||
mode: str = "normal" # normal, rag
|
mode: Literal["normal", "rag"] = "normal"
|
||||||
knowledge_base_ids: Optional[List[int]] = None
|
knowledge_base_ids: Optional[List[int]] = Field(None, description="RAG模式使用的知识库ID列表")
|
||||||
model: Optional[str] = None # 模型ID,如 deepseek-ai/DeepSeek-V3, Qwen/QwQ-32B
|
model: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class ChatResponse(BaseModel):
|
class ChatResponse(BaseModel):
|
||||||
@@ -105,24 +109,24 @@ async def send_message(
|
|||||||
new_title = request.message.strip()[:30] + "..." if len(request.message.strip()) > 30 else request.message.strip()
|
new_title = request.message.strip()[:30] + "..." if len(request.message.strip()) > 30 else request.message.strip()
|
||||||
session.title = new_title
|
session.title = new_title
|
||||||
db.commit()
|
db.commit()
|
||||||
print(f"[DEBUG-AUTO-TITLE] 自动更新会话标题: {new_title}")
|
logger.debug(f"自动更新会话标题: {new_title}")
|
||||||
|
|
||||||
# 根据模式运行不同的问答工作流
|
# 根据模式运行不同的问答工作流
|
||||||
if request.mode == "rag":
|
if request.mode == "rag":
|
||||||
print(f"[DEBUG-RAG] 非流式RAG模式")
|
logger.debug(f"非流式RAG模式")
|
||||||
print(f"[DEBUG-RAG] 接收到的knowledge_base_ids: {request.knowledge_base_ids}")
|
logger.debug(f"接收到的knowledge_base_ids: {request.knowledge_base_ids}")
|
||||||
print(f"[DEBUG-RAG] knowledge_base_ids类型: {type(request.knowledge_base_ids)}")
|
logger.debug(f"knowledge_base_ids类型: {type(request.knowledge_base_ids)}")
|
||||||
if request.knowledge_base_ids:
|
if request.knowledge_base_ids:
|
||||||
print(f"[DEBUG-RAG] 第一个ID: {request.knowledge_base_ids[0]}, 类型: {type(request.knowledge_base_ids[0])}")
|
logger.debug(f"第一个ID: {request.knowledge_base_ids[0]}, 类型: {type(request.knowledge_base_ids[0])}")
|
||||||
|
|
||||||
# 使用LangChain 1.0 RAG链
|
# 使用LangChain 1.0 RAG链
|
||||||
print(f"[DEBUG-RAG] 模型: {request.model}")
|
logger.debug(f"模型: {request.model}")
|
||||||
rag_chain = create_rag_chain(knowledge_base_ids=request.knowledge_base_ids, model=request.model)
|
rag_chain = create_rag_chain(knowledge_base_ids=request.knowledge_base_ids, model=request.model)
|
||||||
result = rag_chain.invoke(request.message)
|
result = rag_chain.invoke(request.message)
|
||||||
else:
|
else:
|
||||||
# 普通模式:使用LangChain 1.0对话链
|
# 普通模式:使用LangChain 1.0对话链
|
||||||
print(f"[DEBUG-CHAT] 普通模式 - 使用LangChain对话链")
|
logger.debug(f"普通模式 - 使用LangChain对话链")
|
||||||
print(f"[DEBUG-CHAT] 模型: {request.model}")
|
logger.debug(f"模型: {request.model}")
|
||||||
conversation_chain = create_conversation_chain(model=request.model)
|
conversation_chain = create_conversation_chain(model=request.model)
|
||||||
|
|
||||||
# 获取聊天历史
|
# 获取聊天历史
|
||||||
@@ -175,7 +179,7 @@ async def stream_message(
|
|||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# 打印请求参数调试信息
|
# 打印请求参数调试信息
|
||||||
print(f"[DEBUG-CHAT] 接收流式请求 - mode: {request.mode}, knowledge_base_ids: {request.knowledge_base_ids}, model: {request.model}, message: {request.message[:50]}...")
|
logger.debug(f"接收流式请求 - mode: {request.mode}, knowledge_base_ids: {request.knowledge_base_ids}, model: {request.model}, message: {request.message[:50]}...")
|
||||||
|
|
||||||
# 获取或创建会话
|
# 获取或创建会话
|
||||||
if request.session_id:
|
if request.session_id:
|
||||||
@@ -215,7 +219,7 @@ async def stream_message(
|
|||||||
new_title = request.message.strip()[:30] + "..." if len(request.message.strip()) > 30 else request.message.strip()
|
new_title = request.message.strip()[:30] + "..." if len(request.message.strip()) > 30 else request.message.strip()
|
||||||
session.title = new_title
|
session.title = new_title
|
||||||
db.commit()
|
db.commit()
|
||||||
print(f"[DEBUG-AUTO-TITLE] 自动更新会话标题: {new_title}")
|
logger.debug(f"自动更新会话标题: {new_title}")
|
||||||
|
|
||||||
# 获取聊天历史
|
# 获取聊天历史
|
||||||
chat_history = []
|
chat_history = []
|
||||||
@@ -231,12 +235,12 @@ async def stream_message(
|
|||||||
|
|
||||||
# 根据模式选择不同的处理方式
|
# 根据模式选择不同的处理方式
|
||||||
if request.mode == "rag":
|
if request.mode == "rag":
|
||||||
print(f"[DEBUG-RAG] 使用LangChain 1.0 RAG链")
|
logger.debug(f"使用LangChain 1.0 RAG链")
|
||||||
print(f"[DEBUG-RAG] 接收到的knowledge_base_ids: {request.knowledge_base_ids}")
|
logger.debug(f"接收到的knowledge_base_ids: {request.knowledge_base_ids}")
|
||||||
print(f"[DEBUG-RAG] knowledge_base_ids类型: {type(request.knowledge_base_ids)}")
|
logger.debug(f"knowledge_base_ids类型: {type(request.knowledge_base_ids)}")
|
||||||
print(f"[DEBUG-RAG] 模型: {request.model}")
|
logger.debug(f"模型: {request.model}")
|
||||||
if request.knowledge_base_ids:
|
if request.knowledge_base_ids:
|
||||||
print(f"[DEBUG-RAG] 第一个ID: {request.knowledge_base_ids[0]}, 类型: {type(request.knowledge_base_ids[0])}")
|
logger.debug(f"第一个ID: {request.knowledge_base_ids[0]}, 类型: {type(request.knowledge_base_ids[0])}")
|
||||||
|
|
||||||
# 创建RAG链
|
# 创建RAG链
|
||||||
rag_chain = create_rag_chain(
|
rag_chain = create_rag_chain(
|
||||||
@@ -288,8 +292,8 @@ async def stream_message(
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
# 普通模式:使用LangChain 1.0对话链
|
# 普通模式:使用LangChain 1.0对话链
|
||||||
print(f"[DEBUG-CHAT] 普通模式 - 使用LangChain流式对话链")
|
logger.debug(f"普通模式 - 使用LangChain流式对话链")
|
||||||
print(f"[DEBUG-CHAT] 模型: {request.model}")
|
logger.debug(f"模型: {request.model}")
|
||||||
conversation_chain = create_conversation_chain(model=request.model)
|
conversation_chain = create_conversation_chain(model=request.model)
|
||||||
|
|
||||||
# 使用带思考过程的流式输出
|
# 使用带思考过程的流式输出
|
||||||
@@ -739,7 +743,7 @@ async def run_rag_workflow_with_context(question: str, session_id: int, db: Sess
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"RAG工作流执行失败: {str(e)}")
|
logger.error(f"RAG工作流执行失败: {str(e)}")
|
||||||
# 降级到基础问答
|
# 降级到基础问答
|
||||||
rag_chain = create_rag_chain(model=model)
|
rag_chain = create_rag_chain(model=model)
|
||||||
return rag_chain.invoke(question)
|
return rag_chain.invoke(question)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
LangGraph节点定义
|
LangGraph节点定义
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Dict, Any, List, TypedDict
|
from typing import Dict, Any, List, TypedDict
|
||||||
from langchain.schema import HumanMessage, AIMessage, SystemMessage
|
from langchain.schema import HumanMessage, AIMessage, SystemMessage
|
||||||
@@ -9,6 +10,8 @@ from ..llm.siliconflow import get_llm_client
|
|||||||
from ..rag.retrievers import KnowledgeBaseRetriever
|
from ..rag.retrievers import KnowledgeBaseRetriever
|
||||||
from ..rag.vector_store import get_vector_store
|
from ..rag.vector_store import get_vector_store
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _parse_json_from_response(text: str) -> dict:
|
def _parse_json_from_response(text: str) -> dict:
|
||||||
"""从LLM响应中提取JSON,兼容markdown代码块包裹的情况"""
|
"""从LLM响应中提取JSON,兼容markdown代码块包裹的情况"""
|
||||||
@@ -73,7 +76,7 @@ def analyze_question_node(state: GraphState) -> GraphState:
|
|||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"问题分析失败: {str(e)}")
|
logger.error(f"问题分析失败: {str(e)}")
|
||||||
new_state = state.copy()
|
new_state = state.copy()
|
||||||
new_state["metadata"] = {**state["metadata"], "analysis": {
|
new_state["metadata"] = {**state["metadata"], "analysis": {
|
||||||
"question_type": "通用",
|
"question_type": "通用",
|
||||||
@@ -138,9 +141,7 @@ def retrieve_knowledge_node(state: GraphState) -> GraphState:
|
|||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"知识检索失败: {str(e)}")
|
logger.error(f"知识检索失败: {str(e)}", exc_info=True)
|
||||||
import traceback
|
|
||||||
print(traceback.format_exc())
|
|
||||||
new_state = state.copy()
|
new_state = state.copy()
|
||||||
new_state["retrieved_docs"] = []
|
new_state["retrieved_docs"] = []
|
||||||
new_state["sources"] = []
|
new_state["sources"] = []
|
||||||
@@ -186,7 +187,7 @@ def generate_answer_node(state: GraphState) -> GraphState:
|
|||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"答案生成失败: {str(e)}")
|
logger.error(f"答案生成失败: {str(e)}")
|
||||||
new_state = state.copy()
|
new_state = state.copy()
|
||||||
new_state["answer"] = "抱歉,我无法生成合适的回答。请稍后重试。"
|
new_state["answer"] = "抱歉,我无法生成合适的回答。请稍后重试。"
|
||||||
return new_state
|
return new_state
|
||||||
@@ -208,5 +209,5 @@ def format_response_node(state: GraphState) -> GraphState:
|
|||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"响应格式化失败: {str(e)}")
|
logger.error(f"响应格式化失败: {str(e)}")
|
||||||
return state
|
return state
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
问答LangGraph工作流
|
问答LangGraph工作流
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from typing import Dict, Any, Optional, List
|
from typing import Dict, Any, Optional, List
|
||||||
from langgraph.graph import StateGraph, END
|
from langgraph.graph import StateGraph, END
|
||||||
from .nodes import GraphState, analyze_question_node, retrieve_knowledge_node, generate_answer_node, format_response_node
|
from .nodes import GraphState, analyze_question_node, retrieve_knowledge_node, generate_answer_node, format_response_node
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def create_qa_graph() -> StateGraph:
|
def create_qa_graph() -> StateGraph:
|
||||||
"""创建问答图"""
|
"""创建问答图"""
|
||||||
@@ -57,7 +60,7 @@ def run_qa_workflow(question: str, knowledge_base_ids: Optional[List[int]] = Non
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"问答工作流执行失败: {str(e)}")
|
logger.error(f"问答工作流执行失败: {str(e)}")
|
||||||
return {
|
return {
|
||||||
"answer": "抱歉,处理您的问题时出现了错误。请稍后重试。",
|
"answer": "抱歉,处理您的问题时出现了错误。请稍后重试。",
|
||||||
"sources": [],
|
"sources": [],
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
大模型API集成 — 支持 SiliconFlow 和 DeepSeek 官方 + 视觉模型
|
大模型API集成 — 支持 SiliconFlow 和 DeepSeek 官方 + 视觉模型
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import base64
|
import base64
|
||||||
from typing import List, Dict, Any, Optional, AsyncGenerator, Tuple
|
from typing import List, Dict, Any, Optional, AsyncGenerator, Tuple
|
||||||
@@ -12,6 +13,8 @@ import openai
|
|||||||
|
|
||||||
from ..core.config import get_settings
|
from ..core.config import get_settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
# 图片描述提示词
|
# 图片描述提示词
|
||||||
@@ -56,7 +59,7 @@ class SiliconFlowLLM:
|
|||||||
api_key, base_url, resolved_model = _resolve_provider(raw_model)
|
api_key, base_url, resolved_model = _resolve_provider(raw_model)
|
||||||
|
|
||||||
self.model_name = resolved_model
|
self.model_name = resolved_model
|
||||||
print(f"[LLM] 模型: {resolved_model}, API: {base_url}")
|
logger.info(f"模型: {resolved_model}, API: {base_url}")
|
||||||
|
|
||||||
self.llm = ChatOpenAI(
|
self.llm = ChatOpenAI(
|
||||||
model=resolved_model,
|
model=resolved_model,
|
||||||
@@ -210,7 +213,7 @@ class SiliconFlowLLM:
|
|||||||
return response.choices[0].message.content or ""
|
return response.choices[0].message.content or ""
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[VLM] 描述失败 attempt={attempt+1}: {e}")
|
logger.warning(f"描述失败 attempt={attempt+1}: {e}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
await asyncio.sleep(2 ** attempt)
|
await asyncio.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
迁移孤立文档到默认知识库
|
迁移孤立文档到默认知识库
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from ..core.database import get_db
|
from ..core.database import get_db
|
||||||
from ..models.user import User
|
from ..models.user import User
|
||||||
from ..models.knowledge_base import KnowledgeBase
|
from ..models.knowledge_base import KnowledgeBase
|
||||||
from ..models.document import Document
|
from ..models.document import Document
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def migrate_orphaned_documents():
|
def migrate_orphaned_documents():
|
||||||
"""将没有知识库的文档迁移到用户的默认知识库"""
|
"""将没有知识库的文档迁移到用户的默认知识库"""
|
||||||
@@ -40,12 +43,12 @@ def migrate_orphaned_documents():
|
|||||||
doc.knowledge_base_id = default_kb.id
|
doc.knowledge_base_id = default_kb.id
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
print(f"用户 {user.username} 的 {len(orphaned_docs)} 个文档已迁移到默认知识库")
|
logger.info(f"用户 {user.username} 的 {len(orphaned_docs)} 个文档已迁移到默认知识库")
|
||||||
|
|
||||||
print("孤立文档迁移完成")
|
logger.info("孤立文档迁移完成")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"迁移失败: {str(e)}")
|
logger.error(f"迁移失败: {str(e)}")
|
||||||
db.rollback()
|
db.rollback()
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
RAG检索链(LangChain 1.0)
|
RAG检索链(LangChain 1.0)
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from typing import List, Optional, Dict, Any
|
from typing import List, Optional, Dict, Any
|
||||||
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
|
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
|
||||||
from langchain_core.prompts import ChatPromptTemplate
|
from langchain_core.prompts import ChatPromptTemplate
|
||||||
@@ -11,6 +12,8 @@ from .retrievers import KnowledgeBaseRetriever
|
|||||||
from .vector_store import get_vector_store
|
from .vector_store import get_vector_store
|
||||||
from ..llm.siliconflow import get_llm_client
|
from ..llm.siliconflow import get_llm_client
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class RAGChain:
|
class RAGChain:
|
||||||
"""RAG问答链(LangChain 1.0标准API)"""
|
"""RAG问答链(LangChain 1.0标准API)"""
|
||||||
|
|
||||||
@@ -31,7 +34,7 @@ class RAGChain:
|
|||||||
score_threshold: 相似度阈值
|
score_threshold: 相似度阈值
|
||||||
model: 可选的模型名称,如 deepseek-ai/DeepSeek-V3, Qwen/QwQ-32B
|
model: 可选的模型名称,如 deepseek-ai/DeepSeek-V3, Qwen/QwQ-32B
|
||||||
"""
|
"""
|
||||||
print(f"[DEBUG-RAGChain] 初始化,knowledge_base_ids: {knowledge_base_ids}, model: {model}")
|
logger.debug(f"初始化RAG链,knowledge_base_ids: {knowledge_base_ids}, model: {model}")
|
||||||
_sf_client = get_llm_client(model=model)
|
_sf_client = get_llm_client(model=model)
|
||||||
self.llm = _sf_client.llm
|
self.llm = _sf_client.llm
|
||||||
self.client = _sf_client
|
self.client = _sf_client
|
||||||
@@ -46,7 +49,7 @@ class RAGChain:
|
|||||||
search_kwargs={"k": k},
|
search_kwargs={"k": k},
|
||||||
score_threshold=score_threshold
|
score_threshold=score_threshold
|
||||||
)
|
)
|
||||||
print(f"[DEBUG-RAGChain] 检索器创建完成,knowledge_base_ids: {self.retriever.knowledge_base_ids}")
|
logger.debug(f"检索器创建完成,knowledge_base_ids: {self.retriever.knowledge_base_ids}")
|
||||||
|
|
||||||
# 创建Prompt
|
# 创建Prompt
|
||||||
self.prompt = create_rag_prompt()
|
self.prompt = create_rag_prompt()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
LangChain 1.0 文档加载器封装 + PDF图片提取
|
LangChain 1.0 文档加载器封装 + PDF图片提取
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from typing import List, Optional, Dict
|
from typing import List, Optional, Dict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import fitz # pymupdf
|
import fitz # pymupdf
|
||||||
@@ -12,6 +13,8 @@ from langchain_community.document_loaders import (
|
|||||||
)
|
)
|
||||||
from langchain_core.documents import Document
|
from langchain_core.documents import Document
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PDFImageExtractor:
|
class PDFImageExtractor:
|
||||||
"""使用pymupdf从PDF中提取内嵌图片"""
|
"""使用pymupdf从PDF中提取内嵌图片"""
|
||||||
@@ -59,7 +62,7 @@ class PDFImageExtractor:
|
|||||||
"size": len(image_bytes),
|
"size": len(image_bytes),
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ImageExtractor] 提取图片失败 page={page_num+1} img={img_idx}: {e}")
|
logger.warning(f"提取图片失败 page={page_num+1} img={img_idx}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
doc.close()
|
doc.close()
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
嵌入模型管理
|
嵌入模型管理
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from typing import List
|
from typing import List
|
||||||
from sentence_transformers import SentenceTransformer
|
from sentence_transformers import SentenceTransformer
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from ..core.config import get_settings
|
from ..core.config import get_settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
|
|
||||||
@@ -23,7 +26,7 @@ class EmbeddingModel:
|
|||||||
"""加载嵌入模型"""
|
"""加载嵌入模型"""
|
||||||
try:
|
try:
|
||||||
self.model = SentenceTransformer(self.model_name)
|
self.model = SentenceTransformer(self.model_name)
|
||||||
print(f"嵌入模型 {self.model_name} 加载成功")
|
logger.info(f"嵌入模型 {self.model_name} 加载成功")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"嵌入模型加载失败: {str(e)}")
|
raise Exception(f"嵌入模型加载失败: {str(e)}")
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
文档检索器
|
文档检索器
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from .vector_store import get_vector_store
|
from .vector_store import get_vector_store
|
||||||
from .embeddings import get_embedding_model
|
from .embeddings import get_embedding_model
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DocumentRetriever:
|
class DocumentRetriever:
|
||||||
"""文档检索器"""
|
"""文档检索器"""
|
||||||
@@ -23,7 +26,7 @@ class DocumentRetriever:
|
|||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""检索相关文档"""
|
"""检索相关文档"""
|
||||||
try:
|
try:
|
||||||
print(f"[DEBUG-RETRIEVER] 开始检索 - query: {query[:50]}..., filter_metadata: {filter_metadata}")
|
logger.debug(f"开始检索 - query: {query[:50]}..., filter_metadata: {filter_metadata}")
|
||||||
|
|
||||||
# 执行向量搜索
|
# 执行向量搜索
|
||||||
results = self.vector_store.search(
|
results = self.vector_store.search(
|
||||||
@@ -31,19 +34,19 @@ class DocumentRetriever:
|
|||||||
n_results=top_k,
|
n_results=top_k,
|
||||||
filter_metadata=filter_metadata
|
filter_metadata=filter_metadata
|
||||||
)
|
)
|
||||||
print(f"[DEBUG-RETRIEVER] 向量搜索返回结果数量: {len(results)}")
|
logger.debug(f"向量搜索返回结果数量: {len(results)}")
|
||||||
|
|
||||||
# 过滤低分结果
|
# 过滤低分结果
|
||||||
filtered_results = [
|
filtered_results = [
|
||||||
result for result in results
|
result for result in results
|
||||||
if result.get("distance", 1.0) <= (1 - score_threshold)
|
if result.get("distance", 1.0) <= (1 - score_threshold)
|
||||||
]
|
]
|
||||||
print(f"[DEBUG-RETRIEVER] 过滤后结果数量: {len(filtered_results)}")
|
logger.debug(f"过滤后结果数量: {len(filtered_results)}")
|
||||||
|
|
||||||
return filtered_results
|
return filtered_results
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"文档检索失败: {str(e)}")
|
logger.error(f"文档检索失败: {str(e)}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def retrieve_by_document_id(self, document_id: str) -> List[Dict[str, Any]]:
|
def retrieve_by_document_id(self, document_id: str) -> List[Dict[str, Any]]:
|
||||||
@@ -56,7 +59,7 @@ class DocumentRetriever:
|
|||||||
)
|
)
|
||||||
return results
|
return results
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"按文档ID检索失败: {str(e)}")
|
logger.error(f"按文档ID检索失败: {str(e)}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_relevant_context(self, query: str, max_length: int = 2000) -> str:
|
def get_relevant_context(self, query: str, max_length: int = 2000) -> str:
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
自定义知识库检索器(LangChain 1.0)
|
自定义知识库检索器(LangChain 1.0)
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
import math
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
from langchain_core.documents import Document
|
from langchain_core.documents import Document
|
||||||
from langchain_core.callbacks.manager import CallbackManagerForRetrieverRun
|
from langchain_core.callbacks.manager import CallbackManagerForRetrieverRun
|
||||||
|
from .score_utils import convert_distance_to_score
|
||||||
from langchain_core.retrievers import BaseRetriever
|
from langchain_core.retrievers import BaseRetriever
|
||||||
|
|
||||||
class KnowledgeBaseRetriever(BaseRetriever):
|
class KnowledgeBaseRetriever(BaseRetriever):
|
||||||
@@ -23,16 +26,16 @@ class KnowledgeBaseRetriever(BaseRetriever):
|
|||||||
run_manager: CallbackManagerForRetrieverRun
|
run_manager: CallbackManagerForRetrieverRun
|
||||||
) -> List[Document]:
|
) -> List[Document]:
|
||||||
"""获取相关文档(LangChain 1.0标准接口)"""
|
"""获取相关文档(LangChain 1.0标准接口)"""
|
||||||
print(f"[DEBUG-Retriever] 查询: {query}")
|
logger.debug(f"查询: {query}")
|
||||||
print(f"[DEBUG-Retriever] knowledge_base_ids: {self.knowledge_base_ids}")
|
logger.debug(f"knowledge_base_ids: {self.knowledge_base_ids}")
|
||||||
|
|
||||||
# 构建知识库过滤条件
|
# 构建知识库过滤条件
|
||||||
filter_dict = None
|
filter_dict = None
|
||||||
if self.knowledge_base_ids:
|
if self.knowledge_base_ids:
|
||||||
filter_dict = {"knowledge_base_id": {"$in": self.knowledge_base_ids}}
|
filter_dict = {"knowledge_base_id": {"$in": self.knowledge_base_ids}}
|
||||||
print(f"[DEBUG-Retriever] 构建的过滤条件: {filter_dict}")
|
logger.debug(f"构建的过滤条件: {filter_dict}")
|
||||||
else:
|
else:
|
||||||
print(f"[DEBUG-Retriever] 没有知识库ID,不进行过滤")
|
logger.debug("没有知识库ID,不进行过滤")
|
||||||
|
|
||||||
# 执行搜索
|
# 执行搜索
|
||||||
if self.search_type == "similarity":
|
if self.search_type == "similarity":
|
||||||
@@ -41,21 +44,21 @@ class KnowledgeBaseRetriever(BaseRetriever):
|
|||||||
k=self.search_kwargs.get("k", 5),
|
k=self.search_kwargs.get("k", 5),
|
||||||
filter=filter_dict
|
filter=filter_dict
|
||||||
)
|
)
|
||||||
print(f"[DEBUG-Retriever] 搜索返回文档数量: {len(docs_and_scores)}")
|
logger.debug(f"搜索返回文档数量: {len(docs_and_scores)}")
|
||||||
|
|
||||||
# 打印每个文档的知识库ID
|
# 打印每个文档的知识库ID
|
||||||
for i, (doc, distance) in enumerate(docs_and_scores):
|
for i, (doc, distance) in enumerate(docs_and_scores):
|
||||||
kb_id = doc.metadata.get("knowledge_base_id", "未知")
|
kb_id = doc.metadata.get("knowledge_base_id", "未知")
|
||||||
print(f"[DEBUG-Retriever] 文档{i+1}: knowledge_base_id={kb_id}, distance={distance:.4f}")
|
logger.debug(f"文档{i+1}: knowledge_base_id={kb_id}, distance={distance:.4f}")
|
||||||
|
|
||||||
# 转换距离为分数并过滤
|
# 转换距离为分数并过滤
|
||||||
filtered_docs = []
|
filtered_docs = []
|
||||||
for doc, distance in docs_and_scores:
|
for doc, distance in docs_and_scores:
|
||||||
score = self._convert_distance_to_score(distance)
|
score = convert_distance_to_score(distance)
|
||||||
if score > self.score_threshold:
|
if score > self.score_threshold:
|
||||||
filtered_docs.append(doc)
|
filtered_docs.append(doc)
|
||||||
|
|
||||||
print(f"[DEBUG-Retriever] 过滤后文档数量: {len(filtered_docs)}")
|
logger.debug(f"过滤后文档数量: {len(filtered_docs)}")
|
||||||
return filtered_docs
|
return filtered_docs
|
||||||
|
|
||||||
elif self.search_type == "mmr":
|
elif self.search_type == "mmr":
|
||||||
@@ -67,16 +70,3 @@ class KnowledgeBaseRetriever(BaseRetriever):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _convert_distance_to_score(self, distance: float) -> float:
|
|
||||||
"""将ChromaDB的distance转换为0-1范围的相似度分数"""
|
|
||||||
# 内积距离(负值)
|
|
||||||
if distance < 0:
|
|
||||||
return (1 + distance) / 2
|
|
||||||
|
|
||||||
# 大距离使用对数缩放
|
|
||||||
if distance > 100:
|
|
||||||
return 1 / (1 + math.log(distance))
|
|
||||||
|
|
||||||
# 标准距离转换
|
|
||||||
return 1 / (1 + distance)
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
LangChain 1.0 向量存储封装
|
LangChain 1.0 向量存储封装
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
@@ -12,6 +13,8 @@ from langchain_core.documents import Document as LangChainDocument
|
|||||||
from ..core.config import get_settings
|
from ..core.config import get_settings
|
||||||
from .embeddings import get_embedding_model
|
from .embeddings import get_embedding_model
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +43,7 @@ class VectorStore:
|
|||||||
self.vectorstore.add_documents(documents)
|
self.vectorstore.add_documents(documents)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"添加文档失败: {str(e)}")
|
logger.error(f"添加文档失败: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def as_retriever(self, **kwargs):
|
def as_retriever(self, **kwargs):
|
||||||
@@ -54,7 +57,7 @@ class VectorStore:
|
|||||||
filter: Optional[Dict] = None
|
filter: Optional[Dict] = None
|
||||||
):
|
):
|
||||||
"""相似度搜索(带分数)"""
|
"""相似度搜索(带分数)"""
|
||||||
print(f"[DEBUG-VectorStore] 查询参数 - k: {k}, filter: {filter}")
|
logger.debug(f"查询参数 - k: {k}, filter: {filter}")
|
||||||
|
|
||||||
result = self.vectorstore.similarity_search_with_score(
|
result = self.vectorstore.similarity_search_with_score(
|
||||||
query=query,
|
query=query,
|
||||||
@@ -62,7 +65,7 @@ class VectorStore:
|
|||||||
filter=filter
|
filter=filter
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"[DEBUG-VectorStore] 返回结果数量: {len(result)}")
|
logger.debug(f"返回结果数量: {len(result)}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def delete_by_document_id(self, document_id: int) -> bool:
|
def delete_by_document_id(self, document_id: int) -> bool:
|
||||||
@@ -73,7 +76,7 @@ class VectorStore:
|
|||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"删除向量数据失败: {str(e)}")
|
logger.error(f"删除向量数据失败: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def max_marginal_relevance_search(
|
def max_marginal_relevance_search(
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
学习分析服务
|
学习分析服务
|
||||||
提供用户学习数据的统计和分析功能
|
提供用户学习数据的统计和分析功能
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import func, distinct, and_
|
from sqlalchemy import func, distinct, and_
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from ..models.user import User
|
from ..models.user import User
|
||||||
from ..models.chat import ChatSession, ChatMessage
|
from ..models.chat import ChatSession, ChatMessage
|
||||||
from ..models.document import Document
|
from ..models.document import Document
|
||||||
@@ -65,7 +67,7 @@ class AnalyticsService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] 获取用户统计数据失败: {str(e)}")
|
logger.error(f"获取用户统计数据失败: {str(e)}")
|
||||||
raise Exception(f"获取统计数据失败: {str(e)}")
|
raise Exception(f"获取统计数据失败: {str(e)}")
|
||||||
|
|
||||||
def get_user_learning_trends(self, user_id: int, days: int = 30) -> List[Dict[str, Any]]:
|
def get_user_learning_trends(self, user_id: int, days: int = 30) -> List[Dict[str, Any]]:
|
||||||
@@ -107,7 +109,7 @@ class AnalyticsService:
|
|||||||
return trends
|
return trends
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] 获取学习趋势数据失败: {str(e)}")
|
logger.error(f"获取学习趋势数据失败: {str(e)}")
|
||||||
raise Exception(f"获取学习趋势失败: {str(e)}")
|
raise Exception(f"获取学习趋势失败: {str(e)}")
|
||||||
|
|
||||||
def get_popular_questions(self, user_id: int, limit: int = 10) -> List[Dict[str, Any]]:
|
def get_popular_questions(self, user_id: int, limit: int = 10) -> List[Dict[str, Any]]:
|
||||||
@@ -141,7 +143,7 @@ class AnalyticsService:
|
|||||||
return popular_questions
|
return popular_questions
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] 获取热门问题失败: {str(e)}")
|
logger.error(f"获取热门问题失败: {str(e)}")
|
||||||
raise Exception(f"获取热门问题失败: {str(e)}")
|
raise Exception(f"获取热门问题失败: {str(e)}")
|
||||||
|
|
||||||
def get_knowledge_coverage(self, user_id: int) -> List[Dict[str, Any]]:
|
def get_knowledge_coverage(self, user_id: int) -> List[Dict[str, Any]]:
|
||||||
@@ -160,7 +162,7 @@ class AnalyticsService:
|
|||||||
return coverage_data
|
return coverage_data
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] 获取知识覆盖度失败: {str(e)}")
|
logger.error(f"获取知识覆盖度失败: {str(e)}")
|
||||||
raise Exception(f"获取知识覆盖度失败: {str(e)}")
|
raise Exception(f"获取知识覆盖度失败: {str(e)}")
|
||||||
|
|
||||||
def get_learning_report(self, user_id: int) -> Dict[str, Any]:
|
def get_learning_report(self, user_id: int) -> Dict[str, Any]:
|
||||||
@@ -211,5 +213,5 @@ class AnalyticsService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] 获取学习报告失败: {str(e)}")
|
logger.error(f"获取学习报告失败: {str(e)}")
|
||||||
raise Exception(f"获取学习报告失败: {str(e)}")
|
raise Exception(f"获取学习报告失败: {str(e)}")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
用户认证服务
|
用户认证服务
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -10,6 +11,8 @@ from ..models.user import User
|
|||||||
from ..core.security import get_password_hash, verify_password, create_access_token
|
from ..core.security import get_password_hash, verify_password, create_access_token
|
||||||
from ..core.config import get_settings
|
from ..core.config import get_settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
|
|
||||||
@@ -58,7 +61,7 @@ class AuthService:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.db.rollback()
|
self.db.rollback()
|
||||||
print(f"创建用户失败: {str(e)}")
|
logger.error(f"创建用户失败: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def authenticate_user(self, username: str, password: str) -> Optional[User]:
|
def authenticate_user(self, username: str, password: str) -> Optional[User]:
|
||||||
@@ -85,7 +88,7 @@ class AuthService:
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"用户认证失败: {str(e)}")
|
logger.error(f"用户认证失败: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_user_by_username(self, username: str) -> Optional[User]:
|
def get_user_by_username(self, username: str) -> Optional[User]:
|
||||||
@@ -117,7 +120,7 @@ class AuthService:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.db.rollback()
|
self.db.rollback()
|
||||||
print(f"更新用户失败: {str(e)}")
|
logger.error(f"更新用户失败: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def deactivate_user(self, user_id: int) -> bool:
|
def deactivate_user(self, user_id: int) -> bool:
|
||||||
@@ -133,7 +136,7 @@ class AuthService:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.db.rollback()
|
self.db.rollback()
|
||||||
print(f"停用用户失败: {str(e)}")
|
logger.error(f"停用用户失败: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def change_password(self, user_id: int, old_password: str, new_password: str) -> bool:
|
def change_password(self, user_id: int, old_password: str, new_password: str) -> bool:
|
||||||
@@ -152,7 +155,7 @@ class AuthService:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.db.rollback()
|
self.db.rollback()
|
||||||
print(f"修改密码失败: {str(e)}")
|
logger.error(f"修改密码失败: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_access_token_for_user(self, user: User) -> Dict[str, Any]:
|
def create_access_token_for_user(self, user: User) -> Dict[str, Any]:
|
||||||
@@ -193,7 +196,7 @@ class AuthService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"获取用户统计失败: {str(e)}")
|
logger.error(f"获取用户统计失败: {str(e)}")
|
||||||
return {
|
return {
|
||||||
"total_users": 0,
|
"total_users": 0,
|
||||||
"active_users": 0,
|
"active_users": 0,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
根据数据库中的行号范围从LaTeX文件动态读取内容
|
根据数据库中的行号范围从LaTeX文件动态读取内容
|
||||||
层级结构:书籍 -> Chapter -> Section -> Subsection(知识点)
|
层级结构:书籍 -> Chapter -> Section -> Subsection(知识点)
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -10,6 +11,8 @@ from sqlalchemy.orm import Session
|
|||||||
from ..models.book_structure import Chapter, Section, Subsection
|
from ..models.book_structure import Chapter, Section, Subsection
|
||||||
from .latex_parser import LaTeXParser
|
from .latex_parser import LaTeXParser
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BookContentService:
|
class BookContentService:
|
||||||
"""书籍内容服务"""
|
"""书籍内容服务"""
|
||||||
@@ -59,7 +62,7 @@ class BookContentService:
|
|||||||
)
|
)
|
||||||
return content
|
return content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"读取章节内容失败: {e}")
|
logger.error(f"读取章节内容失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_section_content(self, db: Session, section_id: int) -> Optional[str]:
|
def get_section_content(self, db: Session, section_id: int) -> Optional[str]:
|
||||||
@@ -91,7 +94,7 @@ class BookContentService:
|
|||||||
)
|
)
|
||||||
return content
|
return content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"读取节内容失败: {e}")
|
logger.error(f"读取节内容失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_subsection_content(self, db: Session, subsection_id: int) -> Optional[str]:
|
def get_subsection_content(self, db: Session, subsection_id: int) -> Optional[str]:
|
||||||
@@ -127,6 +130,6 @@ class BookContentService:
|
|||||||
)
|
)
|
||||||
return content
|
return content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"读取小节内容失败: {e}")
|
logger.error(f"读取小节内容失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import logging
|
||||||
import threading
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Callable
|
from typing import Optional, Callable
|
||||||
@@ -13,6 +14,7 @@ from ..core.config import get_settings
|
|||||||
from ..core.database import get_db
|
from ..core.database import get_db
|
||||||
from .knowledge_base_service import KnowledgeBaseService
|
from .knowledge_base_service import KnowledgeBaseService
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
|
|
||||||
@@ -27,19 +29,19 @@ class KnowledgeBaseHandler(FileSystemEventHandler):
|
|||||||
def on_created(self, event):
|
def on_created(self, event):
|
||||||
"""处理文件创建事件"""
|
"""处理文件创建事件"""
|
||||||
if not event.is_directory and self._is_supported_file(event.src_path):
|
if not event.is_directory and self._is_supported_file(event.src_path):
|
||||||
print(f"检测到新文件: {event.src_path}")
|
logger.info(f"检测到新文件: {event.src_path}")
|
||||||
self._process_file_async(event.src_path, "created")
|
self._process_file_async(event.src_path, "created")
|
||||||
|
|
||||||
def on_modified(self, event):
|
def on_modified(self, event):
|
||||||
"""处理文件修改事件"""
|
"""处理文件修改事件"""
|
||||||
if not event.is_directory and self._is_supported_file(event.src_path):
|
if not event.is_directory and self._is_supported_file(event.src_path):
|
||||||
print(f"检测到文件修改: {event.src_path}")
|
logger.info(f"检测到文件修改: {event.src_path}")
|
||||||
self._process_file_async(event.src_path, "modified")
|
self._process_file_async(event.src_path, "modified")
|
||||||
|
|
||||||
def on_deleted(self, event):
|
def on_deleted(self, event):
|
||||||
"""处理文件删除事件"""
|
"""处理文件删除事件"""
|
||||||
if not event.is_directory and self._is_supported_file(event.src_path):
|
if not event.is_directory and self._is_supported_file(event.src_path):
|
||||||
print(f"检测到文件删除: {event.src_path}")
|
logger.info(f"检测到文件删除: {event.src_path}")
|
||||||
self._handle_file_deletion(event.src_path)
|
self._handle_file_deletion(event.src_path)
|
||||||
|
|
||||||
def _is_supported_file(self, file_path: str) -> bool:
|
def _is_supported_file(self, file_path: str) -> bool:
|
||||||
@@ -54,10 +56,10 @@ class KnowledgeBaseHandler(FileSystemEventHandler):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
result = self.kb_service.process_file(file_path)
|
result = self.kb_service.process_file(file_path)
|
||||||
print(f"文件处理结果 ({event_type}): {result}")
|
logger.info(f"文件处理结果 ({event_type}): {result}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"处理文件失败: {file_path}, 错误: {str(e)}")
|
logger.error(f"处理文件失败: {file_path}, 错误: {str(e)}")
|
||||||
|
|
||||||
# 在后台线程中处理
|
# 在后台线程中处理
|
||||||
thread = threading.Thread(target=process)
|
thread = threading.Thread(target=process)
|
||||||
@@ -80,12 +82,12 @@ class KnowledgeBaseHandler(FileSystemEventHandler):
|
|||||||
|
|
||||||
if document:
|
if document:
|
||||||
result = self.kb_service.delete_document(document.id)
|
result = self.kb_service.delete_document(document.id)
|
||||||
print(f"文件删除处理结果: {result}")
|
logger.info(f"文件删除处理结果: {result}")
|
||||||
else:
|
else:
|
||||||
print(f"未找到对应的数据库记录: {file_path}")
|
logger.info(f"未找到对应的数据库记录: {file_path}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"处理文件删除失败: {file_path}, 错误: {str(e)}")
|
logger.error(f"处理文件删除失败: {file_path}, 错误: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
class FileWatcherService:
|
class FileWatcherService:
|
||||||
@@ -99,15 +101,15 @@ class FileWatcherService:
|
|||||||
def start(self):
|
def start(self):
|
||||||
"""启动文件监控"""
|
"""启动文件监控"""
|
||||||
if self.is_running:
|
if self.is_running:
|
||||||
print("文件监控服务已在运行")
|
logger.info("文件监控服务已在运行")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not settings.enable_file_watcher:
|
if not settings.enable_file_watcher:
|
||||||
print("文件监控服务已禁用")
|
logger.info("文件监控服务已禁用")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.knowledge_base_dir.exists():
|
if not self.knowledge_base_dir.exists():
|
||||||
print(f"知识库目录不存在: {self.knowledge_base_dir}")
|
logger.info(f"知识库目录不存在: {self.knowledge_base_dir}")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -130,19 +132,19 @@ class FileWatcherService:
|
|||||||
self.observer.start()
|
self.observer.start()
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
|
|
||||||
print(f"文件监控服务已启动,监控目录: {self.knowledge_base_dir}")
|
logger.info(f"文件监控服务已启动,监控目录: {self.knowledge_base_dir}")
|
||||||
|
|
||||||
# 确保子目录对应的系统知识库存在
|
# 确保子目录对应的系统知识库存在
|
||||||
print("确保系统知识库与目录同步...")
|
logger.info("确保系统知识库与目录同步...")
|
||||||
kb_service.ensure_system_knowledge_bases()
|
kb_service.ensure_system_knowledge_bases()
|
||||||
|
|
||||||
# 执行初始扫描
|
# 执行初始扫描
|
||||||
print("执行初始知识库扫描...")
|
logger.info("执行初始知识库扫描...")
|
||||||
scan_result = kb_service.scan_directory()
|
scan_result = kb_service.scan_directory()
|
||||||
print(f"初始扫描结果: {scan_result}")
|
logger.info(f"初始扫描结果: {scan_result}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"启动文件监控服务失败: {str(e)}")
|
logger.error(f"启动文件监控服务失败: {str(e)}")
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
@@ -151,7 +153,7 @@ class FileWatcherService:
|
|||||||
self.observer.stop()
|
self.observer.stop()
|
||||||
self.observer.join()
|
self.observer.join()
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
print("文件监控服务已停止")
|
logger.info("文件监控服务已停止")
|
||||||
|
|
||||||
def is_active(self) -> bool:
|
def is_active(self) -> bool:
|
||||||
"""检查监控服务是否活跃"""
|
"""检查监控服务是否活跃"""
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
import httpx
|
import httpx
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ImageGenerationService:
|
class ImageGenerationService:
|
||||||
"""统一的图像生成服务基类"""
|
"""统一的图像生成服务基类"""
|
||||||
@@ -21,9 +24,9 @@ class ImageGenerationService:
|
|||||||
|
|
||||||
async def call_api(self, model: str, payload: dict) -> dict:
|
async def call_api(self, model: str, payload: dict) -> dict:
|
||||||
"""调用硅基流动 API"""
|
"""调用硅基流动 API"""
|
||||||
print(f"[DEBUG] 调用SiliconFlow API: {self.base_url}")
|
logger.debug(f"调用SiliconFlow API: {self.base_url}")
|
||||||
print(f"[DEBUG] 模型: {model}")
|
logger.debug(f"模型: {model}")
|
||||||
print(f"[DEBUG] 请求参数: {payload}")
|
logger.debug(f"请求参数: {payload}")
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
@@ -37,9 +40,9 @@ class ImageGenerationService:
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
result = response.json()
|
result = response.json()
|
||||||
print(f"[DEBUG] API响应状态: {response.status_code}")
|
logger.debug(f"API响应状态: {response.status_code}")
|
||||||
print(f"[DEBUG] API响应类型: {type(result)}")
|
logger.debug(f"API响应类型: {type(result)}")
|
||||||
print(f"[DEBUG] API响应键: {list(result.keys()) if isinstance(result, dict) else '非字典类型'}")
|
logger.debug(f"API响应键: {list(result.keys()) if isinstance(result, dict) else '非字典类型'}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@ class ImageGenerationService:
|
|||||||
# 移除可能的空白字符
|
# 移除可能的空白字符
|
||||||
base64_data = base64_data.strip()
|
base64_data = base64_data.strip()
|
||||||
|
|
||||||
print(f"[DEBUG] 开始解码base64数据,长度: {len(base64_data)}")
|
logger.debug(f"开始解码base64数据,长度: {len(base64_data)}")
|
||||||
|
|
||||||
# 解码 base64 数据
|
# 解码 base64 数据
|
||||||
image_bytes = base64.b64decode(base64_data)
|
image_bytes = base64.b64decode(base64_data)
|
||||||
@@ -62,7 +65,7 @@ class ImageGenerationService:
|
|||||||
if len(image_bytes) == 0:
|
if len(image_bytes) == 0:
|
||||||
raise Exception("解码后的图像数据为空")
|
raise Exception("解码后的图像数据为空")
|
||||||
|
|
||||||
print(f"[DEBUG] 图像数据大小: {len(image_bytes)} bytes")
|
logger.debug(f"图像数据大小: {len(image_bytes)} bytes")
|
||||||
|
|
||||||
# 确保目录存在
|
# 确保目录存在
|
||||||
image_dir = Path(settings.generated_images_dir)
|
image_dir = Path(settings.generated_images_dir)
|
||||||
@@ -73,13 +76,13 @@ class ImageGenerationService:
|
|||||||
with open(image_path, "wb") as f:
|
with open(image_path, "wb") as f:
|
||||||
f.write(image_bytes)
|
f.write(image_bytes)
|
||||||
|
|
||||||
print(f"[DEBUG] 图像已保存到: {image_path}")
|
logger.debug(f"图像已保存到: {image_path}")
|
||||||
|
|
||||||
return str(image_path)
|
return str(image_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] 保存图像失败: {str(e)}")
|
logger.error(f"保存图像失败: {str(e)}")
|
||||||
print(f"[ERROR] base64数据长度: {len(base64_data) if base64_data else 0}")
|
logger.error(f"base64数据长度: {len(base64_data) if base64_data else 0}")
|
||||||
print(f"[ERROR] base64数据前100字符: {base64_data[:100] if base64_data else 'None'}")
|
logger.error(f"base64数据前100字符: {base64_data[:100] if base64_data else 'None'}")
|
||||||
raise Exception(f"保存图像失败: {str(e)}")
|
raise Exception(f"保存图像失败: {str(e)}")
|
||||||
|
|
||||||
def generate_image_id(self) -> str:
|
def generate_image_id(self) -> str:
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import logging
|
||||||
import httpx
|
import httpx
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from fastapi import UploadFile
|
from fastapi import UploadFile
|
||||||
from .image_generation_service import ImageGenerationService
|
from .image_generation_service import ImageGenerationService
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ImageToImageService(ImageGenerationService):
|
class ImageToImageService(ImageGenerationService):
|
||||||
"""图生图服务"""
|
"""图生图服务"""
|
||||||
@@ -59,13 +62,13 @@ class ImageToImageService(ImageGenerationService):
|
|||||||
result = await self.call_api(self.MODEL, payload)
|
result = await self.call_api(self.MODEL, payload)
|
||||||
|
|
||||||
# 调试信息
|
# 调试信息
|
||||||
print(f"API响应结构: {type(result)}")
|
logger.debug(f"API响应结构: {type(result)}")
|
||||||
if isinstance(result, dict):
|
if isinstance(result, dict):
|
||||||
print(f"响应键: {list(result.keys())}")
|
logger.debug(f"响应键: {list(result.keys())}")
|
||||||
if "images" in result:
|
if "images" in result:
|
||||||
print(f"图像数量: {len(result['images'])}")
|
logger.debug(f"图像数量: {len(result['images'])}")
|
||||||
if result["images"]:
|
if result["images"]:
|
||||||
print(f"第一个图像键: {list(result['images'][0].keys()) if isinstance(result['images'][0], dict) else '非字典类型'}")
|
logger.debug(f"第一个图像键: {list(result['images'][0].keys()) if isinstance(result['images'][0], dict) else '非字典类型'}")
|
||||||
|
|
||||||
# 保存结果
|
# 保存结果
|
||||||
image_id = self.generate_image_id()
|
image_id = self.generate_image_id()
|
||||||
@@ -73,56 +76,56 @@ class ImageToImageService(ImageGenerationService):
|
|||||||
|
|
||||||
# 检查是否为字典类型
|
# 检查是否为字典类型
|
||||||
if isinstance(edited_image_data, dict):
|
if isinstance(edited_image_data, dict):
|
||||||
print(f"[DEBUG] 图像数据是字典,键: {list(edited_image_data.keys())}")
|
logger.debug(f"图像数据是字典,键: {list(edited_image_data.keys())}")
|
||||||
|
|
||||||
# 优先检查URL字段
|
# 优先检查URL字段
|
||||||
if "url" in edited_image_data:
|
if "url" in edited_image_data:
|
||||||
image_url = edited_image_data["url"]
|
image_url = edited_image_data["url"]
|
||||||
print(f"[DEBUG] 检测到URL字段: {image_url}")
|
logger.info(f"检测到URL字段: {image_url}")
|
||||||
try:
|
try:
|
||||||
image_path = await self.download_image_from_url(image_id, image_url)
|
image_path = await self.download_image_from_url(image_id, image_url)
|
||||||
print(f"[DEBUG] URL下载成功: {image_path}")
|
logger.info(f"URL下载成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] URL下载失败: {str(e)}")
|
logger.error(f"URL下载失败: {str(e)}")
|
||||||
raise Exception(f"下载图像失败: {str(e)}")
|
raise Exception(f"下载图像失败: {str(e)}")
|
||||||
|
|
||||||
# 如果没有URL,尝试base64字段
|
# 如果没有URL,尝试base64字段
|
||||||
elif any(key in edited_image_data for key in ["b64_json", "b64", "data"]):
|
elif any(key in edited_image_data for key in ["b64_json", "b64", "data"]):
|
||||||
image_b64 = edited_image_data.get("b64_json") or edited_image_data.get("b64") or edited_image_data.get("data")
|
image_b64 = edited_image_data.get("b64_json") or edited_image_data.get("b64") or edited_image_data.get("data")
|
||||||
print(f"[DEBUG] 检测到base64字段,长度: {len(image_b64) if image_b64 else 0}")
|
logger.debug(f"检测到base64字段,长度: {len(image_b64) if image_b64 else 0}")
|
||||||
try:
|
try:
|
||||||
image_path = self.save_image(image_id, image_b64)
|
image_path = self.save_image(image_id, image_b64)
|
||||||
print(f"[DEBUG] base64保存成功: {image_path}")
|
logger.info(f"base64保存成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] base64保存失败: {str(e)}")
|
logger.error(f"base64保存失败: {str(e)}")
|
||||||
raise Exception(f"保存图像失败: {str(e)}")
|
raise Exception(f"保存图像失败: {str(e)}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"[ERROR] 未找到有效的图像数据字段")
|
logger.error(f"未找到有效的图像数据字段")
|
||||||
raise Exception("API响应中未找到有效的图像数据")
|
raise Exception("API响应中未找到有效的图像数据")
|
||||||
|
|
||||||
# 如果直接是字符串,判断是URL还是base64
|
# 如果直接是字符串,判断是URL还是base64
|
||||||
elif isinstance(edited_image_data, str):
|
elif isinstance(edited_image_data, str):
|
||||||
print(f"[DEBUG] 图像数据是字符串,长度: {len(edited_image_data)}")
|
logger.debug(f"图像数据是字符串,长度: {len(edited_image_data)}")
|
||||||
if edited_image_data.startswith('http'):
|
if edited_image_data.startswith('http'):
|
||||||
print(f"[DEBUG] 检测到URL字符串: {edited_image_data}")
|
logger.info(f"检测到URL字符串: {edited_image_data}")
|
||||||
try:
|
try:
|
||||||
image_path = await self.download_image_from_url(image_id, edited_image_data)
|
image_path = await self.download_image_from_url(image_id, edited_image_data)
|
||||||
print(f"[DEBUG] URL下载成功: {image_path}")
|
logger.info(f"URL下载成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] URL下载失败: {str(e)}")
|
logger.error(f"URL下载失败: {str(e)}")
|
||||||
raise Exception(f"下载图像失败: {str(e)}")
|
raise Exception(f"下载图像失败: {str(e)}")
|
||||||
else:
|
else:
|
||||||
print(f"[DEBUG] 检测到base64字符串")
|
logger.debug(f"检测到base64字符串")
|
||||||
try:
|
try:
|
||||||
image_path = self.save_image(image_id, edited_image_data)
|
image_path = self.save_image(image_id, edited_image_data)
|
||||||
print(f"[DEBUG] base64保存成功: {image_path}")
|
logger.info(f"base64保存成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] base64保存失败: {str(e)}")
|
logger.error(f"base64保存失败: {str(e)}")
|
||||||
raise Exception(f"保存图像失败: {str(e)}")
|
raise Exception(f"保存图像失败: {str(e)}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"[ERROR] 未知的图像数据类型: {type(edited_image_data)}")
|
logger.error(f"未知的图像数据类型: {type(edited_image_data)}")
|
||||||
raise Exception(f"不支持的图像数据类型: {type(edited_image_data)}")
|
raise Exception(f"不支持的图像数据类型: {type(edited_image_data)}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -165,70 +168,70 @@ class ImageToImageService(ImageGenerationService):
|
|||||||
result = await self.call_api(self.MODEL, payload)
|
result = await self.call_api(self.MODEL, payload)
|
||||||
|
|
||||||
# 调试信息
|
# 调试信息
|
||||||
print(f"API响应结构: {type(result)}")
|
logger.debug(f"API响应结构: {type(result)}")
|
||||||
if isinstance(result, dict):
|
if isinstance(result, dict):
|
||||||
print(f"响应键: {list(result.keys())}")
|
logger.debug(f"响应键: {list(result.keys())}")
|
||||||
if "images" in result:
|
if "images" in result:
|
||||||
print(f"图像数量: {len(result['images'])}")
|
logger.debug(f"图像数量: {len(result['images'])}")
|
||||||
|
|
||||||
# 处理变体结果
|
# 处理变体结果
|
||||||
variations = []
|
variations = []
|
||||||
for i, img_data in enumerate(result["images"]):
|
for i, img_data in enumerate(result["images"]):
|
||||||
print(f"[DEBUG] 处理第 {i+1} 个变体")
|
logger.debug(f"处理第 {i+1} 个变体")
|
||||||
image_id = self.generate_image_id()
|
image_id = self.generate_image_id()
|
||||||
|
|
||||||
# 检查是否为字典类型
|
# 检查是否为字典类型
|
||||||
if isinstance(img_data, dict):
|
if isinstance(img_data, dict):
|
||||||
print(f"[DEBUG] 图像数据是字典,键: {list(img_data.keys())}")
|
logger.debug(f"图像数据是字典,键: {list(img_data.keys())}")
|
||||||
|
|
||||||
# 优先检查URL字段
|
# 优先检查URL字段
|
||||||
if "url" in img_data:
|
if "url" in img_data:
|
||||||
image_url = img_data["url"]
|
image_url = img_data["url"]
|
||||||
print(f"[DEBUG] 检测到URL字段: {image_url}")
|
logger.info(f"检测到URL字段: {image_url}")
|
||||||
try:
|
try:
|
||||||
image_path = await self.download_image_from_url(image_id, image_url)
|
image_path = await self.download_image_from_url(image_id, image_url)
|
||||||
print(f"[DEBUG] URL下载成功: {image_path}")
|
logger.info(f"URL下载成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] URL下载失败: {str(e)}")
|
logger.error(f"URL下载失败: {str(e)}")
|
||||||
raise Exception(f"下载图像失败: {str(e)}")
|
raise Exception(f"下载图像失败: {str(e)}")
|
||||||
|
|
||||||
# 如果没有URL,尝试base64字段
|
# 如果没有URL,尝试base64字段
|
||||||
elif any(key in img_data for key in ["b64_json", "b64", "data"]):
|
elif any(key in img_data for key in ["b64_json", "b64", "data"]):
|
||||||
image_b64 = img_data.get("b64_json") or img_data.get("b64") or img_data.get("data")
|
image_b64 = img_data.get("b64_json") or img_data.get("b64") or img_data.get("data")
|
||||||
print(f"[DEBUG] 检测到base64字段,长度: {len(image_b64) if image_b64 else 0}")
|
logger.debug(f"检测到base64字段,长度: {len(image_b64) if image_b64 else 0}")
|
||||||
try:
|
try:
|
||||||
image_path = self.save_image(image_id, image_b64)
|
image_path = self.save_image(image_id, image_b64)
|
||||||
print(f"[DEBUG] base64保存成功: {image_path}")
|
logger.info(f"base64保存成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] base64保存失败: {str(e)}")
|
logger.error(f"base64保存失败: {str(e)}")
|
||||||
raise Exception(f"保存图像失败: {str(e)}")
|
raise Exception(f"保存图像失败: {str(e)}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"[ERROR] 未找到有效的图像数据字段")
|
logger.error(f"未找到有效的图像数据字段")
|
||||||
raise Exception("API响应中未找到有效的图像数据")
|
raise Exception("API响应中未找到有效的图像数据")
|
||||||
|
|
||||||
# 如果直接是字符串,判断是URL还是base64
|
# 如果直接是字符串,判断是URL还是base64
|
||||||
elif isinstance(img_data, str):
|
elif isinstance(img_data, str):
|
||||||
print(f"[DEBUG] 图像数据是字符串,长度: {len(img_data)}")
|
logger.debug(f"图像数据是字符串,长度: {len(img_data)}")
|
||||||
if img_data.startswith('http'):
|
if img_data.startswith('http'):
|
||||||
print(f"[DEBUG] 检测到URL字符串: {img_data}")
|
logger.info(f"检测到URL字符串: {img_data}")
|
||||||
try:
|
try:
|
||||||
image_path = await self.download_image_from_url(image_id, img_data)
|
image_path = await self.download_image_from_url(image_id, img_data)
|
||||||
print(f"[DEBUG] URL下载成功: {image_path}")
|
logger.info(f"URL下载成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] URL下载失败: {str(e)}")
|
logger.error(f"URL下载失败: {str(e)}")
|
||||||
raise Exception(f"下载图像失败: {str(e)}")
|
raise Exception(f"下载图像失败: {str(e)}")
|
||||||
else:
|
else:
|
||||||
print(f"[DEBUG] 检测到base64字符串")
|
logger.debug(f"检测到base64字符串")
|
||||||
try:
|
try:
|
||||||
image_path = self.save_image(image_id, img_data)
|
image_path = self.save_image(image_id, img_data)
|
||||||
print(f"[DEBUG] base64保存成功: {image_path}")
|
logger.info(f"base64保存成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] base64保存失败: {str(e)}")
|
logger.error(f"base64保存失败: {str(e)}")
|
||||||
raise Exception(f"保存图像失败: {str(e)}")
|
raise Exception(f"保存图像失败: {str(e)}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"[ERROR] 未知的图像数据类型: {type(img_data)}")
|
logger.error(f"未知的图像数据类型: {type(img_data)}")
|
||||||
raise Exception(f"不支持的图像数据类型: {type(img_data)}")
|
raise Exception(f"不支持的图像数据类型: {type(img_data)}")
|
||||||
|
|
||||||
variations.append({
|
variations.append({
|
||||||
@@ -320,7 +323,7 @@ class ImageToImageService(ImageGenerationService):
|
|||||||
async def download_image_from_url(self, image_id: str, image_url: str) -> str:
|
async def download_image_from_url(self, image_id: str, image_url: str) -> str:
|
||||||
"""从URL下载图像"""
|
"""从URL下载图像"""
|
||||||
try:
|
try:
|
||||||
print(f"[DEBUG] 下载图像URL: {image_url}")
|
logger.debug(f"下载图像URL: {image_url}")
|
||||||
|
|
||||||
# 下载图像
|
# 下载图像
|
||||||
async with httpx.AsyncClient(timeout=30) as client:
|
async with httpx.AsyncClient(timeout=30) as client:
|
||||||
@@ -331,7 +334,7 @@ class ImageToImageService(ImageGenerationService):
|
|||||||
if len(image_bytes) == 0:
|
if len(image_bytes) == 0:
|
||||||
raise Exception("下载的图像数据为空")
|
raise Exception("下载的图像数据为空")
|
||||||
|
|
||||||
print(f"[DEBUG] 下载图像数据大小: {len(image_bytes)} bytes")
|
logger.debug(f"下载图像数据大小: {len(image_bytes)} bytes")
|
||||||
|
|
||||||
# 确保目录存在
|
# 确保目录存在
|
||||||
image_dir = Path(self.get_image_dir())
|
image_dir = Path(self.get_image_dir())
|
||||||
@@ -342,11 +345,11 @@ class ImageToImageService(ImageGenerationService):
|
|||||||
with open(image_path, "wb") as f:
|
with open(image_path, "wb") as f:
|
||||||
f.write(image_bytes)
|
f.write(image_bytes)
|
||||||
|
|
||||||
print(f"[DEBUG] 图像已保存到: {image_path}")
|
logger.debug(f"图像已保存到: {image_path}")
|
||||||
return str(image_path)
|
return str(image_path)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] 下载图像失败: {str(e)}")
|
logger.error(f"下载图像失败: {str(e)}")
|
||||||
raise Exception(f"下载图像失败: {str(e)}")
|
raise Exception(f"下载图像失败: {str(e)}")
|
||||||
|
|
||||||
def get_image_dir(self) -> str:
|
def get_image_dir(self) -> str:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
知识库管理服务
|
知识库管理服务
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -15,6 +16,8 @@ from ..models.user import User
|
|||||||
from ..core.config import get_settings
|
from ..core.config import get_settings
|
||||||
from .document_service import DocumentService
|
from .document_service import DocumentService
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
|
|
||||||
@@ -106,7 +109,7 @@ class KnowledgeBaseService:
|
|||||||
existing.user_id = admin.id
|
existing.user_id = admin.id
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
self.db.refresh(existing)
|
self.db.refresh(existing)
|
||||||
print(f"[KB] 升级为系统知识库: {name} (id={existing.id})")
|
logger.info(f"升级为系统知识库: {name} (id={existing.id})")
|
||||||
return existing.id
|
return existing.id
|
||||||
|
|
||||||
# 找到 admin 用户(或任意 superuser)作为 owner
|
# 找到 admin 用户(或任意 superuser)作为 owner
|
||||||
@@ -123,7 +126,7 @@ class KnowledgeBaseService:
|
|||||||
self.db.add(kb)
|
self.db.add(kb)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
self.db.refresh(kb)
|
self.db.refresh(kb)
|
||||||
print(f"[KB] 自动创建系统知识库: {name} (id={kb.id})")
|
logger.info(f"自动创建系统知识库: {name} (id={kb.id})")
|
||||||
return kb.id
|
return kb.id
|
||||||
|
|
||||||
def _resolve_knowledge_base(self, file_path: Path) -> Optional[int]:
|
def _resolve_knowledge_base(self, file_path: Path) -> Optional[int]:
|
||||||
@@ -249,12 +252,12 @@ class KnowledgeBaseService:
|
|||||||
loop.run_until_complete(self.document_service.process_document(document.id))
|
loop.run_until_complete(self.document_service.process_document(document.id))
|
||||||
loop.close()
|
loop.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"后台处理文档 {document.id} 失败: {e}")
|
logger.error(f"后台处理文档 {document.id} 失败: {e}")
|
||||||
|
|
||||||
thread = threading.Thread(target=process_in_background, daemon=True)
|
thread = threading.Thread(target=process_in_background, daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"启动文档处理线程失败: {e}")
|
logger.error(f"启动文档处理线程失败: {e}")
|
||||||
|
|
||||||
return {"status": "new", "document_id": document.id, "message": "文档创建成功,正在处理中"}
|
return {"status": "new", "document_id": document.id, "message": "文档创建成功,正在处理中"}
|
||||||
|
|
||||||
@@ -287,12 +290,12 @@ class KnowledgeBaseService:
|
|||||||
loop.run_until_complete(self.document_service.process_document(document.id))
|
loop.run_until_complete(self.document_service.process_document(document.id))
|
||||||
loop.close()
|
loop.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"后台处理文档 {document.id} 失败: {e}")
|
logger.error(f"后台处理文档 {document.id} 失败: {e}")
|
||||||
|
|
||||||
thread = threading.Thread(target=process_in_background, daemon=True)
|
thread = threading.Thread(target=process_in_background, daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"启动文档处理线程失败: {e}")
|
logger.error(f"启动文档处理线程失败: {e}")
|
||||||
|
|
||||||
return {"status": "updated", "document_id": document.id, "message": "文档更新成功,正在重新处理中"}
|
return {"status": "updated", "document_id": document.id, "message": "文档更新成功,正在重新处理中"}
|
||||||
|
|
||||||
@@ -482,7 +485,7 @@ class KnowledgeBaseService:
|
|||||||
all_files = [f for f in directory.rglob("*") if f.is_file() and self._is_supported_file(f)]
|
all_files = [f for f in directory.rglob("*") if f.is_file() and self._is_supported_file(f)]
|
||||||
total_files = len(all_files)
|
total_files = len(all_files)
|
||||||
|
|
||||||
print(f" 找到 {total_files} 个支持的文件,开始处理...")
|
logger.info(f"找到 {total_files} 个支持的文件,开始处理...")
|
||||||
|
|
||||||
for idx, file_path in enumerate(all_files, 1):
|
for idx, file_path in enumerate(all_files, 1):
|
||||||
try:
|
try:
|
||||||
@@ -506,7 +509,7 @@ class KnowledgeBaseService:
|
|||||||
# 显示进度(每10个文件或最后一个文件时显示)
|
# 显示进度(每10个文件或最后一个文件时显示)
|
||||||
if idx % 10 == 0 or idx == total_files:
|
if idx % 10 == 0 or idx == total_files:
|
||||||
percentage = (idx * 100) // total_files if total_files > 0 else 0
|
percentage = (idx * 100) // total_files if total_files > 0 else 0
|
||||||
print(f"\r 处理进度: {idx}/{total_files} ({percentage}%)", end="", flush=True)
|
logger.info(f"处理进度: {idx}/{total_files} ({percentage}%)")
|
||||||
|
|
||||||
if existing_doc:
|
if existing_doc:
|
||||||
# 检查是否需要更新
|
# 检查是否需要更新
|
||||||
@@ -549,6 +552,6 @@ class KnowledgeBaseService:
|
|||||||
"error": str(e)
|
"error": str(e)
|
||||||
})
|
})
|
||||||
|
|
||||||
print() # 换行
|
logger.info("文件处理完成")
|
||||||
results["success"] = len(results["errors"]) == 0
|
results["success"] = len(results["errors"]) == 0
|
||||||
return results
|
return results
|
||||||
|
|||||||
@@ -3,12 +3,15 @@
|
|||||||
支持 Kwai-Kolors/Kolors 和 Qwen/Qwen-Image 模型
|
支持 Kwai-Kolors/Kolors 和 Qwen/Qwen-Image 模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
import httpx
|
import httpx
|
||||||
from typing import List, Dict, Any
|
from typing import List, Dict, Any
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from .image_generation_service import ImageGenerationService
|
from .image_generation_service import ImageGenerationService
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TextToImageService(ImageGenerationService):
|
class TextToImageService(ImageGenerationService):
|
||||||
"""文生图服务"""
|
"""文生图服务"""
|
||||||
@@ -73,72 +76,72 @@ class TextToImageService(ImageGenerationService):
|
|||||||
result = await self.call_api(self.MODELS[model], payload)
|
result = await self.call_api(self.MODELS[model], payload)
|
||||||
|
|
||||||
# 调试信息
|
# 调试信息
|
||||||
print(f"API响应结构: {type(result)}")
|
logger.debug(f"API响应结构: {type(result)}")
|
||||||
if isinstance(result, dict):
|
if isinstance(result, dict):
|
||||||
print(f"响应键: {list(result.keys())}")
|
logger.debug(f"响应键: {list(result.keys())}")
|
||||||
if "images" in result:
|
if "images" in result:
|
||||||
print(f"图像数量: {len(result['images'])}")
|
logger.debug(f"图像数量: {len(result['images'])}")
|
||||||
if result["images"]:
|
if result["images"]:
|
||||||
print(f"第一个图像键: {list(result['images'][0].keys()) if isinstance(result['images'][0], dict) else '非字典类型'}")
|
logger.debug(f"第一个图像键: {list(result['images'][0].keys()) if isinstance(result['images'][0], dict) else '非字典类型'}")
|
||||||
|
|
||||||
# 处理结果
|
# 处理结果
|
||||||
images = []
|
images = []
|
||||||
for i, img_data in enumerate(result.get("images", [])):
|
for i, img_data in enumerate(result.get("images", [])):
|
||||||
print(f"[DEBUG] 处理第 {i+1} 张图像")
|
logger.debug(f"处理第 {i+1} 张图像")
|
||||||
image_id = self.generate_image_id()
|
image_id = self.generate_image_id()
|
||||||
|
|
||||||
# 检查是否为字典类型
|
# 检查是否为字典类型
|
||||||
if isinstance(img_data, dict):
|
if isinstance(img_data, dict):
|
||||||
print(f"[DEBUG] 图像数据是字典,键: {list(img_data.keys())}")
|
logger.debug(f"图像数据是字典,键: {list(img_data.keys())}")
|
||||||
|
|
||||||
# 优先检查URL字段
|
# 优先检查URL字段
|
||||||
if "url" in img_data:
|
if "url" in img_data:
|
||||||
image_url = img_data["url"]
|
image_url = img_data["url"]
|
||||||
print(f"[DEBUG] 检测到URL字段: {image_url}")
|
logger.info(f"检测到URL字段: {image_url}")
|
||||||
try:
|
try:
|
||||||
image_path = await self.download_image_from_url(image_id, image_url)
|
image_path = await self.download_image_from_url(image_id, image_url)
|
||||||
print(f"[DEBUG] URL下载成功: {image_path}")
|
logger.info(f"URL下载成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] URL下载失败: {str(e)}")
|
logger.error(f"URL下载失败: {str(e)}")
|
||||||
raise Exception(f"下载图像失败: {str(e)}")
|
raise Exception(f"下载图像失败: {str(e)}")
|
||||||
|
|
||||||
# 如果没有URL,尝试base64字段
|
# 如果没有URL,尝试base64字段
|
||||||
elif any(key in img_data for key in ["b64_json", "b64", "data"]):
|
elif any(key in img_data for key in ["b64_json", "b64", "data"]):
|
||||||
image_b64 = img_data.get("b64_json") or img_data.get("b64") or img_data.get("data")
|
image_b64 = img_data.get("b64_json") or img_data.get("b64") or img_data.get("data")
|
||||||
print(f"[DEBUG] 检测到base64字段,长度: {len(image_b64) if image_b64 else 0}")
|
logger.debug(f"检测到base64字段,长度: {len(image_b64) if image_b64 else 0}")
|
||||||
try:
|
try:
|
||||||
image_path = self.save_image(image_id, image_b64)
|
image_path = self.save_image(image_id, image_b64)
|
||||||
print(f"[DEBUG] base64保存成功: {image_path}")
|
logger.info(f"base64保存成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] base64保存失败: {str(e)}")
|
logger.error(f"base64保存失败: {str(e)}")
|
||||||
raise Exception(f"保存图像失败: {str(e)}")
|
raise Exception(f"保存图像失败: {str(e)}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"[ERROR] 未找到有效的图像数据字段")
|
logger.error(f"未找到有效的图像数据字段")
|
||||||
raise Exception("API响应中未找到有效的图像数据")
|
raise Exception("API响应中未找到有效的图像数据")
|
||||||
|
|
||||||
# 如果直接是字符串,判断是URL还是base64
|
# 如果直接是字符串,判断是URL还是base64
|
||||||
elif isinstance(img_data, str):
|
elif isinstance(img_data, str):
|
||||||
print(f"[DEBUG] 图像数据是字符串,长度: {len(img_data)}")
|
logger.debug(f"图像数据是字符串,长度: {len(img_data)}")
|
||||||
if img_data.startswith('http'):
|
if img_data.startswith('http'):
|
||||||
print(f"[DEBUG] 检测到URL字符串: {img_data}")
|
logger.info(f"检测到URL字符串: {img_data}")
|
||||||
try:
|
try:
|
||||||
image_path = await self.download_image_from_url(image_id, img_data)
|
image_path = await self.download_image_from_url(image_id, img_data)
|
||||||
print(f"[DEBUG] URL下载成功: {image_path}")
|
logger.info(f"URL下载成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] URL下载失败: {str(e)}")
|
logger.error(f"URL下载失败: {str(e)}")
|
||||||
raise Exception(f"下载图像失败: {str(e)}")
|
raise Exception(f"下载图像失败: {str(e)}")
|
||||||
else:
|
else:
|
||||||
print(f"[DEBUG] 检测到base64字符串")
|
logger.debug(f"检测到base64字符串")
|
||||||
try:
|
try:
|
||||||
image_path = self.save_image(image_id, img_data)
|
image_path = self.save_image(image_id, img_data)
|
||||||
print(f"[DEBUG] base64保存成功: {image_path}")
|
logger.info(f"base64保存成功: {image_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] base64保存失败: {str(e)}")
|
logger.error(f"base64保存失败: {str(e)}")
|
||||||
raise Exception(f"保存图像失败: {str(e)}")
|
raise Exception(f"保存图像失败: {str(e)}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"[ERROR] 未知的图像数据类型: {type(img_data)}")
|
logger.error(f"未知的图像数据类型: {type(img_data)}")
|
||||||
raise Exception(f"不支持的图像数据类型: {type(img_data)}")
|
raise Exception(f"不支持的图像数据类型: {type(img_data)}")
|
||||||
|
|
||||||
images.append({
|
images.append({
|
||||||
@@ -182,7 +185,7 @@ class TextToImageService(ImageGenerationService):
|
|||||||
async def download_image_from_url(self, image_id: str, image_url: str) -> str:
|
async def download_image_from_url(self, image_id: str, image_url: str) -> str:
|
||||||
"""从URL下载图像"""
|
"""从URL下载图像"""
|
||||||
try:
|
try:
|
||||||
print(f"[DEBUG] 下载图像URL: {image_url}")
|
logger.debug(f"下载图像URL: {image_url}")
|
||||||
|
|
||||||
# 下载图像
|
# 下载图像
|
||||||
async with httpx.AsyncClient(timeout=30) as client:
|
async with httpx.AsyncClient(timeout=30) as client:
|
||||||
@@ -193,7 +196,7 @@ class TextToImageService(ImageGenerationService):
|
|||||||
if len(image_bytes) == 0:
|
if len(image_bytes) == 0:
|
||||||
raise Exception("下载的图像数据为空")
|
raise Exception("下载的图像数据为空")
|
||||||
|
|
||||||
print(f"[DEBUG] 下载图像数据大小: {len(image_bytes)} bytes")
|
logger.debug(f"下载图像数据大小: {len(image_bytes)} bytes")
|
||||||
|
|
||||||
# 确保目录存在
|
# 确保目录存在
|
||||||
image_dir = Path(self.get_image_dir())
|
image_dir = Path(self.get_image_dir())
|
||||||
@@ -204,11 +207,11 @@ class TextToImageService(ImageGenerationService):
|
|||||||
with open(image_path, "wb") as f:
|
with open(image_path, "wb") as f:
|
||||||
f.write(image_bytes)
|
f.write(image_bytes)
|
||||||
|
|
||||||
print(f"[DEBUG] 图像已保存到: {image_path}")
|
logger.debug(f"图像已保存到: {image_path}")
|
||||||
return str(image_path)
|
return str(image_path)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] 下载图像失败: {str(e)}")
|
logger.error(f"下载图像失败: {str(e)}")
|
||||||
raise Exception(f"下载图像失败: {str(e)}")
|
raise Exception(f"下载图像失败: {str(e)}")
|
||||||
|
|
||||||
def get_image_dir(self) -> str:
|
def get_image_dir(self) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user