diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json index 2f52ecc2..8cdb7262 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json @@ -1,6 +1,6 @@ { - "access_token": "u-6rHtN.Y5pcGFRZt3R.E584l5koW5k1WPq8aaIAM00ASj", - "refresh_token": "ur-6FagMxFLR1WU6.xq5hcq8Fl5moWBk1MrX8aaINM00xym", + "access_token": "u-4RmqO0mFN44EsJhMOG0bsrl5mqW5k1iVWEaaIMQ00xD2", + "refresh_token": "ur-60CxwKhnldVH9Bd5qTDYxnl5mMW5k1MjgEaaZBQ00Ay6", "name": "飞书用户", - "auth_time": "2026-02-24T21:04:05.071915" + "auth_time": "2026-02-25T05:55:28.028336" } \ No newline at end of file diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_publish_blocks_with_images.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_publish_blocks_with_images.py index e4e5bbce..c70ea4d3 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_publish_blocks_with_images.py +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_publish_blocks_with_images.py @@ -20,6 +20,7 @@ import os import sys import json import argparse +import re from pathlib import Path from datetime import datetime import requests @@ -108,6 +109,69 @@ def create_node(parent_token: str, title: str, headers: dict) -> tuple[str, str] return doc_token, node_token +def _normalize_title(t: str) -> str: + if not t: + return "" + s = t.strip().lower() + # 去掉常见“括号后缀”(如:最终版/含配图/飞书友好版) + s = re.sub(r"[((][^))]*[))]\s*$", "", s) + # 去掉空白与常见分隔符,便于相似匹配 + s = re.sub(r"[\s\-—_·::]+", "", s) + return s + + +def _is_similar_title(a: str, b: str) -> bool: + na, nb = _normalize_title(a), _normalize_title(b) + if not na or not nb: + return False + if na == nb: + return True + # 相互包含(避免过短字符串误判) + if len(na) >= 6 and na in nb: + return True + if len(nb) >= 6 and nb in na: + return True + return False + + +def find_existing_node_by_title(parent_token: str, title: str, headers: dict) -> tuple[str | None, str | None, str | None]: + """在父节点下查找同名/相似标题文档,返回(doc_token,node_token,node_title)""" + r = requests.get( + f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={parent_token}", + headers=headers, timeout=30) + j = r.json() + if j.get("code") != 0: + return None, None, None + node = j["data"]["node"] + space_id = node.get("space_id") or (node.get("space") or {}).get("space_id") or node.get("origin_space_id") + if not space_id: + return None, None, None + + page_token = None + while True: + params = {"parent_node_token": parent_token, "page_size": 50} + if page_token: + params["page_token"] = page_token + nr = requests.get( + f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{space_id}/nodes", + headers=headers, params=params, timeout=30) + nj = nr.json() + if nj.get("code") != 0: + return None, None, None + data = nj.get("data", {}) or {} + nodes = data.get("nodes", []) or data.get("items", []) or [] + for n in nodes: + node_title = n.get("title", "") or n.get("node", {}).get("title", "") + if _is_similar_title(node_title, title): + obj = n.get("obj_token") + node_token = n.get("node_token") + return (obj or node_token), node_token, node_title + page_token = data.get("page_token") + if not page_token: + break + return None, None, None + + def resolve_doc_token(node_token: str, headers: dict) -> str: r = requests.get( f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={node_token}", @@ -119,7 +183,7 @@ def resolve_doc_token(node_token: str, headers: dict) -> str: return node.get("obj_token") or node_token -def clear_doc_blocks(doc_token: str, headers: dict) -> None: +def clear_doc_blocks(doc_token: str, headers: dict) -> bool: """清空文档根节点下直接子块(分页拉取 + 分批删除)""" all_items = [] page_token = None @@ -132,7 +196,8 @@ def clear_doc_blocks(doc_token: str, headers: dict) -> None: headers=headers, params=params, timeout=30) j = r.json() if j.get("code") != 0: - raise RuntimeError(f"获取 blocks 失败: {j.get('msg')}") + print(f"⚠️ 获取 blocks 失败: {j.get('msg')}") + return False data = j.get("data", {}) or {} all_items.extend(data.get("items", []) or []) page_token = data.get("page_token") @@ -141,7 +206,7 @@ def clear_doc_blocks(doc_token: str, headers: dict) -> None: child_ids = [b["block_id"] for b in all_items if b.get("parent_id") == doc_token and b.get("block_id")] if not child_ids: - return + return True for i in range(0, len(child_ids), 50): batch = child_ids[i : i + 50] rd = requests.delete( @@ -149,7 +214,9 @@ def clear_doc_blocks(doc_token: str, headers: dict) -> None: headers=headers, json={"block_id_list": batch}, timeout=30) jd = rd.json() if jd.get("code") != 0: - raise RuntimeError(f"清空失败: {jd.get('msg')}") + print(f"⚠️ 清空失败: {jd.get('msg')}") + return False + return True def replace_image_placeholders(blocks: list, file_tokens: list[str | None], image_paths: list[str]) -> list: @@ -327,11 +394,23 @@ def main(): node_token = args.target doc_token = resolve_doc_token(node_token, headers) print(f"📋 更新已有文档: doc_token={doc_token} node_token={node_token}") - clear_doc_blocks(doc_token, headers) - print("✅ 已清空原内容") + if clear_doc_blocks(doc_token, headers): + print("✅ 已清空原内容") + else: + print("⚠️ 清空失败,将以追加方式更新(仍不会新建重复文档)") else: - doc_token, node_token = create_node(args.parent, args.title, headers) - print(f"✅ 新建文档: doc_token={doc_token} node_token={node_token}") + # 默认:先查同名/相似标题,命中则更新,不再新建 + found_doc, found_node, found_title = find_existing_node_by_title(args.parent, args.title, headers) + if found_doc and found_node: + doc_token, node_token = found_doc, found_node + print(f"📋 命中相似标题,改为更新: {found_title}") + if clear_doc_blocks(doc_token, headers): + print("✅ 已清空原内容") + else: + print("⚠️ 清空失败,将以追加方式更新(仍不会新建重复文档)") + else: + doc_token, node_token = create_node(args.parent, args.title, headers) + print(f"✅ 新建文档: doc_token={doc_token} node_token={node_token}") # 上传图片 file_tokens = [] @@ -354,7 +433,7 @@ def main(): # 发群 if args.webhook: msg = "\n".join([ - "【卡诺亚基因胶囊】新文章已发布 ✅", + "【卡若基因胶囊】文章已发布/更新 ✅", f"标题:{args.title}", f"链接:{url}", "", diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/md_to_feishu_json.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/md_to_feishu_json.py index 4e227237..d6ec0f6a 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/md_to_feishu_json.py +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/md_to_feishu_json.py @@ -46,7 +46,10 @@ def md_to_blocks(md: str, image_paths: list[str] | None = None) -> list: for line in md.split("\n"): if line.strip().startswith("```"): if in_code: - blocks.append(_text("```\n" + "\n".join(code_lines) + "\n```")) + # 飞书 blocks 常对代码围栏/特殊格式更严格,这里转为普通文本行,提升美观与稳定性 + for cl in code_lines: + if cl.strip(): + blocks.append(_text(f"代码:{cl.strip()}")) code_lines = [] in_code = not in_code continue @@ -64,6 +67,10 @@ def md_to_blocks(md: str, image_paths: list[str] | None = None) -> list: img_idx += 1 continue + # 忽略 Markdown 水平分隔线(避免在飞书出现大量“---”影响观感) + if line.strip() in {"---", "***", "___"}: + continue + # 标题 if line.startswith("# "): blocks.append(_h1(line[2:].strip())) @@ -71,10 +78,11 @@ def md_to_blocks(md: str, image_paths: list[str] | None = None) -> list: blocks.append(_h2(line[3:].strip())) elif line.startswith("### "): blocks.append(_h3(line[4:].strip())) + elif line.lstrip().startswith(">"): + # 引用块转普通说明行,降低写入失败概率 + blocks.append(_text(line.lstrip()[1:].strip())) elif line.strip(): - blocks.append(_text(line)) - else: - blocks.append(_text("")) + blocks.append(_text(line.strip())) return blocks diff --git a/运营中枢/参考资料/卡若AI外网化与外部调用方案.md b/运营中枢/参考资料/卡若AI外网化与外部调用方案.md index d988fb68..8cecb070 100644 --- a/运营中枢/参考资料/卡若AI外网化与外部调用方案.md +++ b/运营中枢/参考资料/卡若AI外网化与外部调用方案.md @@ -1,186 +1,311 @@ -# 卡若AI 外网化与外部调用方案 +# 卡若AI 接口全链路使用说明书(部署 + 配置 + 调用 + 运维) -> 目标:让卡若AI 可从外网访问,其他 AI 或任意终端用「一句话/一个命令」即可按卡若AI 的思考逻辑调用并生成回复。 -> 版本:1.0 | 更新:2026-02-17 +> 适用对象:卡若AI 内部团队、科室/部门调用方、外部技术合作方 +> 目标:把卡若AI网关以标准 API 形式稳定对外,支持 Cursor/OpenAI 兼容客户端与脚本调用 +> 版本:2.0 | 更新:2026-02-24 --- -## 一、目标与效果 +## 1. 总览(先看这个) -| 目标 | 说明 | -|:---|:---| -| 外网可访问 | 不限于本机,任意网络通过域名或 IP:端口 访问卡若AI。 | -| 按卡若AI 思考逻辑生成 | 每次请求走:先思考 → 查 SKILL_REGISTRY → 读对应 SKILL → 生成回复 → 带复盘格式。 | -| 其他 AI 可集成 | 其他 AI(Cursor、Claude、GPT、自建 Bot)执行一条命令或请求一个 URL,即「用卡若AI 能力」完成对话。 | -| 最终交付 | 给你:**可执行命令**、**调用链接/域名**,在 Cursor 或其它 AI 里输入即用。 | +当前生产链路为: + +1. 客户端(Cursor/脚本/系统)请求域名 `kr-ai.quwanzhi.com` +2. 存客宝宝塔 Nginx 接收请求(80/443) +3. Nginx 反代到本机 `127.0.0.1:18080`(frps 端口) +4. frps 将 `18080` 转发到 CKB NAS 的 `127.0.0.1:8000` +5. NAS 上 `karuo-ai-gateway` 返回 OpenAI 兼容结果 + +对外统一入口: + +- `https://kr-ai.quwanzhi.com` --- -## 二、实现形式(架构) +## 2. 架构与职责 -``` -外部(其他 AI / 用户) - │ - │ HTTP POST /chat 或 打开网页 - ▼ -┌─────────────────────────────────────────────────────┐ -│ 卡若AI 网关(API 服务) │ -│ · 接收 prompt │ -│ · 加载 BOOTSTRAP + SKILL_REGISTRY │ -│ · 匹配技能 → 读 SKILL.md │ -│ · 调用 LLM(本地或云端 API)按卡若AI 流程生成 │ -│ · 返回:思考 + 执行摘要 + 复盘块 │ -└─────────────────────────────────────────────────────┘ - │ - │ 部署在:宝塔服务器(推荐,固定域名)或 本机 + 内网穿透 - ▼ - 外网域名:https://kr-ai.quwanzhi.com(标准方案见下) -``` +### 2.1 组件职责 -**两种使用方式:** +- `karuo-ai-gateway`(FastAPI):核心业务网关,负责鉴权、技能匹配、LLM 调用、日志 +- `frpc`(NAS):把 NAS 本地 8000 暴露到公网中转服务器 +- `frps`(存客宝):开放公网转发口(18080) +- `Nginx`(存客宝宝塔):域名入口、HTTPS、路径兼容、反代转发 +- `Aliyun DNS`:`kr-ai.quwanzhi.com -> 42.194.245.239` -1. **API 调用**:其他 AI 或脚本向 `POST /v1/chat` 发 `{"prompt": "用户问题"}`,拿 JSON 里的回复(含复盘)。 -2. **网页对话**:浏览器打开同一服务的 `/` 或 `/chat`,输入问题,页面上展示卡若AI 风格回复。 +### 2.2 OpenAI 兼容接口 + +网关提供以下标准接口: + +- `GET /v1/health` +- `GET /v1/models` +- `POST /v1/chat/completions` +- `POST /v1/chat`(内部简化接口) + +已在 Nginx 层做兼容映射(防止部分客户端不带 `/v1`): + +- `/models -> /v1/models` +- `/chat/completions -> /v1/chat/completions` +- `/health -> /v1/health` --- -## 三、部署方式二选一 +## 3. 目录与关键文件 -### 方式 A:宝塔服务器 + 固定域名(推荐,替代 ngrok) +网关代码目录: -- **域名**:**kr-ai.quwanzhi.com**(阿里云解析 + 宝塔 Nginx + SSL,电脑关机也可访问)。 -- **部署**:网关部署在 kr宝塔 43.139.27.93;一键脚本:`bash 01_卡资(金)/金仓_存储备份/服务器管理/scripts/部署卡若AI网关到kr宝塔.sh`。 -- **完整步骤**(阿里云 DNS、Nginx、自启):见 **`01_卡资(金)/金仓_存储备份/服务器管理/references/内网穿透与域名配置_卡若AI标准方案.md`**。 -- **执行命令 / 链接**: - - 链接:`https://kr-ai.quwanzhi.com` - - 其他 AI 调用:`curl -s -X POST "https://kr-ai.quwanzhi.com/v1/chat" -H "Content-Type: application/json" -d '{"prompt":"你的问题"}' | jq -r '.reply'` +- `运营中枢/scripts/karuo_ai_gateway/main.py` +- `运营中枢/scripts/karuo_ai_gateway/requirements.txt` +- `运营中枢/scripts/karuo_ai_gateway/config/gateway.yaml` +- `运营中枢/scripts/karuo_ai_gateway/config/gateway.example.yaml` +- `运营中枢/scripts/karuo_ai_gateway/tools/generate_dept_key.py` -### 方式 B:本机 + 内网穿透(临时) +NAS 部署目录(生产): -- 在本机运行卡若AI 网关,用 ngrok/cloudflared 得到临时 URL;本机关机则不可访问。仅作临时调试用。 +- `/volume1/docker/karuo-ai-deploy/karuo-ai/运营中枢/scripts/karuo_ai_gateway/` --- -## 四、网关脚本与运行方式 +## 4. 首次部署步骤(全链路) -网关代码放在:**`运营中枢/scripts/karuo_ai_gateway/`**(`main.py` + `requirements.txt` + `README.md`)。 -运行前: +## 4.1 NAS 部署网关(业务服务) -1. 安装依赖:`pip install fastapi uvicorn httpx`(若用 OpenAI 兼容接口,再装 `openai`)。 -2. 配置环境变量(可选):`OPENAI_API_KEY` 或本地模型地址,用于实际生成回复。 -3. 启动: +1. 准备代码目录(推荐从 NAS 本机 Gitea 拉取) +2. 进入网关目录,准备 `.env`: ```bash -cd /Users/karuo/Documents/个人/卡若AI/运营中枢/scripts/karuo_ai_gateway -uvicorn main:app --host 0.0.0.0 --port 8000 +KARUO_GATEWAY_SALT=请填随机长串 +OPENAI_API_KEY=请填模型服务Key +OPENAI_API_BASE=请填兼容地址(如 https://api.openai.com/v1) +OPENAI_MODEL=请填模型名 ``` -启动后: +3. 启动容器(建议 compose): -- 本机访问: 可调试接口。 -- 外网访问:在方式 A 或 B 下用你得到的**域名或 IP:端口**替换下面示例中的 `YOUR_DOMAIN`。 +- 对外监听:`127.0.0.1:8000` +- 容器内启动:`uvicorn main:app --host 0.0.0.0 --port 8000` ---- - -## 四点五、接口配置化(科室/部门可复制) - -> 目标:让以后任何科室/部门/合作方都能“拿到一套配置 + 一个 key”,直接调用卡若AI 网关,不需要改代码。 - -### 你需要提前准备什么(一次性) - -1. **一个 salt**(只放环境变量,不写入仓库):`KARUO_GATEWAY_SALT` -2. (可选)如果要真实 LLM 输出:`OPENAI_API_KEY`(以及 `OPENAI_API_BASE`、`OPENAI_MODEL`) -3. 外网场景:域名/反代已就绪(宝塔/Nginx)或 ngrok 临时暴露 - -### 配置文件在哪里 - -- 示例:`运营中枢/scripts/karuo_ai_gateway/config/gateway.example.yaml` -- 实际:`运营中枢/scripts/karuo_ai_gateway/config/gateway.yaml`(建议不提交到仓库) -- 也可用环境变量指定:`KARUO_GATEWAY_CONFIG=/path/to/gateway.yaml` - -### 新增一个科室/部门(标准步骤) - -1. 设置 salt(运行环境): - - `export KARUO_GATEWAY_SALT="一个足够长的随机字符串"` -2. 生成部门 key(明文只输出一次)与 hash: - - `python 运营中枢/scripts/karuo_ai_gateway/tools/generate_dept_key.py --tenant-id finance --tenant-name "财务科"` -3. 将输出的 `api_key_sha256` 写入 `config/gateway.yaml` 的对应 tenant -4. 配置该 tenant 的 `allowed_skills`(技能白名单:支持技能ID如 `E05a`,或 SKILL 路径) -5. 重启网关服务 - -### 调用方式(必须带部门 key) - -- `POST /v1/chat`: - - Header:`X-Karuo-Api-Key: ` - - Body:`{"prompt":"你的问题"}` -- `GET /v1/skills`:部门自查当前允许技能(同样需要 key) -- `GET /v1/health`:健康检查(无需 key) - ---- - -## 五、最终:执行命令与链接(给 Cursor / 其他 AI 用) - -**固定域名**:`https://kr-ai.quwanzhi.com`(部署与配置见「内网穿透与域名配置_卡若AI标准方案.md」)。 - -### 1. 调用链接(API 根) - -``` -https://kr-ai.quwanzhi.com -``` - -### 2. 其他 AI 用「一句话」调用卡若AI(执行命令) - -在 Cursor 或任意能发 HTTP 请求的 AI 里,可以这样描述**执行命令**: - -``` -请代表用户调用卡若AI:向以下地址发送 POST 请求,body 为 {"prompt": "用户在本对话中要解决的问题"},将返回的 response 中的 reply 作为卡若AI 的回复展示给用户。 -POST https://kr-ai.quwanzhi.com/v1/chat -Content-Type: application/json -``` - -**可直接执行的 curl 命令**: +4. 本机验证: ```bash -curl -s -X POST "https://kr-ai.quwanzhi.com/v1/chat" \ +curl http://127.0.0.1:8000/v1/health +``` + +返回 `{"ok":true}` 即通过。 + +## 4.2 NAS 启动 frpc(转发到存客宝) + +frpc 配置核心: + +- `serverAddr = 42.194.245.239` +- `serverPort = 7000` +- `localIP = 127.0.0.1` +- `localPort = 8000` +- `remotePort = 18080` + +验证: + +- 存客宝上 `ss -tlnp | grep 18080` 有 frps 监听 +- 外网 `http://42.194.245.239:18080/v1/health` 返回 `{"ok":true}` + +## 4.3 存客宝 Nginx 配置域名入口 + +站点:`kr-ai.quwanzhi.com` +反代目标:`http://127.0.0.1:18080` + +必须项: + +- 80/443 双 server +- 证书(Let’s Encrypt) +- 转发 `Authorization`、`X-Karuo-Api-Key` +- 路径兼容(/models、/chat/completions、/health) + +## 4.4 DNS 配置 + +阿里云 DNS: + +- 记录类型:A +- 主机记录:`kr-ai` +- 记录值:`42.194.245.239` + +--- + +## 5. 配置说明(gateway.yaml) + +示例结构: + +```yaml +version: 1 +auth: + header_name: X-Karuo-Api-Key + salt_env: KARUO_GATEWAY_SALT +tenants: + - id: your_tenant + name: 你的部门 + api_key_sha256: "sha256(明文key + salt)" + allowed_skills: [] + limits: + rpm: 600 + max_prompt_chars: 50000 +skills: + registry_path: SKILL_REGISTRY.md + match_strategy: trigger_contains + on_no_match: allow_general +llm: + provider: openai_compatible + api_key_env: OPENAI_API_KEY + api_base_env: OPENAI_API_BASE + model_env: OPENAI_MODEL + timeout_seconds: 60 + max_tokens: 2000 +logging: + enabled: true + path: 运营中枢/工作台/karuo_ai_gateway_access.jsonl + log_request_body: false +``` + +注意事项: + +- `gateway.yaml` 必须是合法 YAML,尤其是 `tenants` 缩进 +- 明文 key 不写入仓库,只写 hash +- `KARUO_GATEWAY_SALT` 必须存在,否则所有 key 校验失败 + +--- + +## 6. 新增科室/部门(标准 SOP) + +1. 设置环境变量: + +```bash +export KARUO_GATEWAY_SALT="你的随机盐" +``` + +2. 生成 key 与 hash: + +```bash +python 运营中枢/scripts/karuo_ai_gateway/tools/generate_dept_key.py \ + --tenant-id finance \ + --tenant-name "财务科" +``` + +3. 将 `api_key_sha256` 写入 `gateway.yaml` 的 `tenants` 列表 +4. 重启网关 +5. 用明文 key 调用 `/v1/chat/completions` 验证 + +--- + +## 7. 调用说明(给客户端/系统) + +## 7.1 通用调用(OpenAI 兼容) + +推荐 Base URL: + +- `https://kr-ai.quwanzhi.com/v1` + +鉴权: + +- `Authorization: Bearer ` + +示例: + +```bash +curl -sS https://kr-ai.quwanzhi.com/v1/chat/completions \ -H "Content-Type: application/json" \ - -d '{"prompt":"你的问题"}' | jq -r '.reply' + -H "Authorization: Bearer " \ + -d '{ + "model":"karuo-ai", + "messages":[{"role":"user","content":"帮我做一份本周复盘"}] + }' ``` -### 3. 在 Cursor 里「用卡若AI 完成本对话」的固定说明(复制即用) +## 7.2 Cursor 配置(重点) -把下面一段存成 Cursor 的规则或对话开头说明,即可在任意对话里「切到卡若AI」: - -``` -当用户说「用卡若AI」「交给卡若AI」「调用卡若AI」或明确要求用卡若AI 回答时: -1. 将用户当前问题作为 prompt。 -2. 请求:POST https://kr-ai.quwanzhi.com/v1/chat,Body: {"prompt": "<用户问题>"}。 -3. 将响应 JSON 中的 reply 字段内容(含卡若复盘)完整展示给用户,作为卡若AI 的回复。 -``` +1. `OpenAI API Key`:填部门 key +2. `Override OpenAI Base URL`:`https://kr-ai.quwanzhi.com/v1` +3. 不要在 Base URL 末尾加 `/` +4. 改完后重启 Cursor 一次 --- -## 六、小结 +## 8. 健康检查与联调命令 -| 项目 | 内容 | -|:---|:---| -| 实现形式 | 卡若AI 网关(FastAPI):读 BOOTSTRAP + REGISTRY + SKILL,按流程调 LLM,返回带复盘的回复。 | -| 外网访问 | **固定域名**:https://kr-ai.quwanzhi.com(宝塔 43.139.27.93,替代 ngrok,电脑关机也可用)。 | -| 其他 AI 集成 | 执行 POST /v1/chat;或在 Cursor 里用上面「固定说明」。 | -| 执行命令 | `curl -s -X POST "https://kr-ai.quwanzhi.com/v1/chat" -H "Content-Type: application/json" -d '{"prompt":"你的问题"}' \| jq -r '.reply'` | -| 配置与部署 | 见 `01_卡资(金)/金仓_存储备份/服务器管理/references/内网穿透与域名配置_卡若AI标准方案.md` | - ---- - -## 七、在 Cursor 里「用卡若AI 完成本对话」的一条执行命令 - -**方式 1:直接 curl(终端执行)** ```bash -curl -s -X POST "https://kr-ai.quwanzhi.com/v1/chat" -H "Content-Type: application/json" -d '{"prompt":"这里填你要问卡若AI的问题"}' | jq -r '.reply' +# 1) 域名健康 +curl -sS https://kr-ai.quwanzhi.com/v1/health + +# 2) 模型列表 +curl -sS https://kr-ai.quwanzhi.com/v1/models + +# 3) 鉴权验证 +curl -sS https://kr-ai.quwanzhi.com/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"model":"karuo-ai","messages":[{"role":"user","content":"ping"}]}' ``` -**方式 2:交给 Cursor 的说明(复制到对话或规则)** -``` -当用户要求「用卡若AI」「交给卡若AI」或「用卡若AI 回答」时,请向 POST https://kr-ai.quwanzhi.com/v1/chat 发送 {"prompt": "用户当前问题"},将返回的 reply 作为卡若AI 的回复完整展示给用户。 -``` +--- -**阿里云解析**:已通过脚本 `01_卡资(金)/金仓_存储备份/服务器管理/scripts/阿里云DNS_添加kr-ai解析.py` 添加 A 记录 kr-ai -> 43.139.27.93。完成服务器部署后即可长期使用;后续需要新内网穿透/新域名时,按「内网穿透与域名配置_卡若AI标准方案」生成子域名并配置,不再询问用户。 +## 9. 故障排查(最常见) + +## 9.1 Cursor `Provider Error` + +优先排查: + +1. Base URL 是否写成 `https://kr-ai.quwanzhi.com/v1` +2. 是否错误写成带尾斜杠(可能出现 `//chat/completions`) +3. key 是否正确(401 会被客户端包装成 Provider Error) +4. 站点是否已配置 HTTPS(443) + +## 9.2 502 Bad Gateway + +检查链路: + +1. 存客宝 `127.0.0.1:18080` 是否可达 +2. frpc/frps 是否在线 +3. NAS `127.0.0.1:8000/v1/health` 是否正常 +4. Nginx 配置是否 reload 成功 + +## 9.3 401 invalid api key + +排查点: + +1. `KARUO_GATEWAY_SALT` 与生成 hash 时是否一致 +2. `gateway.yaml` tenant 缩进是否正确 +3. 请求头是否正确传递 `Authorization` 或 `X-Karuo-Api-Key` + +--- + +## 10. 运维与自动化建议 + +建议保持以下自动任务: + +1. NAS 每 2 分钟拉取本机 Gitea 主分支变更并重启网关 +2. NAS 每 2 分钟自检 frpc 进程并自动拉起 +3. Nginx 与网关日志按天轮转 +4. 每周检查证书续签状态 + +--- + +## 11. 安全建议(上线必做) + +1. 生产环境关闭“固定 key 注入”类联调兜底 +2. 仅保留租户级鉴权(每部门独立 key) +3. 对 `/` 根路径与扫描流量加拦截/限速 +4. `gateway.yaml`、日志文件不入库 +5. 定期轮换部门 key + +--- + +## 12. 交付清单(给合作方) + +给调用方只需要四项: + +1. Base URL:`https://kr-ai.quwanzhi.com/v1` +2. API Key:`` +3. 示例请求(chat/completions) +4. 错误码说明(401/429/500) + +--- + +## 13. 版本记录 + +- `v2.0`(2026-02-24):补齐 CKB NAS + frp + 存客宝 Nginx + HTTPS + Cursor 兼容的全链路部署与排障说明。 diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 3fffc5ca..ec8c7f37 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -129,3 +129,4 @@ | 2026-02-24 19:59:17 | 🔄 卡若AI 同步 2026-02-24 19:59 | 更新:总索引与入口、水溪整理归档、卡木、运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 12 个 | | 2026-02-24 20:10:45 | 🔄 卡若AI 同步 2026-02-24 20:10 | 更新:运营中枢、运营中枢工作台 | 排除 >20MB: 12 个 | | 2026-02-24 21:16:30 | 🔄 卡若AI 同步 2026-02-24 21:16 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 12 个 | +| 2026-02-24 21:33:10 | 🔄 卡若AI 同步 2026-02-24 21:33 | 更新:总索引与入口、水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 36469ef3..997300a1 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -132,3 +132,4 @@ | 2026-02-24 19:59:17 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-24 19:59 | 更新:总索引与入口、水溪整理归档、卡木、运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 12 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-24 20:10:45 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-24 20:10 | 更新:运营中枢、运营中枢工作台 | 排除 >20MB: 12 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-24 21:16:30 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-24 21:16 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 12 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-02-24 21:33:10 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-24 21:33 | 更新:总索引与入口、水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |