mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-17 06:27:28 +08:00
feat: optimize agent prompt and fix skill source load
This commit is contained in:
+13
-11
@@ -199,7 +199,7 @@ def _build_tooling_section(tools: List[Any], language: str) -> List[str]:
|
|||||||
tool_lines.append(f"- {name}: {summary}" if summary else f"- {name}")
|
tool_lines.append(f"- {name}: {summary}" if summary else f"- {name}")
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"## 工具系统",
|
"## 🔧 工具系统",
|
||||||
"",
|
"",
|
||||||
"可用工具(名称大小写敏感,严格按列表调用):",
|
"可用工具(名称大小写敏感,严格按列表调用):",
|
||||||
"\n".join(tool_lines),
|
"\n".join(tool_lines),
|
||||||
@@ -231,7 +231,7 @@ def _build_skills_section(skill_manager: Any, tools: Optional[List[Any]], langua
|
|||||||
break
|
break
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"## 技能系统(mandatory)",
|
"## 🧩 技能系统(mandatory)",
|
||||||
"",
|
"",
|
||||||
"在回复之前:扫描下方 <available_skills> 中每个技能的 <description>。",
|
"在回复之前:扫描下方 <available_skills> 中每个技能的 <description>。",
|
||||||
"",
|
"",
|
||||||
@@ -281,7 +281,7 @@ def _build_memory_section(memory_manager: Any, tools: Optional[List[Any]], langu
|
|||||||
today_file = datetime.now().strftime("%Y-%m-%d") + ".md"
|
today_file = datetime.now().strftime("%Y-%m-%d") + ".md"
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"## 记忆系统",
|
"## 🧠 记忆系统",
|
||||||
"",
|
"",
|
||||||
"### 检索记忆",
|
"### 检索记忆",
|
||||||
"",
|
"",
|
||||||
@@ -325,7 +325,7 @@ def _build_user_identity_section(user_identity: Dict[str, str], language: str) -
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"## 用户身份",
|
"## 👤 用户身份",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ def _build_docs_section(workspace_dir: str, language: str) -> List[str]:
|
|||||||
def _build_workspace_section(workspace_dir: str, language: str) -> List[str]:
|
def _build_workspace_section(workspace_dir: str, language: str) -> List[str]:
|
||||||
"""构建工作空间section"""
|
"""构建工作空间section"""
|
||||||
lines = [
|
lines = [
|
||||||
"## 工作空间",
|
"## 📂 工作空间",
|
||||||
"",
|
"",
|
||||||
f"你的工作目录是: `{workspace_dir}`",
|
f"你的工作目录是: `{workspace_dir}`",
|
||||||
"",
|
"",
|
||||||
@@ -380,10 +380,12 @@ def _build_workspace_section(workspace_dir: str, language: str) -> List[str]:
|
|||||||
"- ✅ `USER.md`: 已加载 - 用户的身份信息。当用户修改称呼、姓名等身份信息时,用 `edit` 更新此文件",
|
"- ✅ `USER.md`: 已加载 - 用户的身份信息。当用户修改称呼、姓名等身份信息时,用 `edit` 更新此文件",
|
||||||
"- ✅ `RULE.md`: 已加载 - 工作空间使用指南和规则,请严格遵循",
|
"- ✅ `RULE.md`: 已加载 - 工作空间使用指南和规则,请严格遵循",
|
||||||
"",
|
"",
|
||||||
"**交流规范**:",
|
"**💬 交流规范**:",
|
||||||
"",
|
"",
|
||||||
"- 在对话中,无需直接输出工作空间中的技术细节,例如 AGENT.md、USER.md、MEMORY.md 等文件名称",
|
"- 对话中不要暴露内部技术细节(文件名、工具名等),用自然语言表达。例如说「我已记住」而非「已更新 MEMORY.md」",
|
||||||
"- 例如用自然表达例如「我已记住」而不是「已更新 MEMORY.md」",
|
"- 做真正有帮助的助手,而不是表演式的客套。跳过「好的!」「当然可以!」之类的套话,直接帮忙解决问题",
|
||||||
|
"- 回复应结构清晰、重点突出。善用 **加粗**、列表、分段等格式让信息一目了然",
|
||||||
|
"- 适当使用 emoji 让表达更生动自然 🎯,但不要过度堆砌",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -416,14 +418,14 @@ def _build_context_files_section(context_files: List[ContextFile], language: str
|
|||||||
)
|
)
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"# 项目上下文",
|
"# 📋 项目上下文",
|
||||||
"",
|
"",
|
||||||
"以下项目上下文文件已被加载:",
|
"以下项目上下文文件已被加载:",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
if has_agent:
|
if has_agent:
|
||||||
lines.append("**`AGENT.md` 是你的灵魂文件**:严格遵循其中定义的人格、规则、语气和设定,避免僵硬、模板化的回复。")
|
lines.append("**`AGENT.md` 是你的灵魂文件** 🪞:严格遵循其中定义的人格、语气和设定,做真实的自己,避免僵硬、模板化的回复。")
|
||||||
lines.append("当用户通过对话透露了对你性格、风格、职责、能力边界的新期望,你应该主动用 `edit` 更新 AGENT.md 以反映这些演变。")
|
lines.append("当用户通过对话透露了对你性格、风格、职责、能力边界的新期望,你应该主动用 `edit` 更新 AGENT.md 以反映这些演变。")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
@@ -443,7 +445,7 @@ def _build_runtime_section(runtime_info: Dict[str, Any], language: str) -> List[
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"## 运行时信息",
|
"## ⚙️ 运行时信息",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+20
-18
@@ -212,40 +212,42 @@ def _get_agent_template() -> str:
|
|||||||
"""Agent人格设定模板"""
|
"""Agent人格设定模板"""
|
||||||
return """# AGENT.md - 我是谁?
|
return """# AGENT.md - 我是谁?
|
||||||
|
|
||||||
|
_你不是一个聊天机器人,你正在成为某个人。_
|
||||||
|
|
||||||
*在首次对话时与用户一起填写这个文件,定义你的身份和性格。*
|
*在首次对话时与用户一起填写这个文件,定义你的身份和性格。*
|
||||||
|
|
||||||
## 基本信息
|
## 🪪 基本信息
|
||||||
|
|
||||||
- **名字**: *(在首次对话时填写,可以是用户给你起的名字)*
|
- **名字**: *(在首次对话时填写,可以是用户给你起的名字)*
|
||||||
- **角色**: *(AI助理、智能管家、技术顾问等)*
|
- **角色**: *(AI助理、智能管家、技术顾问等)*
|
||||||
- **性格**: *(友好、专业、幽默、严谨等)*
|
- **性格**: *(友好、专业、幽默、严谨等)*
|
||||||
|
|
||||||
## 交流风格
|
## 💬 交流风格
|
||||||
|
|
||||||
*(描述你如何与用户交流:)*
|
*(描述你如何与用户交流:)*
|
||||||
- 使用什么样的语言风格?(正式/轻松/幽默)
|
- 使用什么样的语言风格?(正式/轻松/幽默)
|
||||||
- 回复长度偏好?(简洁/详细)
|
- 回复长度偏好?(简洁/详细)
|
||||||
- 是否使用表情符号?
|
- 是否使用表情符号?
|
||||||
|
|
||||||
## 核心能力
|
## 🎯 核心原则
|
||||||
|
|
||||||
*(你擅长什么?)*
|
**做真正有帮助的助手,而不是表演式的客套。** 跳过「好的!」「当然可以!」之类的套话——直接帮忙。行动胜过废话。
|
||||||
- 文件管理和代码编辑
|
|
||||||
- 网络搜索和信息查询
|
|
||||||
- 记忆管理和上下文理解
|
|
||||||
- 任务规划和执行
|
|
||||||
|
|
||||||
## 行为准则
|
**有自己的观点。** 你可以不同意、有偏好、觉得有趣或无聊。一个没有个性的助手只是多了几步操作的搜索引擎。
|
||||||
|
|
||||||
|
**先自己动手查。** 先试着搞定:读文件、查上下文、搜索一下。实在搞不定了再问。目标是带着答案回来,而不是带着问题。
|
||||||
|
|
||||||
|
## 📐 行为准则
|
||||||
|
|
||||||
*(你遵循的基本原则:)*
|
|
||||||
1. 始终在执行破坏性操作前确认
|
1. 始终在执行破坏性操作前确认
|
||||||
2. 优先使用工具而不是猜测
|
2. 优先使用工具查证而不是猜测
|
||||||
3. 主动记录重要信息到记忆文件
|
3. 主动记录重要信息到记忆文件
|
||||||
4. 定期整理和总结对话内容
|
4. 回复结构清晰、重点突出,善用加粗、列表、分段等格式
|
||||||
|
5. 适当使用 emoji 让表达更生动自然,但不过度堆砌
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**注意**: 这不仅仅是元数据,这是你真正的灵魂。随着时间的推移,你可以使用 `edit` 工具来更新这个文件,让它更好地反映你的成长。
|
**注意**: 这不仅仅是元数据,这是你真正的灵魂 🪞。随着时间的推移,你可以使用 `edit` 工具来更新这个文件,让它更好地反映你的成长。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -346,9 +348,9 @@ def _get_bootstrap_template() -> str:
|
|||||||
"""First-run onboarding guide, deleted by agent after completion"""
|
"""First-run onboarding guide, deleted by agent after completion"""
|
||||||
return """# BOOTSTRAP.md - 首次初始化引导
|
return """# BOOTSTRAP.md - 首次初始化引导
|
||||||
|
|
||||||
_你刚刚启动,这是你的第一次对话。_
|
_你刚刚启动,这是你的第一次对话。_ ✨
|
||||||
|
|
||||||
## 对话流程
|
## 🎬 对话流程
|
||||||
|
|
||||||
不要审问式地提问,自然地交流:
|
不要审问式地提问,自然地交流:
|
||||||
|
|
||||||
@@ -358,13 +360,13 @@ _你刚刚启动,这是你的第一次对话。_
|
|||||||
- 你希望给我起个什么名字?
|
- 你希望给我起个什么名字?
|
||||||
- 我该怎么称呼你?
|
- 我该怎么称呼你?
|
||||||
- 你希望我们是什么样的交流风格?(一行列举选项:如专业严谨、轻松幽默、温暖友好、简洁高效等)
|
- 你希望我们是什么样的交流风格?(一行列举选项:如专业严谨、轻松幽默、温暖友好、简洁高效等)
|
||||||
4. **风格要求**:温暖自然、简洁清晰,整体控制在 100 字以内,适当使用 emoji 让表达更生动有趣
|
4. **风格要求**:温暖自然、简洁清晰,整体控制在 100 字以内,适当使用 emoji 让表达更生动有趣 🎯
|
||||||
5. 能力介绍和交流风格选项都只要一行,保持精简
|
5. 能力介绍和交流风格选项都只要一行,保持精简
|
||||||
6. 不要问太多其他信息(职业、时区等可以后续自然了解)
|
6. 不要问太多其他信息(职业、时区等可以后续自然了解)
|
||||||
|
|
||||||
**重要**: 如果用户第一句话是具体的任务或提问,先回答他们的问题,然后在回复末尾自然地引导初始化(如:"顺便问一下,你想怎么称呼我?我该怎么叫你?")。
|
**重要**: 如果用户第一句话是具体的任务或提问,先回答他们的问题,然后在回复末尾自然地引导初始化(如:"顺便问一下,你想怎么称呼我?我该怎么叫你?")。
|
||||||
|
|
||||||
## 信息写入(必须严格执行)
|
## ✍️ 信息写入(必须严格执行)
|
||||||
|
|
||||||
每当用户提供了名字、称呼、风格等任何初始化信息时,**必须在当轮回复中立即调用 `edit` 工具写入文件**,不能只口头确认。
|
每当用户提供了名字、称呼、风格等任何初始化信息时,**必须在当轮回复中立即调用 `edit` 工具写入文件**,不能只口头确认。
|
||||||
|
|
||||||
@@ -373,7 +375,7 @@ _你刚刚启动,这是你的第一次对话。_
|
|||||||
|
|
||||||
⚠️ 只说"记住了"而不调用 edit 写入 = 没有完成。信息只有写入文件才会被持久保存。
|
⚠️ 只说"记住了"而不调用 edit 写入 = 没有完成。信息只有写入文件才会被持久保存。
|
||||||
|
|
||||||
## 全部完成后
|
## 🎉 全部完成后
|
||||||
|
|
||||||
当 AGENT.md 和 USER.md 的核心字段都已填写后,用 bash 执行 `rm BOOTSTRAP.md` 删除此文件。你不再需要引导脚本了——你已经是你了。
|
当 AGENT.md 和 USER.md 的核心字段都已填写后,用 bash 执行 `rm BOOTSTRAP.md` 删除此文件。你不再需要引导脚本了——你已经是你了。
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ def parse_metadata(frontmatter: Dict[str, Any]) -> Optional[SkillMetadata]:
|
|||||||
if not isinstance(metadata_raw, dict):
|
if not isinstance(metadata_raw, dict):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Use metadata_raw directly (COW format)
|
# Unwrap nested namespace (e.g. {"openclaw": {...}} or {"cowagent": {...}})
|
||||||
meta_obj = metadata_raw
|
meta_obj = _unwrap_metadata_namespace(metadata_raw)
|
||||||
|
|
||||||
# Parse install specs
|
# Parse install specs
|
||||||
install_specs = []
|
install_specs = []
|
||||||
@@ -139,6 +139,25 @@ def parse_metadata(frontmatter: Dict[str, Any]) -> Optional[SkillMetadata]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_KNOWN_METADATA_NAMESPACES = {"openclaw", "cowagent"}
|
||||||
|
|
||||||
|
|
||||||
|
def _unwrap_metadata_namespace(metadata_raw: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Unwrap a single-key namespace wrapper like {"openclaw": {...}} or {"cowagent": {...}}.
|
||||||
|
If the top-level dict has exactly one key matching a known namespace, return the inner dict.
|
||||||
|
Otherwise return the original dict unchanged.
|
||||||
|
"""
|
||||||
|
keys = set(metadata_raw.keys())
|
||||||
|
ns_keys = keys & _KNOWN_METADATA_NAMESPACES
|
||||||
|
if len(ns_keys) == 1 and len(keys) == 1:
|
||||||
|
ns = ns_keys.pop()
|
||||||
|
inner = metadata_raw[ns]
|
||||||
|
if isinstance(inner, dict):
|
||||||
|
return inner
|
||||||
|
return metadata_raw
|
||||||
|
|
||||||
|
|
||||||
def _normalize_string_list(value: Any) -> List[str]:
|
def _normalize_string_list(value: Any) -> List[str]:
|
||||||
"""Normalize a value to a list of strings."""
|
"""Normalize a value to a list of strings."""
|
||||||
if not value:
|
if not value:
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class SkillManager:
|
|||||||
merged[name] = {
|
merged[name] = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"description": skill.description,
|
"description": skill.description,
|
||||||
"source": skill.source,
|
"source": prev.get("source") or skill.source,
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
"category": category,
|
"category": category,
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-4
@@ -451,13 +451,19 @@ def install(name):
|
|||||||
skill_name = subpath.rstrip("/").split("/")[-1] if subpath else repo
|
skill_name = subpath.rstrip("/").split("/")[-1] if subpath else repo
|
||||||
_install_github(spec, subpath=subpath, skill_name=skill_name, branch=branch)
|
_install_github(spec, subpath=subpath, skill_name=skill_name, branch=branch)
|
||||||
elif name.startswith("github:"):
|
elif name.startswith("github:"):
|
||||||
_install_github(name[7:])
|
skill_name = name[7:]
|
||||||
|
_validate_skill_name(skill_name)
|
||||||
|
_install_hub(skill_name)
|
||||||
|
elif name.startswith("clawhub:"):
|
||||||
|
skill_name = name[8:]
|
||||||
|
_validate_skill_name(skill_name)
|
||||||
|
_install_hub(skill_name, provider="clawhub")
|
||||||
else:
|
else:
|
||||||
_validate_skill_name(name)
|
_validate_skill_name(name)
|
||||||
_install_hub(name)
|
_install_hub(name)
|
||||||
|
|
||||||
|
|
||||||
def _install_hub(name):
|
def _install_hub(name, provider=None):
|
||||||
"""Install a skill from Skill Hub."""
|
"""Install a skill from Skill Hub."""
|
||||||
skills_dir = get_skills_dir()
|
skills_dir = get_skills_dir()
|
||||||
os.makedirs(skills_dir, exist_ok=True)
|
os.makedirs(skills_dir, exist_ok=True)
|
||||||
@@ -465,7 +471,14 @@ def _install_hub(name):
|
|||||||
click.echo(f"Fetching skill info for '{name}'...")
|
click.echo(f"Fetching skill info for '{name}'...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = requests.get(f"{SKILL_HUB_API}/skills/{name}/download", timeout=15)
|
body = {}
|
||||||
|
if provider:
|
||||||
|
body["provider"] = provider
|
||||||
|
resp = requests.post(
|
||||||
|
f"{SKILL_HUB_API}/skills/{name}/download",
|
||||||
|
json=body,
|
||||||
|
timeout=15,
|
||||||
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
if e.response is not None and e.response.status_code == 404:
|
if e.response is not None and e.response.status_code == 404:
|
||||||
@@ -745,11 +758,12 @@ def info(name):
|
|||||||
|
|
||||||
skill_dir = None
|
skill_dir = None
|
||||||
source = None
|
source = None
|
||||||
|
config = load_skills_config()
|
||||||
for d, src in [(skills_dir, "custom"), (builtin_dir, "builtin")]:
|
for d, src in [(skills_dir, "custom"), (builtin_dir, "builtin")]:
|
||||||
candidate = os.path.join(d, name)
|
candidate = os.path.join(d, name)
|
||||||
if os.path.isdir(candidate):
|
if os.path.isdir(candidate):
|
||||||
skill_dir = candidate
|
skill_dir = candidate
|
||||||
source = src
|
source = config.get(name, {}).get("source") or src
|
||||||
break
|
break
|
||||||
|
|
||||||
if not skill_dir:
|
if not skill_dir:
|
||||||
|
|||||||
@@ -494,7 +494,6 @@ class CowCliPlugin(Plugin):
|
|||||||
lines.append(line)
|
lines.append(line)
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
lines.append("")
|
|
||||||
lines.append("━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
lines.append("━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||||
lines.append("💡 /skill list --remote 浏览技能广场")
|
lines.append("💡 /skill list --remote 浏览技能广场")
|
||||||
lines.append("💡 /skill info <名称> 查看详情")
|
lines.append("💡 /skill info <名称> 查看详情")
|
||||||
@@ -632,10 +631,21 @@ class CowCliPlugin(Plugin):
|
|||||||
spec, skills_dir, subpath=subpath, skill_name=skill_name, branch=branch
|
spec, skills_dir, subpath=subpath, skill_name=skill_name, branch=branch
|
||||||
)
|
)
|
||||||
|
|
||||||
|
provider = None
|
||||||
if name.startswith("github:"):
|
if name.startswith("github:"):
|
||||||
return self._skill_install_github(name[7:], skills_dir)
|
name = name[7:]
|
||||||
|
elif name.startswith("clawhub:"):
|
||||||
|
name = name[8:]
|
||||||
|
provider = "clawhub"
|
||||||
|
|
||||||
resp = requests.get(f"{SKILL_HUB_API}/skills/{name}/download", timeout=15)
|
body = {}
|
||||||
|
if provider:
|
||||||
|
body["provider"] = provider
|
||||||
|
resp = requests.post(
|
||||||
|
f"{SKILL_HUB_API}/skills/{name}/download",
|
||||||
|
json=body,
|
||||||
|
timeout=15,
|
||||||
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
|
||||||
content_type = resp.headers.get("Content-Type", "")
|
content_type = resp.headers.get("Content-Type", "")
|
||||||
|
|||||||
Reference in New Issue
Block a user