Переглянути джерело

Merge branch 'master' of http://git.longjoe.com:3000/longjoedyy/LongjoeAgent

shuiping150 2 тижнів тому
батько
коміт
ad7e6205fc

+ 1 - 0
api/models.py

@@ -37,6 +37,7 @@ class OCRRequest(BaseModel):
 
     image: str
     type: str
+    file_type: int = 1
 
 
 class MessageCreateBill(BaseModel):

+ 3 - 1
api/routes.py

@@ -62,7 +62,9 @@ async def orc(request: OCRRequest):
             raise HTTPException(400, f"图片格式错误: {str(e)}")
 
         # 2. OCR识别
-        result = await doc_service.pure_ocr(image_bytes=image_bytes)
+        result = await doc_service.pure_ocr(
+            image_bytes=image_bytes, file_type=request.file_type
+        )
 
         # 3. 返回结果
         return result

+ 9 - 3
config/template_config.json

@@ -25,7 +25,11 @@
         },
         "saletask": {
             "field_guidance": {
-                "cusname": [],
+                "cusname": [
+                    "我方/供货商:始终指 华宇,即销售方",
+                    "客户:始终指向我方采购产品的公司,即采购方。在单据中,通常是标题或落款中的购买方",
+                    "例:域程家居-华宇兄弟采购明细,域程家居是客户,而不是华宇"
+                ],
                 "taskdate": [],
                 "requiredate": [],
                 "banktype": [],
@@ -34,7 +38,9 @@
                 "damt": [],
                 "cus_tele": [],
                 "rel_rep": [],
-                "dscrp": [],
+                "dscrp": [
+                    "忽略最底部、大段的、格式化的条款备注"
+                ],
                 "freight": [],
                 "mtrlname": [],
                 "unit": [],
@@ -42,7 +48,7 @@
                 "enprice": [],
                 "rebate": []
             },
-            "additional_rules": ""
+            "additional_rules": "如无法判断内容是客户还是产品,优先判断为产品,'-'号前后内容不拆开. 例如:'Domino多米诺-02 16米 备注 重庆照母山120',Domino多米诺-02 是产品,16米是数量,重庆照母山120是备注.如果出现 谁谁谁要什么之类的,谁谁谁就是客户."
         }
     }
 }

+ 19 - 65
core/agent.py

@@ -22,73 +22,27 @@ dotenv.load_dotenv()
 def create_system_prompt(
     backend_url: str = "", token: str = "", username: str = "default"
 ) -> str:
-    """
-    创建动态的system_prompt,支持参数化配置
-
-    Args:
-        backend_url: 后端API地址
-        token: 访问后端的认证令牌
-        username: 用户名
-    """
-    # 判断token状态
-    if token:
-        token_status = "已配置有效的认证令牌,可以调用后端API获取用户数据"
-    else:
-        token_status = "未提供认证令牌,后端API调用可能受限"
-
-    # 判断backend_url状态
-    if backend_url:
-        backend_status = f"已配置后端地址: {backend_url}"
-    else:
-        backend_status = "未配置后端地址,只能访问知识库"
-
-    system_prompt = f"""你是属于龙嘉软件公司的AI助手,名字叫小龙。
-
-# 当前会话信息
-- 当前用户: {username}
-- 后端服务状态: {backend_status}
-- 认证状态: {token_status}
-现在时间是{datetime.datetime.now().isoformat()}
-
-# 核心能力
-你可为客户提供ERP问题的解决方案,也可回答与龙嘉软件相关的问题。软件面向全球客户,你需按用户提问的语言回答。
-
-# 工作流程
-1. 分析用户问题的意图,提取关键词
-2. 根据意图及关键词调用相应工具
-3. 可以访问知识库工具,也可以调用后端API获取数据
-
-# 后端API使用指南
-{"- 当用户需要查询个人数据、订单信息、账户状态时,可使用后端API" if backend_url and token else "- 由于缺少认证信息,暂时无法调用后端API"}
+    auth_status = "已认证" if token else "未认证"
+    backend_available = "API可用" if backend_url and token else "仅知识库"
+
+    system_prompt = f"""小龙助手(龙嘉软件)- 用户:{username} 认证:{auth_status} 服务:{backend_available}
+
+职责:ERP问题解答,按用户语言回答。
+
+工作流:
+1. 分析问题意图,提取模块关键词
+2. {"优先知识库搜索,需要时调用API" if token else "仅使用知识库搜索"}
+3. 关键词要精准,避免无意义词
+
+回答规则:
+- 知识库优先,找不到时提示"正在学习该问题"
+- {"需要个人数据时验证认证状态" if backend_url else "仅提供知识库支持"}
+- 保护隐私,专业准确
+
 {"- 后端地址: " + backend_url if backend_url else ""}
-{"- API调用会自动包含用户的认证令牌: " + token if token else ""}
-
-# 知识库搜索规则
-- 判断问题所属模块(销售、采购、生产、财务、仓储、权限等)并纳入关键词
-- 文章匹配要精准,例如"销售订单新建权限",拆分为:"销售订单"、"新建"、"权限"
-- 避免使用"的"、"地"、"得"、"了"、"在"等无意义词汇
-- 关键词可以多个,要判断问题属于哪个模块并将其纳入关键字
-- 如果匹配文章太少(少于3篇),尝试以下方法:
-  a) 变更关键字(同义词、近义词)
-  b) 把关键字拆得更细
-  c) 扩大搜索范围(减少关键词数量)
-  d) 重新搜索
-- 获取到文章列表后,用工具获取文章内容然后回答用户问题
-
-# 回答策略
-- 优先使用知识库中的准确信息
-- 如果知识库中有相关文章,结合文章内容进行回答
-- 如果需要实时数据且认证有效,可调用后端API
-- 如果找不到对应知识库文章,向客户说明:"我正在学习这个问题的解决方案,很快就能正式为您服务"
-- 如果用户的问题需要后端数据但认证无效,提示:"查看个人数据需要登录验证,请确保已提供正确的访问令牌"
-
-# 注意事项
-- 保护用户隐私,不在回复中暴露敏感信息
-- 如果API调用失败,提供友好的错误信息
-- 保持回答的专业性和准确性
-- 对于不确定的问题,可以建议用户联系客服或技术支持
+{"- API用户的认证令牌: " + token if token else ""}
+时间:{datetime.datetime.now().strftime("%m-%d %H:%M")}
 """
-    # print(system_prompt)
     return system_prompt
 
 

+ 3 - 2
core/document_processor/document_service.py

@@ -76,13 +76,14 @@ class DocumentProcessingService:
         return result
 
     async def pure_ocr(
-        self, image_bytes: bytes, ocr_options: Dict = None
+        self, image_bytes: bytes, file_type: int = 1, ocr_options: Dict = None
     ) -> Dict[str, Any]:
         """
         扫描图片并返回OCR识别结果
 
         Args:
             image_bytes: 图片字节
+            file_type: 文件类型,1: 图片, 0: PDF
             ocr_options: OCR选项
 
         Returns:
@@ -90,7 +91,7 @@ class DocumentProcessingService:
         """
         # 1. OCR识别
         ocr_result = await self.ocr_service.recognize_image_async(
-            image_bytes, **(ocr_options or {})
+            image_bytes, file_type, **(ocr_options or {})
         )
 
         # 2. 提取文本

+ 1 - 1
cythonize.py

@@ -73,7 +73,7 @@ def compile_project():
 
     # 运行setup.py
     result = subprocess.run(
-        [sys.executable, "setup.py"], capture_output=True, text=True, encoding="utf-8"
+        [sys.executable, "setup.py"], capture_output=True, text=True
     )
 
     if result.returncode != 0:

+ 9 - 6
tools/knowledge_tools.py

@@ -4,20 +4,23 @@ import requests
 import json
 import os
 from .base_tool import html_to_text, get_unique_match_count
+from config.settings import settings
 
 
 @tool
 def get_knowledge_list(filter_words: List[str], match_limit: int = 3) -> str:
-    """获取知识库列表,返回知识库DocID和标题DocName以及关键字keyword的列表,根据关键字及标题,按用户问题筛选出要用到的文章后, 通过DocID获取文章内容,另外会提供访问文章正文的工具,最终返回的文章数最好在10篇以内
+    """根据关键词筛选知识库文章列表
+
     Args:
-        filter_words: 筛选知识库文章的关键词列表,只要符合其中任意一个关键词,就会返回该文章
-        match_limit: 筛选知识库文章的匹配计数,默认值为3,即只要符合3个关键词,就会返回该文章。但如果没有符合的文章或数量太少影响作答,可再次调用该工具,将match_limit减1,直到符合文章,但match_limit不能小于1
+        filter_words: 关键词列表,匹配任一关键词即返回
+        match_limit: 最小匹配数(默认3),无结果时可减少重试(最小1)
+
     Returns:
-        一个包含知识库DocID、标题DocName和关键字keyword的列表,每个元素为字符串,字符串格式为"DocID:DocName|keyword",如果没有keyword,则格式为"DocID:DocName",每个元素之间用换行符分隔
+        文章列表,格式:每行"DocID:DocName|keyword",用于后续获取内容
     """
     print(f"正在查询知识库列表,筛选关键词:{filter_words} 匹配下限:{match_limit}")
 
-    kms_list_url = os.getenv("KMS_LIST_URL")
+    kms_list_url = settings.KMS_LIST_URL  # os.getenv("KMS_LIST_URL")
     payload = {
         "categorycodeList": [],
         "ignoreTypeSub": False,
@@ -79,7 +82,7 @@ def get_knowledge_content(docid: str) -> str:
     """
     print(f"正在获取知识库文章内容,DocID: {docid}")
 
-    kms_view_url = os.getenv("KMS_VIEW_URL")
+    kms_view_url = settings.KMS_VIEW_URL  # os.getenv("KMS_VIEW_URL")
     headers = {
         "Accept": "application/json, text/plain, */*",
         "Content-Type": "application/json",

+ 11 - 1
tools/tool_factory.py

@@ -25,14 +25,24 @@ def get_all_tools() -> List[BaseTool]:
 
     # 扫描工具文件
     tool_files = []
+    # 模式1: 编译前的.py文件
     for file_path in tools_dir.glob("*_tools.py"):
         if file_path.is_file():
             module_name = file_path.stem
             tool_files.append(module_name)
             print(f"📦 发现工具文件: {module_name}")
 
+    # 模式2: 编译后的.pyd文件
+    for file_path in tools_dir.glob("*_tools.cp*.pyd"):
+        if file_path.is_file():
+            # 从文件名中提取模块名,如: ware_tools.cp313-win_amd64.pyd -> ware_tools
+            module_name = file_path.stem.split(".")[0]
+            if module_name not in tool_files:  # 避免重复添加
+                tool_files.append(module_name)
+                print(f"📦 发现编译后工具文件: {module_name}")
+
     if not tool_files:
-        tool_files = ["knowledge_tools", "sale_tools"]
+        tool_files = ["knowledge_tools", "sale_tools", "ware_tools"]
         print("⚠️ 使用默认工具列表")
 
     for module_name in tool_files:

+ 2 - 2
tools/ware_tools.py

@@ -10,11 +10,11 @@ def get_mtrlware_default_config():
             "入参说明": {
                 "backend_url": "后端API地址",
                 "token": "认证令牌",
-                "mtrlname": "物料名称",
+                "mtrlname": "物料名称 或 物料编码, 支持模糊查询",
             },
             "返回值说明": {
                 "格式": "一个包含物料库存数据的字符串",
-                "字段含义": "mtrlcode:物料编码, mtrlname:物料名称, storagename:仓库名称, noallocqty:库存数量, unit:单位, noauditingqty:已开单数量, notauditnoallocqty:未开单数量, pzinfo:配置信息, buydays:采购周期天数",
+                "字段含义": "mtrlcode:物料编码, mtrlname:物料名称, storagename:仓库名称, noallocqty:库存数量, unit:单位, noauditingqty:已开单数量, notauditnoallocqty:未开单数量, pzinfo:配置信息, buydays:采购周期天数,query_status:查询状态",
             },
             "输出格式要求": [
                 "以自然语言描述形式输出,不要使用表格",