Просмотр исходного кода

debug: 添加AI工具调用调试日志和诊断脚本

kinglee 2 дней назад
Родитель
Сommit
3d25c216dd
6 измененных файлов с 940 добавлено и 27 удалено
  1. 4 3
      .wolf/anatomy.md
  2. 33 8
      .wolf/hooks/_session.json
  3. 8 0
      .wolf/memory.md
  4. 693 7
      .wolf/token-ledger.json
  5. 46 9
      app/routes/ai_routes.py
  6. 156 0
      diagnose_tool_call.py

+ 4 - 3
.wolf/anatomy.md

@@ -1,7 +1,7 @@
 # anatomy.md
 
-> Auto-maintained by OpenWolf. Last scanned: 2026-05-25T07:31:48.123Z
-> Files: 552 tracked | Anatomy hits: 0 | Misses: 0
+> Auto-maintained by OpenWolf. Last scanned: 2026-05-25T08:19:25.085Z
+> Files: 553 tracked | Anatomy hits: 0 | Misses: 0
 
 ## ./
 
@@ -15,6 +15,7 @@
 - `CLAUDE.md` — OpenWolf (~1291 tok)
 - `create_admin.py` (~118 tok)
 - `DEPLOY.md` — Docker 部署文档 (~901 tok)
+- `diagnose_tool_call.py` — main (~1660 tok)
 - `docker-compose.dev.yml` — Docker Compose: 1 services (~57 tok)
 - `docker-compose.yml` — Docker Compose services (~517 tok)
 - `Dockerfile` — Docker container definition (~232 tok)
@@ -795,7 +796,7 @@
 
 ## app/routes/
 
-- `ai_routes.py` — execute_sql, parse_tool_calls_from_content, process_chart_option (~9429 tok)
+- `ai_routes.py` — execute_sql, parse_sql_from_text, parse_tool_calls_from_content, process_chart_option (~9923 tok)
 - `collection_routes.py` — 采集任务管理 & save_selected 接口 (~270 tok)
 - `data_routes.py` — 数据查看/导出 (~300 tok)
 - `deep_routes.py` — dashboard, list_data, run_crawl, safe_commit (~3816 tok)

+ 33 - 8
.wolf/hooks/_session.json

@@ -3,8 +3,8 @@
   "started": "2026-05-25T06:26:19.780Z",
   "files_read": {
     "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py": {
-      "count": 11,
-      "tokens": 9173,
+      "count": 18,
+      "tokens": 9743,
       "first_read": "2026-05-25T06:31:27.249Z"
     },
     "D:/bigmodel/ai-LiaoWangweb-app/.env": {
@@ -38,8 +38,8 @@
       "first_read": "2026-05-25T07:18:00.556Z"
     },
     "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html": {
-      "count": 6,
-      "tokens": 0,
+      "count": 7,
+      "tokens": 4238,
       "first_read": "2026-05-25T07:18:10.757Z"
     },
     "D:/bigmodel/ai-LiaoWangweb-app/app/__init__.py": {
@@ -153,20 +153,45 @@
       "action": "edit",
       "tokens": 117,
       "at": "2026-05-25T07:31:48.131Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+      "action": "edit",
+      "tokens": 340,
+      "at": "2026-05-25T08:16:24.995Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/diagnose_tool_call.py",
+      "action": "create",
+      "tokens": 1660,
+      "at": "2026-05-25T08:17:39.871Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+      "action": "edit",
+      "tokens": 322,
+      "at": "2026-05-25T08:18:59.736Z"
+    },
+    {
+      "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+      "action": "edit",
+      "tokens": 795,
+      "at": "2026-05-25T08:19:25.093Z"
     }
   ],
   "edit_counts": {
-    "app/routes/ai_routes.py": 5,
+    "app/routes/ai_routes.py": 8,
     "C:/Users/liwei/.claude/plans/mellow-frolicking-summit.md": 1,
     "app/models.py": 1,
     "app/templates/ai_management.html": 3,
     "app/templates/ai_analysis.html": 3,
     "migrate_add_is_default.py": 1,
-    "entrypoint.sh": 1
+    "entrypoint.sh": 1,
+    "diagnose_tool_call.py": 1
   },
   "anatomy_hits": 7,
   "anatomy_misses": 5,
-  "repeated_reads_warned": 17,
+  "repeated_reads_warned": 25,
   "cerebrum_warnings": 0,
-  "stop_count": 13
+  "stop_count": 17
 }

+ 8 - 0
.wolf/memory.md

@@ -116,3 +116,11 @@
 | 15:30 | Session end: 14 writes across 6 files (ai_routes.py, mellow-frolicking-summit.md, models.py, ai_management.html, ai_analysis.html) | 10 reads | ~15877 tok |
 | 15:31 | Edited entrypoint.sh | 9→10 lines | ~109 |
 | 15:32 | Session end: 15 writes across 7 files (ai_routes.py, mellow-frolicking-summit.md, models.py, ai_management.html, ai_analysis.html) | 12 reads | ~16592 tok |
+| 15:33 | Session end: 15 writes across 7 files (ai_routes.py, mellow-frolicking-summit.md, models.py, ai_management.html, ai_analysis.html) | 12 reads | ~16592 tok |
+| 16:05 | Session end: 15 writes across 7 files (ai_routes.py, mellow-frolicking-summit.md, models.py, ai_management.html, ai_analysis.html) | 12 reads | ~20830 tok |
+| 16:16 | Edited app/routes/ai_routes.py | modified execute_sql() | ~340 |
+| 16:17 | Created diagnose_tool_call.py | — | ~1660 |
+| 16:17 | Session end: 17 writes across 8 files (ai_routes.py, mellow-frolicking-summit.md, models.py, ai_management.html, ai_analysis.html) | 12 reads | ~23233 tok |
+| 16:18 | Edited app/routes/ai_routes.py | expanded (+11 lines) | ~322 |
+| 16:19 | Edited app/routes/ai_routes.py | modified iter_lines() | ~795 |
+| 16:19 | Session end: 19 writes across 8 files (ai_routes.py, mellow-frolicking-summit.md, models.py, ai_management.html, ai_analysis.html) | 12 reads | ~24517 tok |

+ 693 - 7
.wolf/token-ledger.json

@@ -2,14 +2,14 @@
   "version": 1,
   "created_at": "2026-05-21T16:43:25.726Z",
   "lifetime": {
-    "total_tokens_estimated": 618839,
-    "total_reads": 551,
-    "total_writes": 568,
+    "total_tokens_estimated": 704011,
+    "total_reads": 599,
+    "total_writes": 634,
     "total_sessions": 4,
-    "anatomy_hits": 298,
-    "anatomy_misses": 253,
-    "repeated_reads_blocked": 239,
-    "estimated_savings_vs_bare_cli": 649020
+    "anatomy_hits": 326,
+    "anatomy_misses": 273,
+    "repeated_reads_blocked": 322,
+    "estimated_savings_vs_bare_cli": 1225879
   },
   "sessions": [
     {
@@ -6897,6 +6897,692 @@
         "repeated_reads_blocked": 17,
         "anatomy_lookups": 7
       }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T07:33:27.238Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 9173,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 0,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "/home/user/app/routes/ai_routes.py",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrations/alembic.ini",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 0,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/__init__.py",
+          "tokens_estimated": 500,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 3000,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/docker-compose.yml",
+          "tokens_estimated": 517,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/entrypoint.sh",
+          "tokens_estimated": 81,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 148,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 124,
+          "action": "edit"
+        },
+        {
+          "file": "C:/Users/liwei/.claude/plans/mellow-frolicking-summit.md",
+          "tokens_estimated": 339,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 46,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 182,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 38,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 212,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 161,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 245,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 227,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 193,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 438,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 60,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrate_add_is_default.py",
+          "tokens_estimated": 230,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/entrypoint.sh",
+          "tokens_estimated": 117,
+          "action": "edit"
+        }
+      ],
+      "totals": {
+        "input_tokens_estimated": 13832,
+        "output_tokens_estimated": 2760,
+        "reads_count": 12,
+        "writes_count": 15,
+        "repeated_reads_blocked": 17,
+        "anatomy_lookups": 7
+      }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T08:05:49.576Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 9173,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 0,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "/home/user/app/routes/ai_routes.py",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrations/alembic.ini",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 4238,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/__init__.py",
+          "tokens_estimated": 500,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 3000,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/docker-compose.yml",
+          "tokens_estimated": 517,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/entrypoint.sh",
+          "tokens_estimated": 81,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 148,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 124,
+          "action": "edit"
+        },
+        {
+          "file": "C:/Users/liwei/.claude/plans/mellow-frolicking-summit.md",
+          "tokens_estimated": 339,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 46,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 182,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 38,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 212,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 161,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 245,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 227,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 193,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 438,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 60,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrate_add_is_default.py",
+          "tokens_estimated": 230,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/entrypoint.sh",
+          "tokens_estimated": 117,
+          "action": "edit"
+        }
+      ],
+      "totals": {
+        "input_tokens_estimated": 18070,
+        "output_tokens_estimated": 2760,
+        "reads_count": 12,
+        "writes_count": 15,
+        "repeated_reads_blocked": 18,
+        "anatomy_lookups": 7
+      }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T08:17:45.659Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 9576,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 0,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "/home/user/app/routes/ai_routes.py",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrations/alembic.ini",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 4238,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/__init__.py",
+          "tokens_estimated": 500,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 3000,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/docker-compose.yml",
+          "tokens_estimated": 517,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/entrypoint.sh",
+          "tokens_estimated": 81,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 148,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 124,
+          "action": "edit"
+        },
+        {
+          "file": "C:/Users/liwei/.claude/plans/mellow-frolicking-summit.md",
+          "tokens_estimated": 339,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 46,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 182,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 38,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 212,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 161,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 245,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 227,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 193,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 438,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 60,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrate_add_is_default.py",
+          "tokens_estimated": 230,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/entrypoint.sh",
+          "tokens_estimated": 117,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 340,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/diagnose_tool_call.py",
+          "tokens_estimated": 1660,
+          "action": "create"
+        }
+      ],
+      "totals": {
+        "input_tokens_estimated": 18473,
+        "output_tokens_estimated": 4760,
+        "reads_count": 12,
+        "writes_count": 17,
+        "repeated_reads_blocked": 23,
+        "anatomy_lookups": 7
+      }
+    },
+    {
+      "id": "session-2026-05-25-1426",
+      "started": "2026-05-25T06:26:19.780Z",
+      "ended": "2026-05-25T08:19:42.023Z",
+      "reads": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 9743,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/.env",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/config.py",
+          "tokens_estimated": 561,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "C:/Users/liwei/.claude/projects/D--bigmodel-ai-LiaoWangweb-app/bc1d57e2-13fc-4a40-876d-26b76df755ad/tool-results/bz3vr3z8l.txt",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 0,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "/home/user/app/routes/ai_routes.py",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrations/alembic.ini",
+          "tokens_estimated": 0,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 4238,
+          "was_repeated": true,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/__init__.py",
+          "tokens_estimated": 500,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 3000,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/docker-compose.yml",
+          "tokens_estimated": 517,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/entrypoint.sh",
+          "tokens_estimated": 81,
+          "was_repeated": false,
+          "anatomy_had_description": false
+        }
+      ],
+      "writes": [
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 148,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 124,
+          "action": "edit"
+        },
+        {
+          "file": "C:/Users/liwei/.claude/plans/mellow-frolicking-summit.md",
+          "tokens_estimated": 339,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/models.py",
+          "tokens_estimated": 46,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 182,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 38,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 212,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 161,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 245,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_management.html",
+          "tokens_estimated": 227,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 193,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 438,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/templates/ai_analysis.html",
+          "tokens_estimated": 60,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/migrate_add_is_default.py",
+          "tokens_estimated": 230,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/entrypoint.sh",
+          "tokens_estimated": 117,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 340,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/diagnose_tool_call.py",
+          "tokens_estimated": 1660,
+          "action": "create"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 322,
+          "action": "edit"
+        },
+        {
+          "file": "D:/bigmodel/ai-LiaoWangweb-app/app/routes/ai_routes.py",
+          "tokens_estimated": 795,
+          "action": "edit"
+        }
+      ],
+      "totals": {
+        "input_tokens_estimated": 18640,
+        "output_tokens_estimated": 5877,
+        "reads_count": 12,
+        "writes_count": 19,
+        "repeated_reads_blocked": 25,
+        "anatomy_lookups": 7
+      }
     }
   ],
   "daemon_usage": [],

+ 46 - 9
app/routes/ai_routes.py

@@ -68,19 +68,34 @@ def execute_sql(sql_query):
         lower_sql = sql_query.lower().strip()
         if not lower_sql.startswith('select'):
             return "Error: Only SELECT queries are allowed."
-        
+
         # Execute
         result = db.session.execute(text(sql_query))
         rows = result.fetchall()
         if not rows:
             return "No results found."
-            
+
         columns = result.keys()
         data = [dict(zip(columns, row)) for row in rows]
         return json.dumps(data, default=str, ensure_ascii=False)
     except Exception as e:
         return f"Error executing SQL: {str(e)}"
 
+def parse_sql_from_text(text):
+    """Extract SQL queries from model text response as fallback."""
+    import re
+    # 1. Try ```sql code blocks
+    matches = re.findall(r'```sql\s*\n(.*?)```', text, re.DOTALL | re.IGNORECASE)
+    for sql in matches:
+        sql = sql.strip()
+        if sql.lower().startswith('select'):
+            return sql
+    # 2. Try any SELECT statement (multi-line)
+    match = re.search(r'(SELECT\s+[\s\S]+?)(?:;|\Z)', text, re.IGNORECASE)
+    if match:
+        return match.group(1).strip()
+    return None
+
 def parse_tool_calls_from_content(content):
     tool_calls = []
     cleaned_content = content
@@ -439,17 +454,30 @@ def analysis_chat():
                     "max_tokens": 4096,
                     "temperature": 0.1
                 }
-                
+
+                # Debug: Log the payload being sent
+                print(f"\n=== API Request Debug (Turn {turn_count}) ===")
+                print(f"Model: {model.model_name}")
+                print(f"URL: {url}")
+                print(f"Messages count: {len(current_messages)}")
+                print(f"System prompt length: {len(current_messages[0]['content']) if current_messages else 0}")
+                print(f"Tools count: {len(TOOLS)}")
+                print(f"Tool names: {[t['function']['name'] for t in TOOLS]}")
+                print(f"Tool choice: auto")
+                print(f"=== End Debug ===\n")
+
                 full_content = ""
                 tool_calls_buffer = []
-                
+
                 with requests.post(url, json=payload, headers=headers, stream=True) as response:
                     if response.status_code != 200:
                         yield f"data: {json.dumps({'error': f'API Error: {response.status_code} - {response.text}'})}\n\n"
                         return
                         
+                    chunk_count = 0
                     for line in response.iter_lines():
                         if line:
+                            chunk_count += 1
                             decoded_line = line.decode('utf-8')
                             if decoded_line.startswith('data: '):
                                 data_str = decoded_line[6:].strip()
@@ -458,26 +486,35 @@ def analysis_chat():
                                     data_json = json.loads(data_str)
                                     if 'choices' in data_json and len(data_json['choices']) > 0:
                                         delta = data_json['choices'][0].get('delta', {})
-                                        
+
                                         c = delta.get('content', '')
                                         if c:
                                             full_content += c
                                             yield f"data: {json.dumps({'content': c})}\n\n"
-                                            
+
                                         if 'tool_calls' in delta:
                                             tc_chunk = delta['tool_calls']
+                                            print(f"Chunk {chunk_count}: tool_calls received: {json.dumps(tc_chunk, ensure_ascii=False)[:200]}")
                                             for tc in tc_chunk:
                                                 index = tc.index
                                                 if index >= len(tool_calls_buffer):
                                                     tool_calls_buffer.append({'id': '', 'type': 'function', 'function': {'name': '', 'arguments': ''}})
-                                                
+
                                                 tcb = tool_calls_buffer[index]
                                                 if tc.get('id'): tcb['id'] = tc['id']
                                                 if tc.get('function'):
                                                     if tc['function'].get('name'): tcb['function']['name'] += tc['function']['name']
                                                     if tc['function'].get('arguments'): tcb['function']['arguments'] += tc['function']['arguments']
-                                except:
-                                    pass
+                                except Exception as e:
+                                    print(f"Chunk {chunk_count} parse error: {e}")
+                                    print(f"Raw data: {data_str[:200]}")
+
+                    print(f"Stream ended. Total chunks: {chunk_count}")
+                    print(f"Tool calls buffer length: {len(tool_calls_buffer)}")
+                    if tool_calls_buffer:
+                        print(f"Tool calls: {json.dumps(tool_calls_buffer, ensure_ascii=False)}")
+                    else:
+                        print(f"No tool calls. Content length: {len(full_content)}")
 
                 # Post-stream processing
                 final_tool_calls = tool_calls_buffer

+ 156 - 0
diagnose_tool_call.py

@@ -0,0 +1,156 @@
+"""
+Diagnose GLM-4.7 tool calling behavior.
+Calls the API directly and logs raw SSE chunks.
+"""
+import os
+import sys
+import json
+import requests
+
+# Load .env
+from dotenv import load_dotenv
+env_path = os.path.join(os.path.dirname(__file__), '.env')
+load_dotenv(env_path)
+
+# Get model config from command line or use defaults
+API_BASE = sys.argv[1] if len(sys.argv) > 1 else "https://open.bigmodel.cn/api/paas/v4/"
+API_KEY = sys.argv[2] if len(sys.argv) > 2 else "your-api-key-here"
+MODEL_NAME = sys.argv[3] if len(sys.argv) > 3 else "glm-4-flash"
+
+TOOLS = [
+    {
+        "type": "function",
+        "function": {
+            "name": "query_database",
+            "description": "Execute a SQL SELECT query to retrieve data from a PostgreSQL database. Available tables:\n1. deep_collection (id, url, content, summary, status, error_msg, created_at, updated_at)\n2. spider_result (id, task_id, title, abstract, source, cover, link, created_at)\n3. collection_task (id, keyword, source, status, created_at, finished_at)\nUse PostgreSQL syntax. Use COUNT/GROUP BY for statistics. Always call this tool when the user asks about data.",
+            "parameters": {
+                "type": "object",
+                "properties": {
+                    "sql": {
+                        "type": "string",
+                        "description": "The SQL SELECT query to execute."
+                    }
+                },
+                "required": ["sql"]
+            }
+        }
+    },
+    {
+        "type": "function",
+        "function": {
+            "name": "render_chart",
+            "description": "Generate a chart configuration for the user interface. You MUST use this tool to show charts.",
+            "parameters": {
+                "type": "object",
+                "properties": {
+                    "title": {"type": "string"},
+                    "chart_type": {"type": "string", "enum": ["bar", "line", "pie"]},
+                    "data": {"type": "object"}
+                },
+                "required": ["title", "chart_type", "data"]
+            }
+        }
+    }
+]
+
+def main():
+    url = API_BASE.rstrip('/') + '/chat/completions'
+
+    headers = {
+        "Authorization": f"Bearer {API_KEY}",
+        "Content-Type": "application/json"
+    }
+
+    payload = {
+        "model": MODEL_NAME,
+        "messages": [
+            {"role": "system", "content": "你是一个专业的数据分析助手。当用户询问任何关于数据的问题时,你必须首先调用 query_database 工具查询数据。SQL 使用 PostgreSQL 语法。统计类问题使用 COUNT + GROUP BY。"},
+            {"role": "user", "content": "统计一下最近采集的新闻来源分布"}
+        ],
+        "tools": TOOLS,
+        "tool_choice": "auto",
+        "stream": True,
+        "max_tokens": 4096,
+        "temperature": 0.1
+    }
+
+    print(f"=== GLM-4.7 Tool Call Diagnostic ===")
+    print(f"URL: {url}")
+    print(f"Model: {MODEL_NAME}")
+    print(f"Sending request...\n")
+
+    chunk_count = 0
+    tool_call_chunks = []
+    content_chunks = []
+
+    with requests.post(url, json=payload, headers=headers, stream=True, timeout=60) as response:
+        print(f"HTTP Status: {response.status_code}")
+
+        if response.status_code != 200:
+            print(f"Error: {response.text}")
+            return
+
+        for line in response.iter_lines():
+            if not line:
+                continue
+            decoded_line = line.decode('utf-8')
+            if decoded_line.startswith('data: '):
+                data_str = decoded_line[6:].strip()
+                if data_str == '[DONE]':
+                    print(f"\n[DONE] received after {chunk_count} chunks")
+                    break
+
+                chunk_count += 1
+                try:
+                    data_json = json.loads(data_str)
+
+                    if 'choices' in data_json and len(data_json['choices']) > 0:
+                        choice = data_json['choices'][0]
+                        delta = choice.get('delta', {})
+                        finish_reason = choice.get('finish_reason')
+
+                        # Log content
+                        c = delta.get('content', '')
+                        if c:
+                            content_chunks.append(c)
+                            if len(content_chunks) <= 3:
+                                print(f"Chunk #{chunk_count} [content]: {repr(c[:80])}")
+
+                        # Log tool_calls
+                        if 'tool_calls' in delta:
+                            tc_chunk = delta['tool_calls']
+                            tool_call_chunks.append(tc_chunk)
+                            print(f"Chunk #{chunk_count} [tool_calls]: {json.dumps(tc_chunk, ensure_ascii=False)}")
+
+                        # Log finish_reason
+                        if finish_reason:
+                            print(f"Chunk #{chunk_count} [finish_reason]: {finish_reason}")
+
+                    # Log usage if present
+                    if 'usage' in data_json and data_json['usage']:
+                        print(f"Chunk #{chunk_count} [usage]: {data_json['usage']}")
+
+                except json.JSONDecodeError as e:
+                    print(f"Chunk #{chunk_count} [JSON ERROR]: {e}")
+                    print(f"  Raw: {data_str[:200]}")
+
+    print(f"\n=== Summary ===")
+    print(f"Total chunks: {chunk_count}")
+    print(f"Content chunks: {len(content_chunks)}")
+    print(f"Tool call chunks: {len(tool_call_chunks)}")
+
+    if content_chunks:
+        full_content = ''.join(content_chunks)
+        print(f"\nFull content ({len(full_content)} chars):")
+        print(full_content[:500])
+
+    if tool_call_chunks:
+        print(f"\nAll tool call chunks:")
+        for i, tc in enumerate(tool_call_chunks):
+            print(f"  {i}: {json.dumps(tc, ensure_ascii=False)}")
+    else:
+        print("\n*** NO TOOL CALLS RECEIVED ***")
+        print("The model did NOT invoke any tools.")
+
+if __name__ == '__main__':
+    main()