diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md b/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md index 4185a6db..b06ae1e1 100755 --- a/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md +++ b/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md @@ -1,7 +1,7 @@ --- name: 飞书管理 -description: 飞书日志/文档自动写入与知识库管理(含统一文章上传) -triggers: 飞书日志、写入飞书、飞书知识库、飞书运营报表、派对效果数据、104场写入、运营报表填写、派对截图填表发群、Excel写飞书、批量写飞书表格、表格日报、飞书文章上传、MD转飞书JSON、同标题更新飞书 +description: 飞书日志/文档自动写入与知识库管理 +triggers: 飞书日志、写入飞书、飞书知识库、飞书运营报表、派对效果数据、104场写入、运营报表填写、派对截图填表发群、Excel写飞书、批量写飞书表格、表格日报 owner: 水桥 group: 水 version: "1.1" @@ -279,36 +279,6 @@ python3 scripts/wanzhi_feishu_project_sync.py --- -## 统一文章上传(强制唯一入口) - -以后凡是“写文章并上传飞书”,统一走这一条,不再分散用旧脚本。 - -### 一键命令(推荐) - -```bash -python3 /Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_article_unified_publish.py \ - --parent MyvRwCVNSiTg5ok6e3fc6uA5nHg \ - --title "文章标题" \ - --md "/绝对路径/文章.md" \ - --json "/绝对路径/文章_feishu_blocks.json" \ - --webhook "https://open.feishu.cn/open-apis/bot/v2/hook/xxx" -``` - -### 统一规则(已固化进脚本) - -1. **先本地转 JSON**:`md_to_feishu_json.py`,自动清理分隔线/空块,减少飞书样式杂乱和报错。 -2. **同标题优先更新**:在父节点下命中同名/相似标题时更新,不重复新建。 -3. **支持图片上传**:读取 `image_paths` 上传到文档素材。 -4. **图片块失败兜底**:若飞书 API 对图片块返回 `invalid param`,正文照常写入,图片保留在文档素材(可手动插入)。 - -### 当前已验证经验(线上) - -- 飞书 docx 接口在当前租户中,`block_type 12/18/27` 图片块都可能返回 `1770001 invalid param`。 -- 因此采用“**正文稳定写入 + 图片素材保留 + 可手动插图**”作为稳定方案。 -- 后续若飞书放开该能力,再切回全自动嵌图。 - ---- - ## Wiki 子文档创建(日记分享 / 新研究) 在指定飞书 Wiki 节点下创建子文档,用于日记分享、新研究等内容沉淀。 @@ -346,9 +316,6 @@ JSON 格式:与 `团队入职流程与新人登记表_feishu_blocks.json` 相 ├── feishu_video_clip_README.md ├── wanzhi_feishu_project_sync.py # 玩值电竞→飞书项目同步 ├── feishu_wiki_create_doc.py # Wiki 子文档创建(日记/研究) - ├── md_to_feishu_json.py # Markdown -> 飞书 blocks JSON(美观清洗) - ├── feishu_publish_blocks_with_images.py # 同标题更新 + 图片上传 + 发布 - ├── feishu_article_unified_publish.py # 统一入口:文章发布(推荐唯一) └── .feishu_tokens.json # Token 存储 ``` @@ -384,5 +351,5 @@ python3 /Users/karuo/Documents/个人/卡若AI/02_卡人(水)/飞书管理/s --- -**版本**: v3.4 | **更新**: 2026-02-25 -**特性**: 静默授权、倒序插入、TNTWF规范、四象限分类、**写入完成后自动打开飞书日志页面**、**运营报表子技能(截图→填表→发群竖状格式、会议纪要图片上传、月度统计)**、**统一文章上传(MD->JSON->飞书,同标题优先更新,支持图片上传)** +**版本**: v3.3 | **更新**: 2026-02-20 +**特性**: 静默授权、倒序插入、TNTWF规范、四象限分类、**写入完成后自动打开飞书日志页面**、**运营报表子技能(截图→填表→发群竖状格式、会议纪要图片上传、月度统计)** diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json index 76bb1654..edd5c05b 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json @@ -1,6 +1,6 @@ { - "access_token": "u-57b3lfJ8Z7XH0uHVO_Tnx2l5moqBk1qXXEaaFAM00wS6", - "refresh_token": "ur-579x5jnChbCqtQoGJxgcPpl5moMBk1Uph8aaEBw00xzj", + "access_token": "u-4Tr54dmqV8lE_qtfG76A2Il5mMMBk1irW8aaVBM00wO2", + "refresh_token": "ur-4iCTU0PcheAVQUi_Z43c9El5koO5k1MpV8aaIQw00wCn", "name": "飞书用户", - "auth_time": "2026-02-25T05:58:38.159179" + "auth_time": "2026-02-25T09:19:23.848992" } \ No newline at end of file diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_publish_md_direct.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_publish_md_direct.py new file mode 100644 index 00000000..6f49070b --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_publish_md_direct.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +""" +MD 原文直传飞书(不转飞书 JSON) + +特性: +1) 直接读取 .md 原文,按行原样写入(保留 Markdown 符号) +2) 同名/相似标题优先更新,不重复新建 +3) 不处理图片上传与替换(纯原文直传) +""" + +import argparse +import re +from pathlib import Path +import requests +import sys + +SCRIPT_DIR = Path(__file__).resolve().parent +sys.path.insert(0, str(SCRIPT_DIR)) +import feishu_wiki_create_doc as fwd + + +def _text_block(content: str) -> dict: + return { + "block_type": 2, + "text": { + "elements": [{"text_run": {"content": content, "text_element_style": {}}}], + "style": {}, + }, + } + + +def _normalize_title(t: str) -> str: + s = (t or "").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 parse_title(md_text: str, fallback: str) -> str: + for line in md_text.splitlines(): + if line.startswith("# "): + return line[2:].strip() + return fallback + + +def find_existing(parent_token: str, title: str, headers: dict) -> tuple[str | None, str | None, str | None]: + 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}", + headers=headers, + timeout=30, + ) + j = r.json() + if j.get("code") != 0: + raise RuntimeError(f"get_node 失败: {j.get('msg')}") + return j["data"]["node"].get("obj_token") or node_token + + +def clear_doc_blocks(doc_token: str, headers: dict) -> bool: + # 该接口在部分租户会 field validation failed,失败就返回 False(后续走追加) + all_items = [] + page_token = None + while True: + params = {"page_size": 100} + if page_token: + params["page_token"] = page_token + r = requests.get( + f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks", + headers=headers, + params=params, + timeout=30, + ) + j = r.json() + if j.get("code") != 0: + return False + data = j.get("data", {}) or {} + all_items.extend(data.get("items", []) or []) + page_token = data.get("page_token") + if not page_token: + break + 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 True + for i in range(0, len(child_ids), 50): + batch = child_ids[i : i + 50] + rd = requests.delete( + f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks/{doc_token}/children/batch_delete", + headers=headers, + json={"block_id_list": batch}, + timeout=30, + ) + if rd.json().get("code") != 0: + return False + return True + + +def create_node(parent_token: str, title: str, headers: dict) -> tuple[str, str]: + 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: + raise RuntimeError(f"get_node 失败: {j.get('msg')}") + 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: + raise RuntimeError("无法获取 space_id") + cr = requests.post( + f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{space_id}/nodes", + headers=headers, + json={"parent_node_token": parent_token, "obj_type": "docx", "node_type": "origin", "title": title}, + timeout=30, + ) + cj = cr.json() + if cj.get("code") != 0: + raise RuntimeError(f"创建节点失败: {cj.get('msg')}") + node = cj["data"]["node"] + return (node.get("obj_token") or node.get("node_token")), node.get("node_token") + + +def write_raw_md_lines(doc_token: str, headers: dict, md_text: str) -> None: + lines = md_text.splitlines() + blocks = [_text_block(line) for line in lines if line is not None] + for i in range(0, len(blocks), 50): + batch = blocks[i : i + 50] + r = requests.post( + f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks/{doc_token}/children", + headers=headers, + json={"children": batch}, + timeout=30, + ) + j = r.json() + if j.get("code") != 0: + raise RuntimeError(f"写入失败: {j.get('msg')}") + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--parent", default="MyvRwCVNSiTg5ok6e3fc6uA5nHg", help="Wiki 父节点 token") + ap.add_argument("--md", required=True, help="Markdown 文件路径") + ap.add_argument("--title", default="", help="可选,覆盖 MD 第一行标题") + args = ap.parse_args() + + md_path = Path(args.md).expanduser().resolve() + if not md_path.exists(): + raise SystemExit(f"❌ MD 不存在: {md_path}") + md_text = md_path.read_text(encoding="utf-8") + title = args.title.strip() if args.title.strip() else parse_title(md_text, md_path.stem) + + token = fwd.get_token(args.parent) + if not token: + raise SystemExit("❌ Token 无效,请先授权") + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + print("=" * 50) + print("📤 MD 原文直传飞书(不转 JSON)") + print(f"父节点: {args.parent}") + print(f"标题: {title}") + print(f"文件: {md_path}") + print("=" * 50) + + doc_token, node_token, hit_title = find_existing(args.parent, title, headers) + if doc_token and node_token: + print(f"📋 命中相似标题,更新: {hit_title}") + if clear_doc_blocks(doc_token, headers): + print("✅ 已清空原内容") + else: + print("⚠️ 清空失败,改为追加") + else: + doc_token, node_token = create_node(args.parent, title, headers) + print(f"✅ 新建文档: {node_token}") + + write_raw_md_lines(doc_token, headers, md_text) + print(f"✅ 上传完成: https://cunkebao.feishu.cn/wiki/{node_token}") + + +if __name__ == "__main__": + main() + diff --git a/SKILL_REGISTRY.md b/SKILL_REGISTRY.md index b48c8f30..518e01d4 100644 --- a/SKILL_REGISTRY.md +++ b/SKILL_REGISTRY.md @@ -51,7 +51,7 @@ | W04 | 自动记忆管理 | 水溪 | 记忆、存入记忆 | `02_卡人(水)/水溪_整理归档/自动记忆管理/SKILL.md` | 长期记忆写入与检索 | | W05 | 需求拆解与计划制定 | 水泉 | 需求拆解、任务分析 | `02_卡人(水)/水泉_规划拆解/需求拆解与计划制定/SKILL.md` | 大需求拆成可执行步骤 | | W06 | 任务规划 | 水泉 | 任务规划、制定计划 | `02_卡人(水)/水泉_规划拆解/任务规划/SKILL.md` | 制定执行计划与排期 | -| W07 | 飞书管理 | 水桥 | 飞书日志、写入飞书、飞书文章上传、MD转飞书JSON、同标题更新飞书、飞书上传图片 | `02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md` | 飞书日志/文档自动化(统一文章上传入口) | +| W07 | 飞书管理 | 水桥 | 飞书日志、写入飞书 | `02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md` | 飞书日志/文档自动化 | | W08 | 智能纪要 | 水桥 | 会议纪要、产研纪要、**飞书妙记、飞书链接、妙记下载、第几场、指定场次、批量下载妙记、cunkebao.feishu.cn、meetings.feishu.cn/minutes** | `02_卡人(水)/水桥_平台对接/智能纪要/SKILL.md` | 会议录音转结构化纪要;飞书妙记识别与下载(单条/批量),完毕用复盘格式回复 | | W09 | 小程序管理 | 水桥 | 小程序、微信小程序 | `02_卡人(水)/水桥_平台对接/小程序管理/SKILL.md` | 微信小程序发布与维护 | | W10 | Soul文章上传 | 水桥 | **Soul文章上传、Soul派对文章、第9章上传、soul上传** | `02_卡人(水)/水桥_平台对接/Soul文章上传/SKILL.md` | 《一场soul的创业实验》第9章文章写好后上传到小程序,id 已存在则更新不重复 | diff --git a/运营中枢/参考资料/卡若AI_客户端配置快速说明.md b/运营中枢/参考资料/卡若AI_客户端配置快速说明.md new file mode 100644 index 00000000..fa4447bd --- /dev/null +++ b/运营中枢/参考资料/卡若AI_客户端配置快速说明.md @@ -0,0 +1,64 @@ +# 卡若AI 客户端配置快速说明(Cursor/同类工具) + +> 目标:3 分钟完成接入。 +> 适用:Cursor、Cherry Studio、LobeChat、Claude 插件、自建 Agent(支持 OpenAI 兼容协议)。 + +--- + +## 一、统一配置参数 + +- Base URL:`https://kr-ai.quwanzhi.com/v1` +- API Key:`<部门分配的 dept_key>` +- Model:`karuo-ai` + +注意: + +- Base URL 不要加结尾 `/` +- 必须使用 `https` + +--- + +## 二、Cursor 配置步骤 + +1. 打开 `Settings -> API Keys` +2. 在 `OpenAI API Key` 填入 `dept_key` +3. 打开 `Override OpenAI Base URL` +4. 填入:`https://kr-ai.quwanzhi.com/v1` +5. 重启 Cursor 后测试一次对话 + +--- + +## 三、其它同类工具配置步骤 + +1. 找到 OpenAI/自定义模型配置页 +2. 填 Base URL:`https://kr-ai.quwanzhi.com/v1` +3. 填 API Key:`dept_key` +4. 模型填:`karuo-ai` +5. 先关闭流式输出联调,联通后再开启 + +--- + +## 四、联调命令(先测通再用) + +```bash +# 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":"测试连通"}]}' +``` + +--- + +## 五、常见问题(简版) + +- 报 `Provider Error`:先检查 Base URL 是否为 `https://kr-ai.quwanzhi.com/v1` +- 报 `401 invalid api key`:Key 错误或过期,重新申请部门 key +- 报 `502`:服务链路异常,联系运维检查 Nginx/frp/NAS 网关 + diff --git a/运营中枢/参考资料/卡若AI外网化与外部调用方案.md b/运营中枢/参考资料/卡若AI外网化与外部调用方案.md index df8d3afd..d988fb68 100644 --- a/运营中枢/参考资料/卡若AI外网化与外部调用方案.md +++ b/运营中枢/参考资料/卡若AI外网化与外部调用方案.md @@ -1,337 +1,186 @@ -# 卡若AI 接口全链路使用说明书(部署 + 配置 + 调用 + 运维) +# 卡若AI 外网化与外部调用方案 -> 适用对象:卡若AI 内部团队、科室/部门调用方、外部技术合作方 -> 目标:把卡若AI网关以标准 API 形式稳定对外,支持 Cursor/OpenAI 兼容客户端与脚本调用 -> 版本:2.0 | 更新:2026-02-24 +> 目标:让卡若AI 可从外网访问,其他 AI 或任意终端用「一句话/一个命令」即可按卡若AI 的思考逻辑调用并生成回复。 +> 版本:1.0 | 更新:2026-02-17 --- -## 1. 总览(先看这个) +## 一、目标与效果 -当前生产链路为: - -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` +| 目标 | 说明 | +|:---|:---| +| 外网可访问 | 不限于本机,任意网络通过域名或 IP:端口 访问卡若AI。 | +| 按卡若AI 思考逻辑生成 | 每次请求走:先思考 → 查 SKILL_REGISTRY → 读对应 SKILL → 生成回复 → 带复盘格式。 | +| 其他 AI 可集成 | 其他 AI(Cursor、Claude、GPT、自建 Bot)执行一条命令或请求一个 URL,即「用卡若AI 能力」完成对话。 | +| 最终交付 | 给你:**可执行命令**、**调用链接/域名**,在 Cursor 或其它 AI 里输入即用。 | --- -## 2. 架构与职责 +## 二、实现形式(架构) -### 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` - -### 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. 目录与关键文件 - -网关代码目录: - -- `运营中枢/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` - -NAS 部署目录(生产): - -- `/volume1/docker/karuo-ai-deploy/karuo-ai/运营中枢/scripts/karuo_ai_gateway/` - ---- - -## 4. 首次部署步骤(全链路) - -## 4.1 NAS 部署网关(业务服务) - -1. 准备代码目录(推荐从 NAS 本机 Gitea 拉取) -2. 进入网关目录,准备 `.env`: - -```bash -KARUO_GATEWAY_SALT=请填随机长串 -OPENAI_API_KEY=请填模型服务Key -OPENAI_API_BASE=请填兼容地址(如 https://api.openai.com/v1) -OPENAI_MODEL=请填模型名 +``` +外部(其他 AI / 用户) + │ + │ HTTP POST /chat 或 打开网页 + ▼ +┌─────────────────────────────────────────────────────┐ +│ 卡若AI 网关(API 服务) │ +│ · 接收 prompt │ +│ · 加载 BOOTSTRAP + SKILL_REGISTRY │ +│ · 匹配技能 → 读 SKILL.md │ +│ · 调用 LLM(本地或云端 API)按卡若AI 流程生成 │ +│ · 返回:思考 + 执行摘要 + 复盘块 │ +└─────────────────────────────────────────────────────┘ + │ + │ 部署在:宝塔服务器(推荐,固定域名)或 本机 + 内网穿透 + ▼ + 外网域名:https://kr-ai.quwanzhi.com(标准方案见下) ``` -3. 启动容器(建议 compose): +**两种使用方式:** -- 对外监听:`127.0.0.1:8000` -- 容器内启动:`uvicorn main:app --host 0.0.0.0 --port 8000` - -4. 本机验证: - -```bash -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` +1. **API 调用**:其他 AI 或脚本向 `POST /v1/chat` 发 `{"prompt": "用户问题"}`,拿 JSON 里的回复(含复盘)。 +2. **网页对话**:浏览器打开同一服务的 `/` 或 `/chat`,输入问题,页面上展示卡若AI 风格回复。 --- -## 5. 配置说明(gateway.yaml) +## 三、部署方式二选一 -示例结构: +### 方式 A:宝塔服务器 + 固定域名(推荐,替代 ngrok) -```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 -``` +- **域名**:**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'` -注意事项: +### 方式 B:本机 + 内网穿透(临时) -- `gateway.yaml` 必须是合法 YAML,尤其是 `tenants` 缩进 -- 明文 key 不写入仓库,只写 hash -- `KARUO_GATEWAY_SALT` 必须存在,否则所有 key 校验失败 +- 在本机运行卡若AI 网关,用 ngrok/cloudflared 得到临时 URL;本机关机则不可访问。仅作临时调试用。 --- -## 6. 新增科室/部门(标准 SOP) +## 四、网关脚本与运行方式 -1. 设置环境变量: +网关代码放在:**`运营中枢/scripts/karuo_ai_gateway/`**(`main.py` + `requirements.txt` + `README.md`)。 +运行前: + +1. 安装依赖:`pip install fastapi uvicorn httpx`(若用 OpenAI 兼容接口,再装 `openai`)。 +2. 配置环境变量(可选):`OPENAI_API_KEY` 或本地模型地址,用于实际生成回复。 +3. 启动: ```bash -export KARUO_GATEWAY_SALT="你的随机盐" +cd /Users/karuo/Documents/个人/卡若AI/运营中枢/scripts/karuo_ai_gateway +uvicorn main:app --host 0.0.0.0 --port 8000 ``` -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` 验证 +- 本机访问: 可调试接口。 +- 外网访问:在方式 A 或 B 下用你得到的**域名或 IP:端口**替换下面示例中的 `YOUR_DOMAIN`。 --- -## 7. 调用说明(给客户端/系统) +## 四点五、接口配置化(科室/部门可复制) -## 7.1 通用调用(OpenAI 兼容) +> 目标:让以后任何科室/部门/合作方都能“拿到一套配置 + 一个 key”,直接调用卡若AI 网关,不需要改代码。 -推荐 Base URL: +### 你需要提前准备什么(一次性) -- `https://kr-ai.quwanzhi.com/v1` +1. **一个 salt**(只放环境变量,不写入仓库):`KARUO_GATEWAY_SALT` +2. (可选)如果要真实 LLM 输出:`OPENAI_API_KEY`(以及 `OPENAI_API_BASE`、`OPENAI_MODEL`) +3. 外网场景:域名/反代已就绪(宝塔/Nginx)或 ngrok 临时暴露 -鉴权: +### 配置文件在哪里 -- `Authorization: Bearer ` +- 示例:`运营中枢/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 命令**: ```bash -curl -sS https://kr-ai.quwanzhi.com/v1/chat/completions \ +curl -s -X POST "https://kr-ai.quwanzhi.com/v1/chat" \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{ - "model":"karuo-ai", - "messages":[{"role":"user","content":"帮我做一份本周复盘"}] - }' + -d '{"prompt":"你的问题"}' | jq -r '.reply' ``` -## 7.2 Cursor 配置(重点) +### 3. 在 Cursor 里「用卡若AI 完成本对话」的固定说明(复制即用) -1. `OpenAI API Key`:填部门 key -2. `Override OpenAI Base URL`:`https://kr-ai.quwanzhi.com/v1` -3. 不要在 Base URL 末尾加 `/` -4. 改完后重启 Cursor 一次 +把下面一段存成 Cursor 的规则或对话开头说明,即可在任意对话里「切到卡若AI」: -## 7.3 其它同类工具配置(OpenAI 兼容客户端) +``` +当用户说「用卡若AI」「交给卡若AI」「调用卡若AI」或明确要求用卡若AI 回答时: +1. 将用户当前问题作为 prompt。 +2. 请求:POST https://kr-ai.quwanzhi.com/v1/chat,Body: {"prompt": "<用户问题>"}。 +3. 将响应 JSON 中的 reply 字段内容(含卡若复盘)完整展示给用户,作为卡若AI 的回复。 +``` -适用于:Claude Desktop(OpenAI兼容插件)、Cherry Studio、LobeChat、自建 Agent、任意支持 OpenAI API 的工具。 +--- -统一填写: +## 六、小结 -1. `Base URL`:`https://kr-ai.quwanzhi.com/v1` -2. `API Key`:`` -3. `Model`:`karuo-ai`(或工具允许的自定义名) -4. 关闭流式可先联调,联通后再开流式 +| 项目 | 内容 | +|:---|:---| +| 实现形式 | 卡若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` | -联调顺序: +--- -1. 先测 `GET /v1/models` 是否返回模型列表 -2. 再测 `POST /v1/chat/completions` 是否返回 `choices` -3. 最后在工具内发一条真实问题验证 - -最简请求模板: +## 七、在 Cursor 里「用卡若AI 完成本对话」的一条执行命令 +**方式 1:直接 curl(终端执行)** ```bash -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":"测试连通"}]}' +curl -s -X POST "https://kr-ai.quwanzhi.com/v1/chat" -H "Content-Type: application/json" -d '{"prompt":"这里填你要问卡若AI的问题"}' | jq -r '.reply' ``` ---- - -## 8. 健康检查与联调命令 - -```bash -# 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 的回复完整展示给用户。 ``` ---- - -## 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 兼容的全链路部署与排障说明。 +**阿里云解析**:已通过脚本 `01_卡资(金)/金仓_存储备份/服务器管理/scripts/阿里云DNS_添加kr-ai解析.py` 添加 A 记录 kr-ai -> 43.139.27.93。完成服务器部署后即可长期使用;后续需要新内网穿透/新域名时,按「内网穿透与域名配置_卡若AI标准方案」生成子域名并配置,不再询问用户。 diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 9093d011..500e2dcf 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -131,3 +131,4 @@ | 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 个 | | 2026-02-25 05:57:01 | 🔄 卡若AI 同步 2026-02-25 05:56 | 更新:水桥平台对接、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 | +| 2026-02-25 06:51:14 | 🔄 卡若AI 同步 2026-02-25 06:51 | 更新:水桥平台对接、水溪整理归档、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 0a2833e4..f83a4b77 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -134,3 +134,4 @@ | 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) | | 2026-02-25 05:57:01 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 05:56 | 更新:水桥平台对接、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-02-25 06:51:14 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 06:51 | 更新:水桥平台对接、水溪整理归档、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |