Эх сурвалжийг харах

Merge branch 'master' of http://git.yangzhiqiang.tech/jiayq/ai-tagging

jianggs 2 долоо хоног өмнө
parent
commit
eff663be8c
36 өөрчлөгдсөн 467 нэмэгдсэн , 212 устгасан
  1. BIN
      agent/agent-0.1.0-py3-none-any.whl
  2. 0 24
      agent/build.md
  3. 13 5
      agent/config.ini
  4. 16 0
      agent/export.md
  5. 12 0
      agent/install.md
  6. BIN
      agent/logs/aitagging-app.2026-03-10_10-12-47_456980.log.zip
  7. BIN
      agent/logs/aitagging-app.2026-03-11_17-19-07_238711.log.zip
  8. BIN
      agent/logs/aitagging-app.2026-03-12_15-29-18_138302.log.zip
  9. BIN
      agent/logs/aitagging-app.2026-03-13_09-47-02_435341.log.zip
  10. BIN
      agent/logs/aitagging-app.2026-03-16_09-34-01_075272.log.zip
  11. 64 21
      agent/pdm.lock
  12. 8 3
      agent/pyproject.toml
  13. 6 0
      agent/readme.md
  14. 19 5
      agent/src/agent/agent.py
  15. 60 18
      agent/src/agent/api_inner.py
  16. 45 41
      agent/src/agent/api_outter.py
  17. 15 0
      agent/src/agent/core/config.py
  18. 7 4
      agent/src/agent/core/dao.py
  19. 37 14
      agent/src/agent/core/es.py
  20. 20 8
      agent/src/agent/core/vector.py
  21. 30 0
      agent/src/agent/logger.py
  22. 15 3
      agent/src/agent/main.py
  23. 0 18
      agent/src/agent/service_vector.py
  24. 7 0
      agent/tests/test_consuming.py
  25. 15 0
      agent/tests/test_embedding.py
  26. 1 1
      agent/tests/test_generate_reg.py
  27. 8 4
      agent/tests/test_inner_api.py
  28. 1 1
      agent/tests/test_query.py
  29. 2 2
      agent/tests/test_reg.py
  30. 7 0
      agent/tests/test_sync_category.py
  31. 3 2
      agent/tests/test_tagging.py
  32. 0 5
      agent/tests/test_tagging_batch.py
  33. 29 15
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagTagCategoryController.java
  34. 2 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/FastApiService.java
  35. 2 2
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagTagInfoServiceImpl.java
  36. 23 16
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/FastApiServiceImpl.java

BIN
agent/agent-0.1.0-py3-none-any.whl


+ 0 - 24
agent/build.md

@@ -1,24 +0,0 @@
-# 1. 锁定并安装(确保 pdm.lock 最新)
-pdm install --prod
-
-# 2. 导出 requirements.txt
-pdm export -o requirements.txt --prod
-
-# 3. 下载所有 wheel
-rm -rf release
-mkdir -p release
-pip download -r requirements.txt -d release/ --prefer-binary
-
-# 4. 导出pdm wheel
-pip download pdm -d ./release
-
-# 5. 打包
-rm release.tar.gz
-tar -czvf release.tar.gz release/
-
-# 6. 在目标机上安装pdm
-pip install --no-index --find-links ./release pdm
-
-# 7. 修改 PDM 配置,设置 find-links
-pdm config pypi.url file://$(pwd)/release
-pdm install

+ 13 - 5
agent/config.ini

@@ -1,23 +1,31 @@
 [database]
-host = 10.192.72.11
+host = 10.192.72.11  ;数据库地址
 port = 4321
 user = root
 password = admin
 database = ai_tagging
+schema=ai_tagging
 
 [llm]
-model = Qwen3-4B-Instruct
+model = qwen3-32b
 temperature = 0.2
-base_url = http://10.192.72.12:8003/v1
+base_url = http://172.16.40.16:20001/compatible-mode/v1
 api_key = 
 
 [embedding]
 model = Qwen3-Embedding-8B
 base_url = http://10.192.72.11:18081/v1/embeddings
+api_key =
+default_dims = 4096
+enable_config = false
 
 [es]
 url = http://10.192.72.13:9200
-vecotr_dims = 4096
 
 [app]
-top_k = 2
+top_k = 2
+port=9876
+concurrence=3
+
+[logging]
+log_path= logs/aitagging-app.log

+ 16 - 0
agent/export.md

@@ -0,0 +1,16 @@
+# 1. 锁定并安装(确保 pdm.lock 最新)
+pdm install --prod
+
+# 2. 导出 requirements.txt
+pdm export -o requirements.txt --prod
+
+# 3. 下载所有 wheel
+rm -rf dependices
+mkdir -p dependices
+pip download -r requirements.txt -d dependices/ --prefer-binary
+
+# 5. 打包
+rm dependices.tar.gz
+tar -czvf dependices.tar.gz dependices/
+
+

+ 12 - 0
agent/install.md

@@ -0,0 +1,12 @@
+# 1. 离线机器执行,安装依赖包
+pip3 install  --no-index   --find-links=file:///home/agent/dependices   ./dependices/*.whl
+
+# 2. 安装程序包,/home/agent/目录下放agent-xxx.whl
+pip3 install --no-index   --find-links=file:///home/agent/dist   agent
+
+# 3. 设置配置文件
+vim ~/.bashrc
+export TAGGING_AGENT_CONFIG=/home/app/fjnx/agent/config.ini
+
+# 4. 启动
+nohup tagging-agent-server & 

BIN
agent/logs/aitagging-app.2026-03-10_10-12-47_456980.log.zip


BIN
agent/logs/aitagging-app.2026-03-11_17-19-07_238711.log.zip


BIN
agent/logs/aitagging-app.2026-03-12_15-29-18_138302.log.zip


BIN
agent/logs/aitagging-app.2026-03-13_09-47-02_435341.log.zip


BIN
agent/logs/aitagging-app.2026-03-16_09-34-01_075272.log.zip


+ 64 - 21
agent/pdm.lock

@@ -5,7 +5,7 @@
 groups = ["default"]
 strategy = ["inherit_metadata"]
 lock_version = "4.5.0"
-content_hash = "sha256:6733268f6b19c3ef998cc0e93e0c7287e601b0ce3a4de4d63bcb10731f9ab579"
+content_hash = "sha256:b92e7824261b29937eaae4defc966808f8dfd685ab873fc1a9d629b638045af9"
 
 [[metadata.targets]]
 requires_python = "==3.10.*"
@@ -72,6 +72,18 @@ files = [
 ]
 
 [[package]]
+name = "asyncio"
+version = "4.0.0"
+requires_python = ">=3.4"
+summary = "Deprecated backport of asyncio; use the stdlib package instead"
+groups = ["default"]
+marker = "python_version == \"3.10\""
+files = [
+    {file = "asyncio-4.0.0-py3-none-any.whl", hash = "sha256:c1eddb0659231837046809e68103969b2bef8b0400d59cfa6363f6b5ed8cc88b"},
+    {file = "asyncio-4.0.0.tar.gz", hash = "sha256:570cd9e50db83bc1629152d4d0b7558d6451bb1bfd5dfc2e935d96fc2f40329b"},
+]
+
+[[package]]
 name = "certifi"
 version = "2026.1.4"
 requires_python = ">=3.7"
@@ -132,7 +144,7 @@ version = "0.4.6"
 requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 summary = "Cross-platform colored terminal text."
 groups = ["default"]
-marker = "platform_system == \"Windows\" and python_version == \"3.10\""
+marker = "sys_platform == \"win32\" and python_version == \"3.10\" or platform_system == \"Windows\" and python_version == \"3.10\""
 files = [
     {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
     {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
@@ -182,6 +194,18 @@ files = [
 ]
 
 [[package]]
+name = "et-xmlfile"
+version = "2.0.0"
+requires_python = ">=3.8"
+summary = "An implementation of lxml.xmlfile for the standard library"
+groups = ["default"]
+marker = "python_version == \"3.10\""
+files = [
+    {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"},
+    {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"},
+]
+
+[[package]]
 name = "exceptiongroup"
 version = "1.3.1"
 requires_python = ">=3.7"
@@ -470,28 +494,20 @@ files = [
 ]
 
 [[package]]
-name = "numpy"
-version = "2.2.6"
-requires_python = ">=3.10"
-summary = "Fundamental package for array computing in Python"
+name = "loguru"
+version = "0.7.3"
+requires_python = "<4.0,>=3.5"
+summary = "Python logging made (stupidly) simple"
 groups = ["default"]
 marker = "python_version == \"3.10\""
+dependencies = [
+    "aiocontextvars>=0.2.0; python_version < \"3.7\"",
+    "colorama>=0.3.4; sys_platform == \"win32\"",
+    "win32-setctime>=1.0.0; sys_platform == \"win32\"",
+]
 files = [
-    {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"},
-    {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"},
-    {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"},
-    {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"},
-    {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"},
-    {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"},
-    {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"},
-    {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"},
-    {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"},
-    {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"},
-    {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"},
-    {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"},
-    {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"},
-    {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"},
-    {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"},
+    {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"},
+    {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"},
 ]
 
 [[package]]
@@ -517,6 +533,21 @@ files = [
 ]
 
 [[package]]
+name = "openpyxl"
+version = "3.1.5"
+requires_python = ">=3.8"
+summary = "A Python library to read/write Excel 2010 xlsx/xlsm files"
+groups = ["default"]
+marker = "python_version == \"3.10\""
+dependencies = [
+    "et-xmlfile",
+]
+files = [
+    {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"},
+    {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"},
+]
+
+[[package]]
 name = "orjson"
 version = "3.11.7"
 requires_python = ">=3.10"
@@ -886,6 +917,18 @@ files = [
 ]
 
 [[package]]
+name = "win32-setctime"
+version = "1.2.0"
+requires_python = ">=3.5"
+summary = "A small Python utility to set file creation time on Windows"
+groups = ["default"]
+marker = "sys_platform == \"win32\" and python_version == \"3.10\""
+files = [
+    {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"},
+    {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"},
+]
+
+[[package]]
 name = "xxhash"
 version = "3.6.0"
 requires_python = ">=3.7"

+ 8 - 3
agent/pyproject.toml

@@ -1,6 +1,6 @@
 [project]
 name = "agent"
-version = "0.1.0"
+version = "0.1.4"
 description = "Default template for PDM package"
 authors = [
     {name = "jiayongqiang", email = "15936285643@163.com"},
@@ -12,9 +12,11 @@ dependencies = [
     "langchain>=1.2.9",
     "langchain-openai>=1.1.7",
     "elasticsearch==8.9.0",
-    "numpy>=2.2.6",
     "asyncer>=0.0.17",
     "psycopg2-binary>=2.9.11",
+    "loguru>=0.7.3",
+    "openpyxl>=3.1.5",
+    "asyncio>=4.0.0",
 ]
 requires-python = ">=3.10"
 readme = "README.md"
@@ -24,8 +26,11 @@ license = {text = "MIT"}
 build.package-dir = "src"
 distribution = true
 
+[project.scripts]
+tagging-agent-server = "agent.main:main"   # 格式:模块:函数
+
 [tool.pdm.scripts]
-start = "uvicorn agent.main:app --host 0.0.0.0 --port 9876"
+start = "python -m agent.main"
 start-bg = { shell = "nohup pdm run start > uvicorn.log 2>&1 & echo 'Started in background'; echo \"Logs in uvicorn.log\"" }
 
 [build-system]

+ 6 - 0
agent/readme.md

@@ -14,6 +14,12 @@
 - Content-Type: application/json
 - Body:{"tag_ids": ["2","3"]}
 
+## 1.3 按体系同步标签
+- URL:/api/aitag/admin/v1/synchronize_category
+- METHOD: post
+- Content-Type: application/json
+- BODY:{"category_id": xx}
+
 ### 1.3 生成正则表达式接口
 - URL: /api/aitag/admin/v1/generate_reg
 - METHOD:POST

+ 19 - 5
agent/src/agent/agent.py

@@ -1,10 +1,14 @@
 from langchain.chat_models import init_chat_model
-import configparser
 from langchain.agents import create_agent
 from pydantic import BaseModel,Field
+import json
+from agent.core.config import get_config_path
+from agent.logger import logger
+import uuid
+from datetime import datetime
 
-config = configparser.ConfigParser()
-config.read('config.ini')
+config = get_config_path()
+TOP_K = config['app']['top_k']
 
 base_url = config['llm']['base_url']
 api_key_env_var = config['llm']['api_key']
@@ -28,6 +32,7 @@ class Lable(BaseModel):
     tag_code: str = Field(description="标签编码")
     tag_name: str = Field(description="标签名称")
     tag_path: str = Field(description="标签路径")
+    category_id: str = Field(description="标签类别ID")
     desc: str = Field(description="给出简要的理由说明该标签为何被保留或剔除。")
     passr:bool = Field(description="是否保留该标签,true表示保留,false表示剔除。")
 
@@ -82,11 +87,20 @@ async def reflect_check(context: str, labels: list):
         3.不以企业整体行业属性替代对贷款具体内容的判断
         4.所有判断应服务于产业打标的可用性与可解释性
     """
-    print(prompt)
+    uid = uuid.uuid4().hex
+    l1 = datetime.now().isoformat()
+    logger.info(f"Starting reflection check with uuid {uid}. timestamp: {l1}")
     response = await agent.ainvoke({
         "messages": [{"role":"system","content": prompt}]
     },context = {})
-    return response["structured_response"]
+    result = response["structured_response"]
+    result =  [r.dict() for r in result.labels]
+    result = json.dumps(result, ensure_ascii=False)
+    l2 = datetime.now().isoformat()
+    logger.info(f"Reflection check completed with uuid {uid}. timestamp: {l2}, consuming {(datetime.fromisoformat(l2) - datetime.fromisoformat(l1)).total_seconds()} seconds.")
+    logger.info(f"{context} LLM result: {result}")
+
+    return result
 
 class GenerateReg(BaseModel):
     """ 大模型生成的正则表达式 """

+ 60 - 18
agent/src/agent/api_inner.py

@@ -3,18 +3,17 @@ from langchain.chat_models import init_chat_model
 from pydantic import BaseModel, Field
 from agent.core import es, dao
 from agent.core.vector import get_embeddings
-import configparser
 from fastapi import BackgroundTasks
-import logging
 import agent.agent as agent
 from fastapi import BackgroundTasks
-
+from agent.logger import logger
+from agent.core.config import get_config_path
+from typing import Optional
+config = get_config_path()
+TOP_K = config['app']['top_k']
 
 router = APIRouter(prefix="/v1", tags=["平台内部接口"])
 
-config = configparser.ConfigParser()
-config.read('config.ini')
-
 base_url = config['llm']['base_url']
 api_key_env_var = config['llm']['api_key']
 temperature = config['llm']['temperature']
@@ -30,7 +29,8 @@ llm = init_chat_model(
 )
 
 class SynchronizeTagRequest(BaseModel):
-    tag_ids: list[str] = Field(..., description="需要同步的标签编码列表")
+    tag_ids: Optional[list[str]] = Field(None, description="需要同步的标签编码列表")
+    category_id: Optional[str] = Field(None, description="标签分类ID")
 
 def load_tag_2_es(tag_ids: list[str]):
     if not tag_ids:
@@ -48,8 +48,9 @@ def load_tag_2_es(tag_ids: list[str]):
                     tti.tag_path,
                     ttc.category_nm,
                     ttc.category_code,
-                    tti.tag_prompt
-                    from ai_tagging.aitag_tag_info tti left join ai_tagging.aitag_tag_category  ttc 
+                    tti.tag_prompt,
+                    tti.category_id
+                    from aitag_tag_info tti left join aitag_tag_category  ttc 
                     on tti.category_id = ttc.id 
                     where ttc.is_delete=0 and tti.is_delete=0 and ttc.state =  0 and tti.state = 0 and tti.tag_level = ttc.visibility_level
                     and tti.id in ({placeholders})"""    
@@ -63,20 +64,59 @@ def load_tag_2_es(tag_ids: list[str]):
         "tag_remark": label[3],
         "tag_reg": label[4],
         "tag_prompt": label[9],
+        "category_id":label[10],
         "tag_vector": get_embeddings([(label[6] or '')+(label[4] or '')])[0] 
     } for label in labels])
     return labels
 
+def load_category_2_es(category_id: str):
+    if not category_id:
+        return None
+    sql = f"""select 
+                    tti.id,
+                    tti.tag_nm,
+                    tti.tag_code,
+                    tti.tag_remark,
+                    tti.reg,
+                    tti.tag_level,
+                    tti.tag_path,
+                    ttc.category_nm,
+                    ttc.category_code,
+                    tti.tag_prompt,
+                    tti.category_id
+                    from aitag_tag_info tti left join aitag_tag_category  ttc 
+                    on tti.category_id = ttc.id 
+                    where ttc.is_delete=0 and tti.is_delete=0 and ttc.state =  0 and tti.state = 0 and tti.tag_level = ttc.visibility_level
+                    and tti.category_id = %s"""    
+    labels = dao.query(sql, (category_id,))
+    res = es.bulk_upsert([{
+        "id": label[0],
+        "tag_code": label[2],
+        "tag_name": label[1],
+        "tag_path": label[6],
+        "tag_level": label[5],
+        "tag_remark": label[3],
+        "tag_reg": label[4],
+        "tag_prompt": label[9],
+        "category_id":label[10],
+        "tag_vector": get_embeddings([(label[6] or '')+(label[4] or '')])[0] 
+    } for label in labels])
+    return labels
+
+
 @router.post("/synchronize_tag")
 def synchronize_tag(request: SynchronizeTagRequest):
+    logger.info(f"Received request to synchronize tags: {request.tag_ids}")
     load_tag_2_es(request.tag_ids)
     return {
         "code": 200,
         "message": "synchronize_tag successful"
     }
 
+
 @router.post("/delete_tag")
 def delete_tag(request: SynchronizeTagRequest):
+    logger.info(f"Received request to delete tags: {request.tag_ids}")
     for tag_id in request.tag_ids:
         es.delete_document(tag_id)
     return {
@@ -84,6 +124,15 @@ def delete_tag(request: SynchronizeTagRequest):
         "message": "delete_tag successful"
     }   
 
+@router.post("/synchronize_category")
+def synchronize_category(request: SynchronizeTagRequest):
+    logger.info(f"Received request to synchronize category: {request.category_id}")
+    es.delete_category_documents(request.category_id)
+    load_category_2_es(request.category_id)
+    return {
+        "code": 200,
+        "message": "synchronize_category successful"
+    }
 
 class GenerateRegRequest(BaseModel):
     tag_name: str = Field(..., description="标签名称")
@@ -91,20 +140,13 @@ class GenerateRegRequest(BaseModel):
 
 async def generate_and_update_reg(tag_name: str, tag_remark: str):
     reg = await agent.generate_reg(tag_name,tag_remark)
-    logging.info(f"Generated reg for tag {tag_name}: {reg}")
     reg = reg.replace('))(', ')).*(')
-    logging.info(f"Generated reg for tag {tag_name}: {reg}")
-    logging.info(f"Updated reg in database for tag {tag_name}: {reg}")
+    logger.info(f"Generated reg for tag {tag_name}: {reg}")
     return reg
 
 @router.post("/generate_reg")
 async def generate_reg(request: GenerateRegRequest, background_tasks: BackgroundTasks):
-    print(f"Received request to generate reg for tag remark: {request.tag_remark}")
-    # background_tasks.add_task(
-    #     generate_and_update_reg,  # 后台任务函数
-    #     tag_name=request.tag_name,
-    #     tag_remark=request.tag_remark
-    # )
+    logger.info(f"Received request to generate reg for tag: {request.tag_name}, remark: {request.tag_remark}")
     reg = await generate_and_update_reg(request.tag_name, request.tag_remark)
     return {
         "code": 200,

+ 45 - 41
agent/src/agent/api_outter.py

@@ -1,10 +1,8 @@
-import configparser
 import re
 from fastapi import APIRouter , UploadFile 
 from pydantic import BaseModel,Field
 from requests import request
 from agent.core.sign_check import check
-import logging
 import agent.core.dao as dao
 from asyncer import asyncify
 from agent.core.vector import get_embeddings
@@ -15,13 +13,16 @@ import uuid
 from fastapi import BackgroundTasks
 from typing import Optional
 import json
+from agent.logger import logger
+from agent.core.config import get_config_path
+import asyncio
 
-router = APIRouter(prefix="/v1", tags=["AI Tagging"])
-
-config = configparser.ConfigParser()
-config.read('config.ini')
+config = get_config_path()
 TOP_K = config['app']['top_k']
+CONCURRENCE = int(config['app']['concurrence'])
+background_semaphore = asyncio.Semaphore(CONCURRENCE)
 
+router = APIRouter(prefix="/v1", tags=["AI Tagging"])
 
 class TaggingRequest(BaseModel):
     app_id:  Optional[str] = Field(None, description="应用ID")
@@ -35,7 +36,7 @@ async def execute_reg(log_id:str,tag_category_id:str,phrase: str)-> list:
     sql = f"""select 
                     tti.id,
                     tti.reg
-                    from ai_tagging.aitag_tag_info tti left join ai_tagging.aitag_tag_category  ttc 
+                    from aitag_tag_info tti left join aitag_tag_category  ttc 
                     on tti.category_id = ttc.id 
                     where ttc.is_delete=0 and tti.is_delete=0 and ttc.state =  0 and tti.state = 0 and tti.tag_level = ttc.visibility_level
                     """    
@@ -47,78 +48,80 @@ async def execute_reg(log_id:str,tag_category_id:str,phrase: str)-> list:
     try:
         for label in labels:
             reg = label[1]
-            logging.info(f"Executing regex for label_id {label[0]}")
             if reg is not None:
                 pattern = re.compile(reg, re.VERBOSE)
+                logger.info(f"Executing regex for label_id {label[0]}: {reg}")
                 if pattern.match(phrase):
+                    logger.info(f"Executing regex for label_id {label[0]}: {reg} true")
                     result.append(label[0])
             else:
                 result.append(label[0])
     except Exception as e:
-        logging.error(f"Regex execution failed: {e}")
-    logging.info(f"Regex filtering candidates: {result}")
+        logger.error(f"Regex execution failed: {e}")
+    logger.info(f"Regex filtering candidates: {result}")
     dao.execute(
-            """UPDATE ai_tagging.aitag_tag_log SET reg_result = %s WHERE id = %s""",
+            """UPDATE aitag_tag_log SET reg_result = %s WHERE id = %s""",
             (str(result), log_id)
         )
-    logging.info(f"Updated reg_result for log_id {id}")
+    logger.info(f"[{log_id}] Regex filtering result: {result}")
     return result
 
 def vector_similarity_search(phrase: str, ids:list)-> list:
+    logger.info("Starting vector similarity search...")
     # 这里应该调用向量数据库进行相似度检索,返回相关标签id列表
     query = get_embeddings([phrase])[0]
     results = hybrid_search(ids, query, top_k=TOP_K)
     # return [{"id": r["_id"], "score": r["_score"], "tag_prompt": r["_source"]["tag_prompt"],"tag_name": r["_source"]["tag_name"],"tag_code": r["_source"]["tag_code"]} for r in results]
-    return [{"id": r["_id"], "tag_remark":r["_source"]["tag_remark"], "tag_prompt": r["_source"]["tag_prompt"],"tag_name": r["_source"]["tag_name"],"tag_code": r["_source"]["tag_code"],"tag_path": r["_source"]["tag_path"]   } for r in results]
+    r = [{"id": r["_id"], "tag_remark":r["_source"]["tag_remark"], "tag_prompt": r["_source"]["tag_prompt"],"tag_name": r["_source"]["tag_name"],"tag_code": r["_source"]["tag_code"],"tag_path": r["_source"]["tag_path"],"category_id": r["_source"]["category_id"]   } for r in results]
+    logger.info(f"{phrase} Vector search result: {r}")
+    return r
 
 def init_tag_log(request: TaggingRequest):
     id = uuid.uuid4().hex
     # 保证business_attr的唯一性,如果已经存在相同business_attr且状态为0(处理中)的记录,则将is_delete设为1(已删除),然后插入新记录
     dao.execute(
-        """UPDATE ai_tagging.aitag_tag_log SET is_delete = 1 WHERE business_attr = %s""",
+        """UPDATE aitag_tag_log SET is_delete = 1 WHERE business_attr = %s""",
         (request.business_attr,)
     )
     # 业务编号如果以test开头,则tag_scope = 1,否则都是0
     tag_scope = 1 if request.business_attr.startswith("test") else 0
     dao.execute(
-        """INSERT INTO ai_tagging.aitag_tag_log (id,app_id, insert_time, business_attr, phrase, state, tag_scope) VALUES (%s, %s, %s, %s, %s, %s, %s)""",
+        """INSERT INTO aitag_tag_log (id,app_id, insert_time, business_attr, phrase, state, tag_scope) VALUES (%s, %s, %s, %s, %s, %s, %s)""",
         (id,request.app_id, datetime.now(), request.business_attr, request.phrase, 0, tag_scope)
     )
     return id
 
 def update_tag_log(id:str, result:str):
     dao.execute(
-            """UPDATE ai_tagging.aitag_tag_log SET state = %s, result = %s WHERE id = %s""",
-            (1, result, id)
+            """UPDATE aitag_tag_log SET state = %s, result = %s, ai_result_endtime = %s WHERE id = %s""",
+            (1, result, datetime.now(), id)
         )
 
 async def run_ai_pipeline(log_id: str, tag_category_id: str, phrase: str):
     try:
-        # step1: 正则过滤
-        result = await execute_reg(log_id,tag_category_id,phrase)
-        logging.info(f"[{log_id}] Regex filtering result: {result}")
-
-        # step2: 向量检索
-        if result:
+        async with background_semaphore:
+            # step1: 正则过滤
+            result = await execute_reg(log_id,tag_category_id,phrase)
+            # step2: 向量检索
             result = vector_similarity_search(phrase, result)
-            logging.info(f"[{log_id}] Vector search result: {result}")
-
-        # step3: LLM 打标
-        if result:
-            result = await reflect_check(phrase, result)
-            result =  [r.dict() for r in result.labels]
-            result = json.dumps(result, ensure_ascii=False)
-            logging.info(f"[{log_id}] LLM result: {result}")
-
-        # step4: 更新数据库
-        update_tag_log(log_id, str(result))
+            # step3: LLM 打标
+            if result:
+                try:
+                    result = await reflect_check(phrase, result)
+                except Exception as e:
+                    logger.error(f"LLM reflection check failed: {e}")
+                    result = None
+            # step4: 更新数据库
+            # 如果result是个空集合,插入None
+            update_tag_log(log_id, result if result else None)
             
     except Exception as e:
-        logging.error(f"[{log_id}] Pipeline failed: {e}")
+        logger.error(f"[{log_id}] Pipeline failed: {e}")
+        update_tag_log(log_id, None)
 
 @router.post("/tagging")
 async def ai_tagging(request: TaggingRequest,background_tasks: BackgroundTasks):
-    logging.info(f"app_id: {request.app_id}, timestamp: {request.timestamp}, sign: {request.sign}, business_attr: {request.business_attr}, phrase: {request.phrase}")
+    logger.info(f"app_id: {request.app_id}, timestamp: {request.timestamp}, sign: {request.sign}, business_attr: {request.business_attr}, phrase: {request.phrase}")
     # 数据库中插入一条记录,记录请求的app_id、timestamp、business_attr、phrase等信息,状态设为“处理中”,后续步骤完成后更新状态和结果
     id = init_tag_log(request)
     # 执行异步任务
@@ -128,7 +131,7 @@ async def ai_tagging(request: TaggingRequest,background_tasks: BackgroundTasks):
         tag_category_id=request.tag_category_id,    
         phrase=request.phrase
     )
-    logging.info(f"Started background task for log_id: {id}")
+    logger.info(f"Started background task for log_id: {id}")
     return {
         "code": 200,
         "message": "submit successful"
@@ -136,10 +139,11 @@ async def ai_tagging(request: TaggingRequest,background_tasks: BackgroundTasks):
 
 @router.get("/query")
 def query(business_attr: str):
+    logger.info(f"Querying tag log for business_attr: {business_attr}")
     # 查询指定business_attr对应的标签信息
-    sql = """SELECT * FROM ai_tagging.aitag_tag_log WHERE business_attr = %s and is_delete = 0"""
+    sql = """SELECT * FROM aitag_tag_log WHERE business_attr = %s and is_delete = 0"""
     result = dao.query_dict(sql, (business_attr,))
-    print(result)
+    logger.info(f"Query result: {result}")
     return {"code": 200, "message": "AI Query Endpoint", "data": result[0] if result else None}
 
 class FeedbackRequest(BaseModel):
@@ -154,10 +158,10 @@ class FeedbackRequest(BaseModel):
 
 @router.post("/feedback")
 def ai_feedback(feedback_request: FeedbackRequest):
-    logging.info(f"Received feedback request: {feedback_request}")
+    logger.info(f"Received feedback request: {feedback_request}")
     # 这里将用户的反馈信息保存到数据库中aitag_tag_log,供后续分析和模型优化使用
     dao.execute(
-        """update ai_tagging.aitag_tag_log set feedback = %s, feedback_result = %s, feedback_time = %s, feedback_user_id = %s, feedback_user_nm = %s, contract_no = %s, feedback_user_org = %s, feedback_user_endpoint = %s, state = %s where business_attr = %s""",   
+        """update aitag_tag_log set feedback = %s, feedback_result = %s, feedback_time = %s, feedback_user_id = %s, feedback_user_nm = %s, contract_no = %s, feedback_user_org = %s, feedback_user_endpoint = %s, state = %s where business_attr = %s""",   
         (feedback_request.feedback, feedback_request.feedback_result, datetime.now(), feedback_request.user_id, feedback_request.user_nm, feedback_request.contract_no, feedback_request.user_org, feedback_request.user_endpoint, 2, feedback_request.business_attr)
     )
     return {"code": 200, "message": "Feedback received successfully"}

+ 15 - 0
agent/src/agent/core/config.py

@@ -0,0 +1,15 @@
+import os
+import configparser
+
+from uvicorn import config
+
+def get_config_path():
+    config = configparser.ConfigParser()
+    # 1. 优先读取环境变量指定的配置路径
+    config_path = os.getenv("TAGGING_AGENT_CONFIG")  
+    if config_path and os.path.exists(config_path):
+        config.read(config_path)
+    else:
+        config.read("config.ini")
+        
+    return config

+ 7 - 4
agent/src/agent/core/dao.py

@@ -1,16 +1,18 @@
 import psycopg2
 from psycopg2 import pool
-import configparser
+
 from contextlib import contextmanager
 
-config = configparser.ConfigParser()
-config.read('config.ini')
+from agent.core.config import get_config_path
+config = get_config_path()
+TOP_K = config['app']['top_k']
 
 host = config['database']['host']
 port = int(config['database']['port'])
 user = config['database']['user']
 password = config['database']['password']
 database = config['database']['database']
+schema = config['database']['schema']
 
 pool = psycopg2.pool.SimpleConnectionPool(
     20, 50,
@@ -18,7 +20,8 @@ pool = psycopg2.pool.SimpleConnectionPool(
     port=port,
     database=database,
     user=user,
-    password=password
+    password=password,
+    options=f'-c search_path={schema}'
 )
 
 @contextmanager

+ 37 - 14
agent/src/agent/core/es.py

@@ -1,14 +1,12 @@
 from elasticsearch import Elasticsearch,helpers
-import configparser
-import logging
+from agent.logger import logger
 
-from numpy.ma import count
-
-config = configparser.ConfigParser()
-config.read('config.ini')
+from agent.core.config import get_config_path
+config = get_config_path()
+TOP_K = config['app']['top_k']
 
 url = config['es']['url']
-DIMS = int(config['es']['vecotr_dims'])
+DIMS = int(config['embedding']['default_dims'])
 
 es = Elasticsearch(
     hosts=[url],
@@ -30,6 +28,7 @@ if not es.indices.exists(index=INDEX_NAME):
                 "tag_remark": {"type": "text"},
                 "tag_reg": {"type": "text"},
                 "tag_prompt": {"type": "text"},
+                "category_id": {"type": "text"},
                 "tag_vector": {
                     "type": "dense_vector",
                     "dims": DIMS,
@@ -50,6 +49,16 @@ def delete_document(doc_id):
         index=INDEX_NAME,
         id=doc_id
     )
+
+def delete_category_documents(category_id):
+    query = {
+        "query": {
+            "term": {
+                "category_id": category_id
+            }
+        }
+    }
+    es.options(request_timeout=60).delete_by_query(index=INDEX_NAME, body=query)
     
 def search_documents(query):
     response = es.search(
@@ -78,18 +87,18 @@ def bulk_upsert(documents):
         for doc in documents
     ]
     try:
-        helpers.bulk(es, actions,chunk_size=500, request_timeout=60)
+        success, errors = helpers.bulk(es, actions,chunk_size=500, request_timeout=60,raise_on_error=False)
+        logger.info(f"Bulk upsert completed: {success} successful, {len(errors)} errors")
+        logger.error(f"Bulk upsert errors: {errors}")
     except Exception as e:
-        print(f"Bulk upsert failed: {e}")
+        logger.error(f"Bulk upsert failed: {e}")
         for error in e.errors:
-            print(error)
+            logger.error(f"Error: {error}")
         raise
 
 def hybrid_search( target_doc_ids, query_vector, top_k=2):
-    logging.info(f"Performing hybrid search with target_doc_ids: {target_doc_ids}, query_vector: {len(query_vector)}, top_k: {top_k}")
-    response = es.search(
-        index=INDEX_NAME,
-        knn={
+    logger.info(f"Performing hybrid search with target_doc_ids: {target_doc_ids}, query_vector: {len(query_vector)}, top_k: {top_k}")
+    knn={
             "field": "tag_vector",
             "query_vector": query_vector,
             "k": top_k,           # 不超过候选集大小
@@ -100,10 +109,24 @@ def hybrid_search( target_doc_ids, query_vector, top_k=2):
                 }
             }
         }
+    if not target_doc_ids:
+        knn={
+            "field": "tag_vector",
+            "query_vector": query_vector,
+            "k": top_k,           # 不超过候选集大小
+            "num_candidates": 100
+        }
+    print(f"Constructed knn query: {knn}")
+    response = es.search(
+        index=INDEX_NAME,
+        knn=knn
     )
     return response["hits"]["hits"]
 
 if __name__ == "__main__":
     results = search_all()
+    for r in results:
+        # 排除 tag_vector 字段的输出
+        print({k: v for k, v in r["_source"].items() if k != "tag_vector"})
     print(f"Search results: {len(results)}")
     

+ 20 - 8
agent/src/agent/core/vector.py

@@ -1,23 +1,34 @@
 import requests
 import json
-import configparser
-
-config = configparser.ConfigParser()
-config.read('config.ini')
+from agent.logger import logger
+from agent.core.config import get_config_path
+config = get_config_path()
+TOP_K = config['app']['top_k']
 
 model = config['embedding']['model']
 base_url = config['embedding']['base_url']
-
+api_key = config['embedding']['api_key']
+dims = config["embedding"]["default_dims"]
+enable_config = config["embedding"]["enable_config"]
+# 将enable_config转换为布尔值
+if isinstance(enable_config, str):
+    enable_config = enable_config.lower() in ['true', '1', 'yes']
 def get_embeddings(texts):
     url = base_url
     headers = {
-        "Authorization": "1",
+        "Authorization": api_key,
         "Content-Type": "application/json"
     }
     data = {
         "input": texts,
-        "model": model
+        "model": model,
     }
+    if enable_config:
+        data = {
+            "input": texts,
+            "model": model,
+            "dimensions": dims
+        }
     response = requests.post(url, headers=headers, data=json.dumps(data))
     embeddings = response.json()['data']
     return [e['embedding'] for e in embeddings]
@@ -26,4 +37,5 @@ if __name__ == "__main__":
     texts = ["Hello, world!", "How are you?"]
     embeddings = get_embeddings(texts)
     # print(embeddings)
-    print(f"Embedding for '{texts[0]}': {embeddings[0][:5]}...")  # 打印前5个维度的值
+    logger.info(f"Embedding for '{texts[0]}': len({embeddings[0]})") 
+    logger.info(f"Embedding for '{texts[0]}': {embeddings[0][:5]}...")  # 打印前5个维度的值

+ 30 - 0
agent/src/agent/logger.py

@@ -0,0 +1,30 @@
+from loguru import logger
+import sys
+from agent.core.config import get_config_path
+config = get_config_path()
+log_path = config['logging']['log_path']
+
+logger.remove()
+
+logger.add(
+    sys.stderr,
+    format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
+    level="INFO"
+)
+
+
+logger.add(
+    log_path,
+    rotation="00:00",        # 每天午夜轮转
+    retention="30 days",      # 保留 7 天
+    compression="zip",       # 轮转后压缩(可选)
+    encoding="utf-8",
+    level="DEBUG",
+    format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
+    enqueue=True,            # 异步安全(多进程/线程安全)
+    backtrace=True,
+    diagnose=True
+)
+
+# 导出 logger 实例供其他模块使用
+__all__ = ["logger"]

+ 15 - 3
agent/src/agent/main.py

@@ -1,17 +1,29 @@
 from fastapi import FastAPI,APIRouter   
 from agent.api_outter import router as outter_router
 from agent.api_inner import router as inner_router  
-import logging
-logging.basicConfig(level=logging.INFO, force=True,format='%(asctime)s - %(levelname)s - %(message)s')
-logging.info("app starting!")
+from agent.logger import logger
+import uvicorn
+logger.info("ai-tagging starting!")
 
 api_router = APIRouter()
 api_router.include_router(inner_router,prefix="/admin")
 api_router.include_router(outter_router,prefix="")
 
+from agent.core.config import get_config_path
+config = get_config_path()
+# 如果没有配置port,默认使用9876
+port = int(config['app'].get('port', 9876))
+
 app = FastAPI(
     title="AI-TAGGING",
     description="智能打标系统", 
     version="0.1.0"
 )
 app.include_router(api_router, prefix="/api/aitag")
+print('API routes initialized')
+def main():
+    print("Starting AI Tagging Service...")
+    uvicorn.run(app, host="0.0.0.0", port=port)
+
+if __name__ == "__main__":
+    main()

+ 0 - 18
agent/src/agent/service_vector.py

@@ -1,18 +0,0 @@
-
-import time
-
-# EMBEDDING_MODEL = 'models/bge-small-zh-v1.5'
-# transformer = SentenceTransformer(EMBEDDING_MODEL)
-
-# def compute_embedding(text: str):
-#     embedding = transformer.encode(text)
-#     return embedding
-
-# if __name__ == "__main__":
-#     text = "你好,世界!"
-#     l1 = time.time()
-#     embedding = compute_embedding([text,"hello,world"])
-#     l2 = time.time()
-#     print(f"Time taken: {l2 - l1} seconds")
-#     print(embedding)
-#     print(f"Embedding dimension: {len(embedding)}")

+ 7 - 0
agent/tests/test_consuming.py

@@ -0,0 +1,7 @@
+from datetime import datetime
+from agent.logger import logger
+l1 = datetime.now().isoformat()
+logger.info(f"Starting reflection check with uuid . timestamp: {l1}")
+
+l2 = datetime.now().isoformat()
+logger.info(f"Reflection check completed with uuid . timestamp: {l2}, consuming {(datetime.fromisoformat(l2) - datetime.fromisoformat(l1)).total_seconds()} seconds.")

+ 15 - 0
agent/tests/test_embedding.py

@@ -0,0 +1,15 @@
+import requests
+import json
+url = "http://10.192.72.11:18081/v1/embeddings"
+headers = {
+    "Authorization": "1",
+    "Content-Type": "application/json"
+}
+data = {
+    "input": ["Hello, world!"],
+    "model": "Qwen3-Embedding-8B"
+}
+response = requests.post(url, headers=headers, data=json.dumps(data))
+# embeddings = response.json()['data']
+# print([e['embedding'] for e in embeddings])
+print(response.text)

+ 1 - 1
agent/tests/test_generate_reg.py

@@ -8,5 +8,5 @@ res = requests.post("http://10.192.72.13:9876/api/aitag/admin/v1/generate_reg",
 #     "tag_name": "智能电网",
 #     "tag_remark": "指以智能电网技术为核心,建设或改造电力系统,提升电力系统智能化水平和运行效率的项目。"
 # })
-print(res.json())
+print(res.text)
 

+ 8 - 4
agent/tests/test_inner_api.py

@@ -90,9 +90,13 @@ res = requests.post("http://localhost:9876/api/aitag/admin/v1/synchronize_tag",
 })
 print(res.text)
 
-# res = requests.post("http://localhost:9876/api/aitag/admin/v1/delete_tag", json={
-#     "tag_ids": ["20260200083"]
-# })
+res = requests.post("http://localhost:9876/api/aitag/admin/v1/delete_tag", json={
+    "tag_ids": ["20260200083"]
+})
+print(res.text)
 
-# print(res.text)
+res = requests.post("http://10.192.72.13:9876/api/aitag/admin/v1/synchronize_category", json={
+    "category_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
+})
+print(res.text)
 

+ 1 - 1
agent/tests/test_query.py

@@ -1,4 +1,4 @@
 import requests
 
-result = requests.get("http://localhost:9876/api/aitag/v1/query?business_attr=test_attr")
+result = requests.get("http://10.192.72.13:9876/api/aitag/v1/query?business_attr=17f02e5c92cb41c6ae3e835bcefb3523")
 print(result.text)

+ 2 - 2
agent/tests/test_reg.py

@@ -1,14 +1,14 @@
 import re
 
 regstr = """
-^(?!.*(服装|纺织|食品|农业|医疗)).*(海洋.{0,6}(防护|防腐|防污|防附着|防渗透)|防护.{0,6}海洋|防腐.{0,6}(材料|涂料)|防污.{0,6}(材料|涂层)|船舶.{0,3}(防腐|防污)|海工.{0,3}(材料|防护))
+^(?!.*(淡水|池塘|内河|江河|湖泊|水库|观赏鱼|锦鲤|龙鱼|花鸟)).*?(?:海.{0,100}(养殖|鲍|参|虾|蟹|贝|蚝|蛎|蛤|扇贝|鲍鱼|海参|对虾|虾|贻贝|牡蛎|蛏|螺|紫菜|海带)|(养殖|鲍|参|虾|蟹|贝|蚝|蛎|蛤|扇贝|鲍鱼|海参|对虾|蟹|虾|贻贝|牡蛎|蛏|螺|紫菜|海带).{0,100}海)
 """
 
 test_cases = [
     "防腐材料",       
     "海洋防污涂层",     
     "船舶防腐",        
-    "职业:个体;投向:船体防腐;用途:海洋防污涂层", 
+    "职业:水产养殖人员 投向:内陆养殖 用途:养殖鲍鱼", 
     "材料",           
     "医疗防护服",      
 ]

+ 7 - 0
agent/tests/test_sync_category.py

@@ -0,0 +1,7 @@
+import requests
+
+res = requests.post("http://10.192.72.13:9876/api/aitag/admin/v1/synchronize_category", json={
+    "category_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
+})
+print(res.text)
+

+ 3 - 2
agent/tests/test_tagging.py

@@ -3,12 +3,13 @@ import logging
 logging.basicConfig(level=logging.INFO, force=True,format='%(asctime)s - %(levelname)s - %(message)s')
 logging.info("app starting!")
 
-res = requests.post("http://localhost:9876/api/aitag/v1/tagging", json={
+res = requests.post("http://10.192.72.13:9876/api/aitag/v1/tagging", json={
     # "app_id": "test_app",
     # "timestamp": 1234567890,
     # "sign": "test_sign",
     "business_attr": "test_attr",
-    "phrase": "职业:水产养殖人员 投向:海水养殖 用途:个人经营"
+    "phrase": "职业:水产养殖人员 投向:内陆养殖 用途:养殖鲍鱼"
 })
+
 logging.info(res.text)
 

+ 0 - 5
agent/tests/test_tagging_batch.py

@@ -1,8 +1,5 @@
 import requests
-import logging
 import uuid
-logging.basicConfig(level=logging.INFO, force=True,format='%(asctime)s - %(levelname)s - %(message)s')
-logging.info("app starting!")
 
 #批量读取test_data.txt中的测试用例,并调用接口进行测试
 with open("tests/test_data.txt", "r", encoding="utf-8") as f:
@@ -15,11 +12,9 @@ with open("tests/test_data.txt", "r", encoding="utf-8") as f:
         if len(phrase) == 3:
             phrase = "职业:"+phrase[0]+" "+"投向:"+phrase[1]+" "+"用途:"+phrase[2]
 
-        logging.info("测试用例: "+phrase)
         res = requests.post("http://10.192.72.13:9876/api/aitag/v1/tagging", json={
             "business_attr": uuid.uuid4().hex,
             "phrase": phrase
         })
-        logging.info(res.text)
 
 

+ 29 - 15
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagTagCategoryController.java

@@ -6,6 +6,7 @@ import cn.com.yusys.yusp.domain.entity.AitagTagCategory;
 import cn.com.yusys.yusp.domain.vo.AitagTagCategoryVo;
 import cn.com.yusys.yusp.model.Result;
 import cn.com.yusys.yusp.service.AitagTagCategoryService;
+import cn.com.yusys.yusp.service.FastApiService;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
@@ -21,14 +22,25 @@ public class AitagTagCategoryController {
     @Autowired
     private AitagTagCategoryService aiTagCategoryService;
 
+    @Autowired
+    private FastApiService fastApiService;
+
+    // 定义一个系统默认页大小,防止前端不传时查询数据量过大
+    private static final int DEFAULT_PAGE_SIZE = 10;
+    private static final int MAX_PAGE_SIZE = 100;
+
     @ApiOperationType("标签体系列表")
     @GetMapping("/list")
     public Result<List<AitagTagCategoryVo>> listCategories(
             @RequestParam(required = false) String categoryNm,
             @RequestParam(defaultValue = "1") int page,
-            @RequestParam(defaultValue = "5") int size) {
+            @RequestParam(value = "pageSize", required = false) Integer size) {
+
+        // 处理 pageSize:如果前端没传,使用默认值;如果传了,可以使用前端值
+        int actualSize = (size == null || size <= 0) ? DEFAULT_PAGE_SIZE : Math.min(size, MAX_PAGE_SIZE);
+
         try {
-            Page<AitagTagCategoryVo> pageResult = aiTagCategoryService.listCategories(categoryNm, page, size);
+            Page<AitagTagCategoryVo> pageResult = aiTagCategoryService.listCategories(categoryNm, page, actualSize);
             return Result.pageSuccess(pageResult.getRecords(), pageResult.getTotal());
         } catch (Exception e) {
             return Result.error("500", "分页查询失败:" + e.getMessage());
@@ -40,6 +52,7 @@ public class AitagTagCategoryController {
     public Result<AitagTagCategory> createCategory(@Validated @RequestBody AitagTagCategoryCreateDto dto) {
         try {
             AitagTagCategory category = aiTagCategoryService.createCategory(dto);
+            fastApiService.categorySync(category.getId());
             return Result.success(category);
         } catch (Exception e) {
             return Result.error("500", "新建标签体系失败:" + e.getMessage());
@@ -51,6 +64,7 @@ public class AitagTagCategoryController {
     public Result<AitagTagCategory> updateCategory(@Validated @RequestBody AitagTagCategoryUpdateDto dto) {
         try {
             AitagTagCategory category = aiTagCategoryService.updateCategory(dto);
+            fastApiService.categorySync(category.getId());
             return Result.success(category);
         } catch (Exception e) {
             return Result.error("500", "编辑标签体系失败:" + e.getMessage());
@@ -62,6 +76,7 @@ public class AitagTagCategoryController {
     public Result<Void> enableCategory(@RequestParam String id) {
         try {
             aiTagCategoryService.enableCategory(id);
+            fastApiService.categorySync(id);
             return Result.success();
         } catch (Exception e) {
             return Result.error("500", "启用标签体系失败:" + e.getMessage());
@@ -73,6 +88,7 @@ public class AitagTagCategoryController {
     public Result<Void> disableCategory(@RequestParam String id) {
         try {
             aiTagCategoryService.disableCategory(id);
+            fastApiService.categorySync(id);
             return Result.success();
         } catch (Exception e) {
             return Result.error("500", "停用标签体系失败:" + e.getMessage());
@@ -94,17 +110,18 @@ public class AitagTagCategoryController {
     @GetMapping("/enablelist")
     public Result<List<AitagTagCategoryVo>> listEnabledCategories(
             @RequestParam(defaultValue = "1") int page,
-            @RequestParam(defaultValue = "5") int size) {
+            @RequestParam(value = "pageSize", required = false) Integer size) { // 修改点:改为 Integer,移除 defaultValue
+
+        int actualSize = (size == null || size <= 0) ? DEFAULT_PAGE_SIZE : Math.min(size, MAX_PAGE_SIZE);
+
         try {
-            Page<AitagTagCategoryVo> pageResult = aiTagCategoryService.listEnabledCategories(page, size);
+            Page<AitagTagCategoryVo> pageResult = aiTagCategoryService.listEnabledCategories(page, actualSize);
             return Result.pageSuccess(pageResult.getRecords(), pageResult.getTotal());
         } catch (Exception e) {
             return Result.error("500", "分页查询失败:" + e.getMessage());
         }
     }
 
-
-
     @ApiOperationType("标签体系详情")
     @GetMapping("/detail/{id}")
     public Result<AitagTagCategoryVo> getCategoryDetail(@PathVariable String id) {
@@ -116,22 +133,19 @@ public class AitagTagCategoryController {
         }
     }
 
-
-    /**
-     * 外部页面获取已启用的标签体系列表,无需token
-     *
-     * @return ResultDto
-     */
     @ApiOperationType("外部标签体系")
     @GetMapping("/enablelistnoauth")
     public Result<List<AitagTagCategoryVo>> listEnabledCategoriesNoAuth(
             @RequestParam(defaultValue = "1") int page,
-            @RequestParam(defaultValue = "5") int size) {
+            @RequestParam(value = "pageSize", required = false) Integer size) { // 修改点:改为 Integer,移除 defaultValue
+
+        int actualSize = (size == null || size <= 0) ? DEFAULT_PAGE_SIZE : Math.min(size, MAX_PAGE_SIZE);
+
         try {
-            Page<AitagTagCategoryVo> pageResult = aiTagCategoryService.listEnabledCategories(page, size);
+            Page<AitagTagCategoryVo> pageResult = aiTagCategoryService.listEnabledCategories(page, actualSize);
             return Result.pageSuccess(pageResult.getRecords(), pageResult.getTotal());
         } catch (Exception e) {
             return Result.error("500", "分页查询失败:" + e.getMessage());
         }
     }
-}
+}

+ 2 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/FastApiService.java

@@ -28,4 +28,6 @@ public interface FastApiService {
      * @return 响应结果
      */
     AiTaggingResponseVo feedback(AiTaggingFeedbackRequestDto request);
+
+    AiTaggingResponseVo categorySync(String categoryId);
 }

+ 2 - 2
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagTagInfoServiceImpl.java

@@ -126,7 +126,7 @@ public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagT
         checkTagCodeDuplicate(aitagTagInfo.getTagCode());
         if(!StringUtils.isBlank(parentId)){
             LambdaQueryWrapper<AitagTagInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
-            queryWrapper.eq(AitagTagInfoEntity::getParentId,parentId);
+            queryWrapper.eq(AitagTagInfoEntity::getId,parentId);
             queryWrapper.eq(AitagTagInfoEntity::getIsDelete,TAG_UNDELETED);
             List<AitagTagInfoEntity> aitagTagInfoEntities = this.baseMapper.selectList(queryWrapper);
             if(!aitagTagInfoEntities.isEmpty()){
@@ -200,7 +200,7 @@ public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagT
         aitagTagInfoEntity.setTagVersion(Integer.parseInt(oldTagInfo.getTagVersion())+1+"");
         if(!StringUtils.isBlank(aitagTagInfoEntity.getParentId())){
             AitagTagInfoEntity parentTagInfo = this.baseMapper.selectById(aitagTagInfoEntity.getParentId());
-            if(parentTagInfo !=null){
+            if(parentTagInfo !=null && TAG_UNDELETED.equals(parentTagInfo.getIsDelete())){
                 aitagTagInfoEntity.setTagPath(parentTagInfo.getTagPath()+"/"+aitagTagInfoEntity.getTagNm());
                 aitagTagInfoEntity.setTagLevel(parentTagInfo.getTagLevel()+1);
                 if(aitagTagInfoEntity.getTagLevel() > 4){

+ 23 - 16
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/FastApiServiceImpl.java

@@ -1,26 +1,17 @@
 package cn.com.yusys.yusp.service.impl;
 
-import cn.com.yusys.yusp.commons.util.StringUtils;
+import cn.com.yusys.yusp.commons.exception.BizException;
 import cn.com.yusys.yusp.config.FastApiConfig;
-import cn.com.yusys.yusp.domain.dto.TagResultDto;
 import cn.com.yusys.yusp.domain.dto.fastapidto.AiTaggingFeedbackRequestDto;
 import cn.com.yusys.yusp.domain.dto.fastapidto.AiTaggingQueryRequestDto;
 import cn.com.yusys.yusp.domain.dto.fastapidto.AiTaggingRequestDto;
-import cn.com.yusys.yusp.domain.entity.AitagTagLogEntity;
-import cn.com.yusys.yusp.domain.vo.EsbVo.CustomerProfileNode;
-import cn.com.yusys.yusp.domain.vo.EsbVo.CustomerProfileReqVo;
 import cn.com.yusys.yusp.domain.vo.fastapivo.AiTaggingQueryResponseVo;
 import cn.com.yusys.yusp.domain.vo.fastapivo.AiTaggingResponseVo;
 import cn.com.yusys.yusp.mapper.AitagTagLogDao;
-import cn.com.yusys.yusp.service.AitagTagLogService;
 import cn.com.yusys.yusp.service.FastApiService;
 import cn.com.yusys.yusp.service.esb.ESBService;
-import cn.com.yusys.yusp.util.SessionCommonUtil;
 import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.http.HttpEntity;
 import org.apache.http.client.config.RequestConfig;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
@@ -34,11 +25,6 @@ import org.springframework.stereotype.Service;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-
-import static cn.com.yusys.yusp.config.DataDictionary.CONFIRM_TAGGING;
-import static cn.com.yusys.yusp.config.DataDictionary.FEEDBACK_RESULT_AGREE;
 
 @Slf4j
 @Service
@@ -216,9 +202,30 @@ public class FastApiServiceImpl implements FastApiService {
             return errorResponse;
         }
     }
-// ... existing code ...
 
 
+    @Override
+    public AiTaggingResponseVo categorySync(String categoryId) {
+        String url = fastApiConfig.getUrl() + "/api/aitag/admin/v1/synchronize_category";
+        log.info("调用标签体系信息同步接口: {}", url);
+
+        try (CloseableHttpClient httpClient = createHttpClient()) {
+            HttpPost httpPost = new HttpPost(url);
+            httpPost.setHeader("Content-Type", "application/json");
+            // 构造请求体
+            String requestBody ="{\"category_id\": \""+categoryId+"\"}";
+            httpPost.setEntity(new StringEntity(requestBody, "UTF-8"));
+            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+                String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
+                log.info("调用标签体系信息同步接口: {}", responseBody);
+                return JSON.parseObject(responseBody, AiTaggingResponseVo.class);
+            }
+        } catch (Exception e) {
+            log.error("调用标签体系信息同步接口", e);
+            throw BizException.of("E008");
+        }
+    }
+
 
     // 辅助方法用于 URL 编码
     private String encodeParam(String param) {