routes.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. import base64
  2. from datetime import datetime
  3. from typing import List
  4. from fastapi import APIRouter, HTTPException
  5. from utils.device_id import get_device_id
  6. from .models import (
  7. ChatRequest, ChatResponse, OCRRequest, MessageCreateBill,
  8. ImageVectorRequest, ImageVectorResponse, BuildIndexRequest, BuildIndexResponse,
  9. SearchRequest, SearchResponse, SearchResultItem
  10. )
  11. from core.chat_service import chat_service
  12. from core.agent_manager import agent_manager
  13. from core.image_search_service import image_search_service
  14. from utils.logger import chat_logger
  15. from tools.tool_factory import get_all_tools
  16. import time
  17. from utils.registration_manager import registration_manager
  18. from core.ocr_service import PaddleOCRService
  19. from core.document_processor.document_service import DocumentProcessingService
  20. # 初始化服务
  21. ocr_service = PaddleOCRService(
  22. api_url="https://a8l0g1qda8zd48nb.aistudio-app.com/ocr",
  23. token="f97d214abf87d5ea3c156e21257732a3b19661cb",
  24. )
  25. doc_service = DocumentProcessingService(ocr_service=ocr_service)
  26. router = APIRouter()
  27. @router.post("/chat", response_model=ChatResponse)
  28. async def chat_endpoint(request: ChatRequest):
  29. """聊天接口"""
  30. try:
  31. result = await chat_service.process_chat_request(request.model_dump())
  32. return ChatResponse(**result)
  33. except Exception as e:
  34. chat_logger.error(f"API处理失败: {str(e)}")
  35. raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")
  36. @router.post("/ocr")
  37. async def orc(request: OCRRequest):
  38. """OCR接口"""
  39. try:
  40. chat_logger.info(f"开始进行图片识别")
  41. # 1. 解码Base64图片
  42. try:
  43. if "," in request.image:
  44. # 去掉 data:image/xxx;base64, 前缀
  45. base64_str = request.image.split(",", 1)[1]
  46. else:
  47. base64_str = request.image
  48. image_bytes = base64.b64decode(base64_str)
  49. except Exception as e:
  50. chat_logger.error(f"图片解码失败: {e}")
  51. raise HTTPException(400, f"图片格式错误: {str(e)}")
  52. # 2. OCR识别
  53. result = await doc_service.pure_ocr(
  54. image_bytes=image_bytes, file_type=request.file_type
  55. )
  56. # 3. 返回结果
  57. return result
  58. except HTTPException:
  59. raise
  60. except Exception as e:
  61. chat_logger.error(f"处理失败: {e}")
  62. raise HTTPException(500, f"处理失败: {str(e)}")
  63. @router.post("/ocr_create_bill")
  64. async def ocr_create_bill_endpoint(request: OCRRequest):
  65. """
  66. 处理单张图片
  67. 请求格式:
  68. {
  69. "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
  70. "type": "invoice",
  71. }
  72. """
  73. try:
  74. chat_logger.info(f"开始处理 {request.type} 单据")
  75. # 1. 解码Base64图片
  76. try:
  77. if "," in request.image:
  78. # 去掉 data:image/xxx;base64, 前缀
  79. base64_str = request.image.split(",", 1)[1]
  80. else:
  81. base64_str = request.image
  82. image_bytes = base64.b64decode(base64_str)
  83. except Exception as e:
  84. chat_logger.error(f"图片解码失败: {e}")
  85. raise HTTPException(400, f"图片格式错误: {str(e)}")
  86. # 2. 处理单据
  87. result = await doc_service.ocr_create_bill(
  88. image_bytes=image_bytes, document_type=request.type
  89. )
  90. # 3. 返回结果
  91. return {
  92. "success": True,
  93. "type": request.type,
  94. "text": result.get("ocr_text", ""), # 识别出的文本
  95. "data": result.get("parsed_data", {}), # 结构化数据
  96. "timestamp": datetime.now().isoformat(),
  97. }
  98. except HTTPException:
  99. raise
  100. except Exception as e:
  101. chat_logger.error(f"处理失败: {e}")
  102. raise HTTPException(500, f"处理失败: {str(e)}")
  103. @router.post("/message_create_bill")
  104. async def message_create_bill_endpoint(request: MessageCreateBill):
  105. """
  106. 通过文本消息辅助建立单据
  107. 请求格式:
  108. {
  109. "message": "这是一条单据描述",
  110. "document_type": "invoice",
  111. }
  112. """
  113. try:
  114. result = await doc_service.message_create_bill(
  115. message=request.message, document_type=request.document_type
  116. )
  117. return result
  118. except Exception as e:
  119. chat_logger.error(f"处理失败: {e}")
  120. raise HTTPException(500, f"处理失败: {str(e)}")
  121. @router.get("/cache/status")
  122. async def cache_status():
  123. """查看缓存状态 - 修复版本"""
  124. try:
  125. cache_status = await agent_manager.get_cache_status()
  126. # ✅ 确保返回完整信息
  127. return {
  128. "success": True,
  129. "cache_size": cache_status.get("cache_size", 0),
  130. "cache_expiry_seconds": cache_status.get("cache_expiry_seconds", 3600),
  131. "cache_entries_count": len(cache_status.get("cache_entries", [])),
  132. "cache_entries": cache_status.get("cache_entries", []),
  133. "timestamp": time.time(),
  134. }
  135. except Exception as e:
  136. chat_logger.error(f"获取缓存状态失败: {str(e)}")
  137. return {
  138. "success": False,
  139. "error": str(e),
  140. "cache_size": 0,
  141. "cache_entries_count": 0,
  142. "cache_entries": [],
  143. }
  144. @router.delete("/cache/clear")
  145. async def clear_cache():
  146. """清空缓存"""
  147. try:
  148. # ✅ 使用异步版本
  149. cleared = await agent_manager.clear_cache()
  150. chat_logger.info(f"清空agent缓存, 清理数量={cleared}")
  151. return {
  152. "cleared_entries": cleared,
  153. "message": "缓存已清空",
  154. "status": "success",
  155. }
  156. except Exception as e:
  157. chat_logger.error(f"清空缓存失败: {str(e)}")
  158. raise HTTPException(status_code=500, detail=f"清空缓存失败: {str(e)}")
  159. @router.get("/health")
  160. async def health_check():
  161. """健康检查"""
  162. try:
  163. # ✅ 使用异步版本
  164. registration_status = await registration_manager.check_registration()
  165. return {
  166. "status": "healthy",
  167. "service": "龙嘉软件AI助手API",
  168. "registration_status": (
  169. "已注册" if registration_status else "未注册或注册过期"
  170. ),
  171. "device_id": get_device_id(),
  172. "timestamp": time.time(),
  173. }
  174. except Exception as e:
  175. return {
  176. "status": "unhealthy",
  177. "service": "龙嘉软件AI助手API",
  178. "error": str(e),
  179. "timestamp": time.time(),
  180. }
  181. @router.get("/tools/status")
  182. async def tools_status():
  183. """查看工具状态"""
  184. try:
  185. tools = get_all_tools()
  186. tool_info = []
  187. for i, tool in enumerate(tools):
  188. tool_info.append(
  189. {
  190. "index": i + 1,
  191. "name": getattr(tool, "name", "unknown"),
  192. "description": getattr(tool, "description", "unknown")[:100]
  193. + "...",
  194. }
  195. )
  196. return {"total_tools": len(tools), "tools": tool_info, "status": "success"}
  197. except Exception as e:
  198. chat_logger.error(f"获取工具状态失败: {str(e)}")
  199. raise HTTPException(status_code=500, detail=f"获取工具状态失败: {str(e)}")
  200. @router.post("/cache/initialize")
  201. async def initialize_cache():
  202. """初始化缓存系统"""
  203. try:
  204. # ✅ 使用异步版本
  205. await agent_manager.initialize()
  206. chat_logger.info("缓存系统初始化完成")
  207. return {"status": "success", "message": "缓存系统初始化完成"}
  208. except Exception as e:
  209. chat_logger.error(f"初始化缓存失败: {str(e)}")
  210. raise HTTPException(status_code=500, detail=f"初始化缓存失败: {str(e)}")
  211. @router.post("/cache/shutdown")
  212. async def shutdown_cache():
  213. """关闭缓存系统"""
  214. try:
  215. # ✅ 使用异步版本
  216. cleared = await agent_manager.shutdown()
  217. chat_logger.info(f"缓存系统已关闭,清理了 {cleared} 个实例")
  218. return {
  219. "cleared_entries": cleared,
  220. "message": "缓存系统已关闭",
  221. "status": "success",
  222. }
  223. except Exception as e:
  224. chat_logger.error(f"关闭缓存失败: {str(e)}")
  225. raise HTTPException(status_code=500, detail=f"关闭缓存失败: {str(e)}")
  226. @router.post("/admin/refresh-registration")
  227. async def refresh_registration():
  228. """手动刷新注册状态(管理员用)"""
  229. registration_manager.force_refresh()
  230. return {"status": "success", "message": "注册状态已刷新"}
  231. @router.get("/admin/registration-status")
  232. async def get_registration_status():
  233. """获取注册状态(管理员用)"""
  234. status = await registration_manager.check_registration()
  235. status_info = registration_manager.get_status()
  236. return {"is_registered": status, "status_info": status_info}
  237. @router.get("/")
  238. async def root():
  239. registration_status = await registration_manager.check_registration()
  240. base_info = {
  241. "service": "龙嘉软件AI助手API",
  242. "version": "1.0.0",
  243. "registration_status": "active" if registration_status else "expired",
  244. "device_id": get_device_id(),
  245. }
  246. if registration_status:
  247. base_info.update(
  248. {
  249. "endpoints": {
  250. "POST /chat": "聊天",
  251. "GET /health": "健康检查",
  252. "GET /cache/status": "查看agent缓存状态",
  253. "DELETE /cache/clear": "清空agent缓存",
  254. "GET /": "API信息",
  255. }
  256. }
  257. )
  258. else:
  259. base_info.update(
  260. {
  261. "message": "⚠️ 服务注册已过期,部分功能受限",
  262. "available_endpoints": {
  263. "GET /health": "健康检查",
  264. "GET /registration/status": "查看注册状态",
  265. "GET /service/info": "服务完整信息",
  266. "GET /": "API信息",
  267. },
  268. "restricted_endpoints": {"POST /chat": "AI聊天功能(需续费)"},
  269. "support_contact": "请联系管理员续费服务",
  270. }
  271. )
  272. return base_info
  273. @router.post("/image/vector/batch", response_model=List[ImageVectorResponse])
  274. async def batch_calculate_vectors_endpoint(requests: List[ImageVectorRequest]):
  275. """批量计算图片特征向量"""
  276. try:
  277. # 构建请求数据
  278. image_items = []
  279. for req in requests:
  280. image_items.append({
  281. "image": req.image,
  282. "image_id": req.image_id
  283. })
  284. # 调用服务
  285. results = await image_search_service.batch_calculate_vectors(image_items)
  286. # 构建响应
  287. responses = []
  288. for result in results:
  289. response = ImageVectorResponse(
  290. success=result.get("success", False),
  291. image_id=result.get("image_id"),
  292. vector=result.get("vector"),
  293. error=result.get("error")
  294. )
  295. responses.append(response)
  296. return responses
  297. except Exception as e:
  298. chat_logger.error(f"批量计算图片特征向量失败: {str(e)}")
  299. raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")
  300. @router.post("/image/index/build", response_model=BuildIndexResponse)
  301. async def build_index_endpoint(request: BuildIndexRequest):
  302. """构建索引及映射关系"""
  303. try:
  304. # 构建请求数据
  305. image_vectors = []
  306. for item in request.image_vectors:
  307. image_vectors.append({
  308. "image_id": item.image_id,
  309. "vector": item.vector,
  310. "image_name": item.image_name,
  311. "image_path": item.image_path
  312. })
  313. # 调用服务
  314. indexed_count = await image_search_service.build_index(image_vectors)
  315. # 构建响应
  316. response = BuildIndexResponse(
  317. success=True,
  318. indexed_count=indexed_count
  319. )
  320. return response
  321. except Exception as e:
  322. chat_logger.error(f"构建索引失败: {str(e)}")
  323. raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")
  324. @router.post("/image/search", response_model=SearchResponse)
  325. async def search_endpoint(request: SearchRequest):
  326. """搜索相似图片(支持以图搜图和以文搜图)"""
  327. try:
  328. import time
  329. start_time = time.time()
  330. # 处理图片数据
  331. image_bytes = None
  332. if request.image:
  333. try:
  334. if "," in request.image:
  335. base64_str = request.image.split(",", 1)[1]
  336. else:
  337. base64_str = request.image
  338. image_bytes = base64.b64decode(base64_str)
  339. except Exception as e:
  340. chat_logger.error(f"图片解码失败: {e}")
  341. raise HTTPException(400, f"图片格式错误: {str(e)}")
  342. # 调用服务
  343. results = await image_search_service.search(
  344. image_bytes=image_bytes,
  345. text=request.text,
  346. top_k=request.top_k
  347. )
  348. # 计算处理时间
  349. processing_time = time.time() - start_time
  350. # 构建响应
  351. search_results = []
  352. for result in results:
  353. item = SearchResultItem(
  354. image_id=result.get("image_id"),
  355. similarity=result.get("similarity"),
  356. image_name=result.get("image_name"),
  357. image_path=result.get("image_path")
  358. )
  359. search_results.append(item)
  360. response = SearchResponse(
  361. success=True,
  362. results=search_results,
  363. total_count=len(search_results),
  364. processing_time=round(processing_time, 4)
  365. )
  366. return response
  367. except HTTPException:
  368. raise
  369. except Exception as e:
  370. chat_logger.error(f"搜索失败: {str(e)}")
  371. raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")
  372. @router.get("/image/index/status")
  373. async def get_index_status_endpoint():
  374. """获取索引状态"""
  375. try:
  376. status = await image_search_service.get_index_status()
  377. return {
  378. "success": True,
  379. "status": status
  380. }
  381. except Exception as e:
  382. chat_logger.error(f"获取索引状态失败: {str(e)}")
  383. raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")
  384. @router.post("/image/index/clear")
  385. async def clear_index_endpoint():
  386. """清空索引"""
  387. try:
  388. await image_search_service.clear_index()
  389. return {
  390. "success": True,
  391. "message": "索引已清空"
  392. }
  393. except Exception as e:
  394. chat_logger.error(f"清空索引失败: {str(e)}")
  395. raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")