From bd89d17d90e0a3e7d4513bfe21cf2c1637334e1b Mon Sep 17 00:00:00 2001 From: karuo Date: Wed, 25 Feb 2026 13:21:25 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E5=8D=A1=E8=8B=A5AI=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=202026-02-25=2013:21=20|=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9A=E8=BF=90=E8=90=A5=E4=B8=AD=E6=9E=A2=E3=80=81=E8=BF=90?= =?UTF-8?q?=E8=90=A5=E4=B8=AD=E6=9E=A2=E5=B7=A5=E4=BD=9C=E5=8F=B0=20|=20?= =?UTF-8?q?=E6=8E=92=E9=99=A4=20>20MB:=2013=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 运营中枢/scripts/karuo_ai_gateway/main.py | 57 ++++++++++++++++++++--- 运营中枢/工作台/gitea_push_log.md | 1 + 运营中枢/工作台/代码管理.md | 1 + 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/运营中枢/scripts/karuo_ai_gateway/main.py b/运营中枢/scripts/karuo_ai_gateway/main.py index d6048828..d2787e9b 100644 --- a/运营中枢/scripts/karuo_ai_gateway/main.py +++ b/运营中枢/scripts/karuo_ai_gateway/main.py @@ -270,6 +270,27 @@ def _is_unusable_llm_reply(text: str) -> bool: return False +def _direct_reply_for_simple_prompt(prompt: str) -> str: + s = (prompt or "").strip().lower().replace("?", "?") + if s in {"你是谁", "你是谁?", "who are you", "你叫什么", "你叫什么名字"}: + return "我是卡若AI,你的私域运营与项目落地数字管家。你给目标,我直接给可执行结果。" + return "" + + +def _looks_mismatched_reply(prompt: str, reply: str) -> bool: + p = (prompt or "").strip() + r = (reply or "").strip() + if not p or not r: + return False + # 短问题却输出“根据摘要/任务拆解”等模板,判定为答偏 + if len(p) <= 24 and ("根据摘要" in r or "任务拆解" in r or "思考与拆解" in r): + return True + # 身份问题却自报成其他助手 + if ("你是谁" in p or "who are you" in p.lower()) and ("我是 v0" in r or "vercel 的 ai 助手" in r.lower()): + return True + return False + + def _send_provider_alert(cfg: Dict[str, Any], errors: List[str], prompt: str, matched_skill: str, skill_path: str) -> None: """ 当所有 LLM 接口都失败时,发邮件告警(支持 QQ SMTP)。 @@ -339,11 +360,17 @@ def _send_provider_alert(cfg: Dict[str, Any], errors: List[str], prompt: str, ma def build_reply_with_llm(prompt: str, cfg: Dict[str, Any], matched_skill: str, skill_path: str) -> str: """调用 LLM 生成回复(OpenAI 兼容)。未配置则返回模板回复。""" - bootstrap = load_bootstrap() + direct = _direct_reply_for_simple_prompt(prompt) + if direct: + return direct + system = ( - f"你是卡若AI。请严格按以下规则回复:\n\n{bootstrap[:4000]}\n\n" - f"当前匹配技能:{matched_skill},路径:{skill_path}。" - "先简短思考并输出,再给执行要点,最后必须带「[卡若复盘]」块(含目标·结果·达成率、过程 1 2 3、反思、总结、下一步)。" + "你是卡若AI。回答要求:\n" + "1) 只回答用户最后一个真实问题,不要复述系统标签、上下文注入块、历史摘要。\n" + "2) 用简体中文,大白话,先给结论,再给最多3步可执行建议。\n" + "3) 问题很简单时(如“你是谁”),直接1-3句回答,不要输出“思考与拆解/任务拆解”等模板。\n" + "4) 保持可靠、务实,不编造未发生的执行结果。\n" + f"当前匹配技能:{matched_skill}({skill_path})。" ) llm_cfg = _llm_settings(cfg) providers = _build_provider_queue(llm_cfg) @@ -366,7 +393,7 @@ def build_reply_with_llm(prompt: str, cfg: Dict[str, Any], matched_skill: str, s if r.status_code == 200: data = r.json() reply = data["choices"][0]["message"]["content"] - if _is_unusable_llm_reply(reply): + if _is_unusable_llm_reply(reply) or _looks_mismatched_reply(prompt, reply): errors.append(f"provider#{idx} unusable_reply={reply[:120]}") continue return reply @@ -427,6 +454,7 @@ _CONTEXT_TEXT_NOISE = ( "note: these files may or may not be relevant", "workspace paths:", "is directory a git repo:", + "previous conversation summary", ) @@ -480,6 +508,21 @@ def _content_to_text(content: Any) -> str: return "" +def _extract_user_query_from_text(text: str) -> str: + """ + 如果文本里带 ...,优先抽取这个真实问题。 + """ + s = (text or "").strip() + if not s: + return "" + m = re.findall(r"\s*(.*?)\s*", s, flags=re.IGNORECASE | re.DOTALL) + if m: + picked = (m[-1] or "").strip() + if picked: + return picked + return s + + def _messages_to_prompt(messages: List[Dict[str, Any]]) -> str: """ 优先取最后一条 user 消息;否则拼接全部文本。 @@ -488,7 +531,7 @@ def _messages_to_prompt(messages: List[Dict[str, Any]]) -> str: chunks: List[str] = [] for m in messages or []: role = str(m.get("role", "")).strip() - content = _content_to_text(m.get("content", "")) + content = _extract_user_query_from_text(_content_to_text(m.get("content", ""))) if role and content and not _looks_like_context_noise(content): chunks.append(f"{role}: {content}") if role == "user" and content and not _looks_like_context_noise(content): @@ -531,7 +574,7 @@ async def _fallback_prompt_from_request_body(request: Request) -> str: continue if str(m.get("role", "")).strip().lower() != "user": continue - txt = _content_to_text(m.get("content", "")) + txt = _extract_user_query_from_text(_content_to_text(m.get("content", ""))) if txt and not _looks_like_context_noise(txt): user_texts.append(txt) if user_texts: diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index b09ab288..f53d2f7f 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -145,3 +145,4 @@ | 2026-02-25 12:11:44 | 🔄 卡若AI 同步 2026-02-25 12:11 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | | 2026-02-25 12:13:11 | 🔄 卡若AI 同步 2026-02-25 12:13 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | | 2026-02-25 12:27:51 | 🔄 卡若AI 同步 2026-02-25 12:27 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | +| 2026-02-25 12:30:32 | 🔄 卡若AI 同步 2026-02-25 12:30 | 更新:运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index dd5ecac5..cd16af6b 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -148,3 +148,4 @@ | 2026-02-25 12:11:44 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 12:11 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-25 12:13:11 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 12:13 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-25 12:27:51 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 12:27 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-02-25 12:30:32 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 12:30 | 更新:运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |