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) |