mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-02-24 08:19:49 +08:00
fix: tool call failed problem
This commit is contained in:
@@ -171,54 +171,75 @@ class AgentStreamExecutor:
|
||||
tool_results = []
|
||||
tool_result_blocks = []
|
||||
|
||||
for tool_call in tool_calls:
|
||||
result = self._execute_tool(tool_call)
|
||||
tool_results.append(result)
|
||||
|
||||
# Log tool result in compact format
|
||||
status_emoji = "✅" if result.get("status") == "success" else "❌"
|
||||
result_data = result.get('result', '')
|
||||
# Format result string with proper Chinese character support
|
||||
if isinstance(result_data, (dict, list)):
|
||||
result_str = json.dumps(result_data, ensure_ascii=False)
|
||||
else:
|
||||
result_str = str(result_data)
|
||||
logger.info(f" {status_emoji} {tool_call['name']} ({result.get('execution_time', 0):.2f}s): {result_str[:200]}{'...' if len(result_str) > 200 else ''}")
|
||||
try:
|
||||
for tool_call in tool_calls:
|
||||
result = self._execute_tool(tool_call)
|
||||
tool_results.append(result)
|
||||
|
||||
# Log tool result in compact format
|
||||
status_emoji = "✅" if result.get("status") == "success" else "❌"
|
||||
result_data = result.get('result', '')
|
||||
# Format result string with proper Chinese character support
|
||||
if isinstance(result_data, (dict, list)):
|
||||
result_str = json.dumps(result_data, ensure_ascii=False)
|
||||
else:
|
||||
result_str = str(result_data)
|
||||
logger.info(f" {status_emoji} {tool_call['name']} ({result.get('execution_time', 0):.2f}s): {result_str[:200]}{'...' if len(result_str) > 200 else ''}")
|
||||
|
||||
# Build tool result block (Claude format)
|
||||
# Format content in a way that's easy for LLM to understand
|
||||
is_error = result.get("status") == "error"
|
||||
|
||||
if is_error:
|
||||
# For errors, provide clear error message
|
||||
result_content = f"Error: {result.get('result', 'Unknown error')}"
|
||||
elif isinstance(result.get('result'), dict):
|
||||
# For dict results, use JSON format
|
||||
result_content = json.dumps(result.get('result'), ensure_ascii=False)
|
||||
elif isinstance(result.get('result'), str):
|
||||
# For string results, use directly
|
||||
result_content = result.get('result')
|
||||
else:
|
||||
# Fallback to full JSON
|
||||
result_content = json.dumps(result, ensure_ascii=False)
|
||||
|
||||
tool_result_block = {
|
||||
"type": "tool_result",
|
||||
"tool_use_id": tool_call["id"],
|
||||
"content": result_content
|
||||
}
|
||||
|
||||
# Add is_error field for Claude API (helps model understand failures)
|
||||
if is_error:
|
||||
tool_result_block["is_error"] = True
|
||||
|
||||
tool_result_blocks.append(tool_result_block)
|
||||
|
||||
# Add tool results to message history as user message (Claude format)
|
||||
self.messages.append({
|
||||
"role": "user",
|
||||
"content": tool_result_blocks
|
||||
})
|
||||
# Build tool result block (Claude format)
|
||||
# Format content in a way that's easy for LLM to understand
|
||||
is_error = result.get("status") == "error"
|
||||
|
||||
if is_error:
|
||||
# For errors, provide clear error message
|
||||
result_content = f"Error: {result.get('result', 'Unknown error')}"
|
||||
elif isinstance(result.get('result'), dict):
|
||||
# For dict results, use JSON format
|
||||
result_content = json.dumps(result.get('result'), ensure_ascii=False)
|
||||
elif isinstance(result.get('result'), str):
|
||||
# For string results, use directly
|
||||
result_content = result.get('result')
|
||||
else:
|
||||
# Fallback to full JSON
|
||||
result_content = json.dumps(result, ensure_ascii=False)
|
||||
|
||||
tool_result_block = {
|
||||
"type": "tool_result",
|
||||
"tool_use_id": tool_call["id"],
|
||||
"content": result_content
|
||||
}
|
||||
|
||||
# Add is_error field for Claude API (helps model understand failures)
|
||||
if is_error:
|
||||
tool_result_block["is_error"] = True
|
||||
|
||||
tool_result_blocks.append(tool_result_block)
|
||||
|
||||
finally:
|
||||
# CRITICAL: Always add tool_result to maintain message history integrity
|
||||
# Even if tool execution fails, we must add error results to match tool_use
|
||||
if tool_result_blocks:
|
||||
# Add tool results to message history as user message (Claude format)
|
||||
self.messages.append({
|
||||
"role": "user",
|
||||
"content": tool_result_blocks
|
||||
})
|
||||
elif tool_calls:
|
||||
# If we have tool_calls but no tool_result_blocks (unexpected error),
|
||||
# create error results for all tool calls to maintain message integrity
|
||||
logger.warning("⚠️ Tool execution interrupted, adding error results to maintain message history")
|
||||
emergency_blocks = []
|
||||
for tool_call in tool_calls:
|
||||
emergency_blocks.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": tool_call["id"],
|
||||
"content": "Error: Tool execution was interrupted",
|
||||
"is_error": True
|
||||
})
|
||||
self.messages.append({
|
||||
"role": "user",
|
||||
"content": emergency_blocks
|
||||
})
|
||||
|
||||
self._emit_event("turn_end", {
|
||||
"turn": turn,
|
||||
@@ -257,6 +278,9 @@ class AgentStreamExecutor:
|
||||
Returns:
|
||||
(response_text, tool_calls)
|
||||
"""
|
||||
# Validate and fix message history first
|
||||
self._validate_and_fix_messages()
|
||||
|
||||
# Trim messages if needed (using agent's context management)
|
||||
self._trim_messages()
|
||||
|
||||
@@ -513,6 +537,27 @@ class AgentStreamExecutor:
|
||||
})
|
||||
return error_result
|
||||
|
||||
def _validate_and_fix_messages(self):
|
||||
"""
|
||||
Validate message history and fix incomplete tool_use/tool_result pairs.
|
||||
Claude API requires each tool_use to have a corresponding tool_result immediately after.
|
||||
"""
|
||||
if not self.messages:
|
||||
return
|
||||
|
||||
# Check last message for incomplete tool_use
|
||||
if len(self.messages) > 0:
|
||||
last_msg = self.messages[-1]
|
||||
if last_msg.get("role") == "assistant":
|
||||
# Check if assistant message has tool_use blocks
|
||||
content = last_msg.get("content", [])
|
||||
if isinstance(content, list):
|
||||
has_tool_use = any(block.get("type") == "tool_use" for block in content)
|
||||
if has_tool_use:
|
||||
# This is incomplete - remove it
|
||||
logger.warning(f"⚠️ Removing incomplete tool_use message from history")
|
||||
self.messages.pop()
|
||||
|
||||
def _trim_messages(self):
|
||||
"""
|
||||
Trim message history to stay within context limits.
|
||||
|
||||
@@ -21,6 +21,7 @@ from agent.tools.memory.memory_get import MemoryGetTool
|
||||
|
||||
# Import web tools
|
||||
from agent.tools.web_fetch.web_fetch import WebFetch
|
||||
from agent.tools.bocha_search.bocha_search import BochaSearch
|
||||
|
||||
# Import tools with optional dependencies
|
||||
def _import_optional_tools():
|
||||
@@ -93,6 +94,7 @@ __all__ = [
|
||||
'MemorySearchTool',
|
||||
'MemoryGetTool',
|
||||
'WebFetch',
|
||||
'BochaSearch',
|
||||
# Optional tools (may be None if dependencies not available)
|
||||
'GoogleSearch',
|
||||
'FileSave',
|
||||
|
||||
@@ -46,7 +46,7 @@ class WebFetch(BaseTool):
|
||||
|
||||
def __init__(self, config: dict = None):
|
||||
self.config = config or {}
|
||||
self.timeout = self.config.get("timeout", 30)
|
||||
self.timeout = self.config.get("timeout", 20)
|
||||
self.max_redirects = self.config.get("max_redirects", 3)
|
||||
self.user_agent = self.config.get(
|
||||
"user_agent",
|
||||
|
||||
@@ -171,13 +171,15 @@ class AgentLLMModel(LLMModel):
|
||||
|
||||
class AgentBridge:
|
||||
"""
|
||||
Bridge class that integrates single super Agent with COW
|
||||
Bridge class that integrates super Agent with COW
|
||||
Manages multiple agent instances per session for conversation isolation
|
||||
"""
|
||||
|
||||
def __init__(self, bridge: Bridge):
|
||||
self.bridge = bridge
|
||||
self.agents = {} # session_id -> Agent instance mapping
|
||||
self.default_agent = None # For backward compatibility (no session_id)
|
||||
self.agent: Optional[Agent] = None
|
||||
|
||||
def create_agent(self, system_prompt: str, tools: List = None, **kwargs) -> Agent:
|
||||
"""
|
||||
Create the super agent with COW integration
|
||||
@@ -209,8 +211,8 @@ class AgentBridge:
|
||||
except Exception as e:
|
||||
logger.warning(f"[AgentBridge] Failed to load tool {tool_name}: {e}")
|
||||
|
||||
# Create the single super agent
|
||||
self.agent = Agent(
|
||||
# Create agent instance
|
||||
agent = Agent(
|
||||
system_prompt=system_prompt,
|
||||
description=kwargs.get("description", "AI Super Agent"),
|
||||
model=model,
|
||||
@@ -225,21 +227,38 @@ class AgentBridge:
|
||||
)
|
||||
|
||||
# Log skill loading details
|
||||
if self.agent.skill_manager:
|
||||
if agent.skill_manager:
|
||||
logger.info(f"[AgentBridge] SkillManager initialized:")
|
||||
logger.info(f"[AgentBridge] - Managed dir: {self.agent.skill_manager.managed_skills_dir}")
|
||||
logger.info(f"[AgentBridge] - Workspace dir: {self.agent.skill_manager.workspace_dir}")
|
||||
logger.info(f"[AgentBridge] - Total skills: {len(self.agent.skill_manager.skills)}")
|
||||
for skill_name in self.agent.skill_manager.skills.keys():
|
||||
logger.info(f"[AgentBridge] - Managed dir: {agent.skill_manager.managed_skills_dir}")
|
||||
logger.info(f"[AgentBridge] - Workspace dir: {agent.skill_manager.workspace_dir}")
|
||||
logger.info(f"[AgentBridge] - Total skills: {len(agent.skill_manager.skills)}")
|
||||
for skill_name in agent.skill_manager.skills.keys():
|
||||
logger.info(f"[AgentBridge] * {skill_name}")
|
||||
|
||||
return self.agent
|
||||
return agent
|
||||
|
||||
def get_agent(self) -> Optional[Agent]:
|
||||
"""Get the super agent, create if not exists"""
|
||||
if self.agent is None:
|
||||
self._init_default_agent()
|
||||
return self.agent
|
||||
def get_agent(self, session_id: str = None) -> Optional[Agent]:
|
||||
"""
|
||||
Get agent instance for the given session
|
||||
|
||||
Args:
|
||||
session_id: Session identifier (e.g., user_id). If None, returns default agent.
|
||||
|
||||
Returns:
|
||||
Agent instance for this session
|
||||
"""
|
||||
# If no session_id, use default agent (backward compatibility)
|
||||
if session_id is None:
|
||||
if self.default_agent is None:
|
||||
self._init_default_agent()
|
||||
return self.default_agent
|
||||
|
||||
# Check if agent exists for this session
|
||||
if session_id not in self.agents:
|
||||
logger.info(f"[AgentBridge] Creating new agent for session: {session_id}")
|
||||
self._init_agent_for_session(session_id)
|
||||
|
||||
return self.agents[session_id]
|
||||
|
||||
def _init_default_agent(self):
|
||||
"""Initialize default super agent with new prompt system"""
|
||||
@@ -307,6 +326,12 @@ class AgentBridge:
|
||||
tool.cwd = file_config.get("cwd", tool.cwd if hasattr(tool, 'cwd') else None)
|
||||
if 'memory_manager' in file_config:
|
||||
tool.memory_manager = file_config['memory_manager']
|
||||
# Apply API key for bocha_search tool
|
||||
elif tool_name == 'bocha_search':
|
||||
bocha_api_key = conf().get("bocha_api_key", "")
|
||||
if bocha_api_key:
|
||||
tool.config = {"bocha_api_key": bocha_api_key}
|
||||
tool.api_key = bocha_api_key
|
||||
tools.append(tool)
|
||||
logger.debug(f"[AgentBridge] Loaded tool: {tool_name}")
|
||||
except Exception as e:
|
||||
@@ -370,6 +395,127 @@ class AgentBridge:
|
||||
if memory_manager:
|
||||
agent.memory_manager = memory_manager
|
||||
logger.info(f"[AgentBridge] Memory manager attached to agent")
|
||||
|
||||
# Store as default agent
|
||||
self.default_agent = agent
|
||||
|
||||
def _init_agent_for_session(self, session_id: str):
|
||||
"""
|
||||
Initialize agent for a specific session
|
||||
Reuses the same configuration as default agent
|
||||
"""
|
||||
from config import conf
|
||||
import os
|
||||
|
||||
# Get workspace from config
|
||||
workspace_root = os.path.expanduser(conf().get("agent_workspace", "~/cow"))
|
||||
|
||||
# Initialize workspace
|
||||
from agent.prompt import ensure_workspace, load_context_files, PromptBuilder
|
||||
|
||||
workspace_files = ensure_workspace(workspace_root, create_templates=True)
|
||||
|
||||
# Setup memory system
|
||||
memory_manager = None
|
||||
memory_tools = []
|
||||
|
||||
try:
|
||||
from agent.memory import MemoryManager, MemoryConfig
|
||||
from agent.tools import MemorySearchTool, MemoryGetTool
|
||||
|
||||
memory_config = MemoryConfig(
|
||||
workspace_root=workspace_root,
|
||||
embedding_provider="local",
|
||||
embedding_model="all-MiniLM-L6-v2"
|
||||
)
|
||||
|
||||
memory_manager = MemoryManager(memory_config)
|
||||
memory_tools = [
|
||||
MemorySearchTool(memory_manager),
|
||||
MemoryGetTool(memory_manager)
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"[AgentBridge] Memory system not available for session {session_id}: {e}")
|
||||
|
||||
# Load tools
|
||||
from agent.tools import ToolManager
|
||||
tool_manager = ToolManager()
|
||||
tool_manager.load_tools()
|
||||
|
||||
tools = []
|
||||
file_config = {
|
||||
"cwd": workspace_root,
|
||||
"memory_manager": memory_manager
|
||||
} if memory_manager else {"cwd": workspace_root}
|
||||
|
||||
for tool_name in tool_manager.tool_classes.keys():
|
||||
try:
|
||||
tool = tool_manager.create_tool(tool_name)
|
||||
if tool:
|
||||
if tool_name in ['read', 'write', 'edit', 'bash', 'grep', 'find', 'ls']:
|
||||
tool.config = file_config
|
||||
tool.cwd = file_config.get("cwd", tool.cwd if hasattr(tool, 'cwd') else None)
|
||||
if 'memory_manager' in file_config:
|
||||
tool.memory_manager = file_config['memory_manager']
|
||||
elif tool_name == 'bocha_search':
|
||||
bocha_api_key = conf().get("bocha_api_key", "")
|
||||
if bocha_api_key:
|
||||
tool.config = {"bocha_api_key": bocha_api_key}
|
||||
tool.api_key = bocha_api_key
|
||||
tools.append(tool)
|
||||
except Exception as e:
|
||||
logger.warning(f"[AgentBridge] Failed to load tool {tool_name} for session {session_id}: {e}")
|
||||
|
||||
if memory_tools:
|
||||
tools.extend(memory_tools)
|
||||
|
||||
# Load context files
|
||||
context_files = load_context_files(workspace_root)
|
||||
|
||||
# Check if this is the first conversation
|
||||
from agent.prompt.workspace import is_first_conversation, mark_conversation_started
|
||||
is_first = is_first_conversation(workspace_root)
|
||||
|
||||
# Build system prompt
|
||||
prompt_builder = PromptBuilder(
|
||||
workspace_dir=workspace_root,
|
||||
language="zh"
|
||||
)
|
||||
|
||||
runtime_info = {
|
||||
"model": conf().get("model", "unknown"),
|
||||
"workspace": workspace_root,
|
||||
"channel": conf().get("channel_type", "unknown")
|
||||
}
|
||||
|
||||
system_prompt = prompt_builder.build(
|
||||
tools=tools,
|
||||
context_files=context_files,
|
||||
memory_manager=memory_manager,
|
||||
runtime_info=runtime_info,
|
||||
is_first_conversation=is_first
|
||||
)
|
||||
|
||||
if is_first:
|
||||
mark_conversation_started(workspace_root)
|
||||
|
||||
# Create agent for this session
|
||||
agent = self.create_agent(
|
||||
system_prompt=system_prompt,
|
||||
tools=tools,
|
||||
max_steps=50,
|
||||
output_mode="logger",
|
||||
workspace_dir=workspace_root,
|
||||
enable_skills=True
|
||||
)
|
||||
|
||||
if memory_manager:
|
||||
agent.memory_manager = memory_manager
|
||||
|
||||
# Store agent for this session
|
||||
self.agents[session_id] = agent
|
||||
logger.info(f"[AgentBridge] Agent created for session: {session_id}")
|
||||
|
||||
def agent_reply(self, query: str, context: Context = None,
|
||||
on_event=None, clear_history: bool = False) -> Reply:
|
||||
@@ -378,7 +524,7 @@ class AgentBridge:
|
||||
|
||||
Args:
|
||||
query: User query
|
||||
context: COW context (optional)
|
||||
context: COW context (optional, contains session_id for user isolation)
|
||||
on_event: Event callback (optional)
|
||||
clear_history: Whether to clear conversation history
|
||||
|
||||
@@ -386,8 +532,13 @@ class AgentBridge:
|
||||
Reply object
|
||||
"""
|
||||
try:
|
||||
# Get agent (will auto-initialize if needed)
|
||||
agent = self.get_agent()
|
||||
# Extract session_id from context for user isolation
|
||||
session_id = None
|
||||
if context:
|
||||
session_id = context.kwargs.get("session_id") or context.get("session_id")
|
||||
|
||||
# Get agent for this session (will auto-initialize if needed)
|
||||
agent = self.get_agent(session_id=session_id)
|
||||
if not agent:
|
||||
return Reply(ReplyType.ERROR, "Failed to initialize super agent")
|
||||
|
||||
@@ -402,4 +553,21 @@ class AgentBridge:
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Agent reply error: {e}")
|
||||
return Reply(ReplyType.ERROR, f"Agent error: {str(e)}")
|
||||
return Reply(ReplyType.ERROR, f"Agent error: {str(e)}")
|
||||
|
||||
def clear_session(self, session_id: str):
|
||||
"""
|
||||
Clear a specific session's agent and conversation history
|
||||
|
||||
Args:
|
||||
session_id: Session identifier to clear
|
||||
"""
|
||||
if session_id in self.agents:
|
||||
logger.info(f"[AgentBridge] Clearing session: {session_id}")
|
||||
del self.agents[session_id]
|
||||
|
||||
def clear_all_sessions(self):
|
||||
"""Clear all agent sessions"""
|
||||
logger.info(f"[AgentBridge] Clearing all sessions ({len(self.agents)} total)")
|
||||
self.agents.clear()
|
||||
self.default_agent = None
|
||||
@@ -49,8 +49,6 @@ class WebChannel(ChatChannel):
|
||||
self.msg_id_counter = 0 # 添加消息ID计数器
|
||||
self.session_queues = {} # 存储session_id到队列的映射
|
||||
self.request_to_session = {} # 存储request_id到session_id的映射
|
||||
# web channel无需前缀
|
||||
conf()["single_chat_prefix"] = [""]
|
||||
|
||||
|
||||
def _generate_msg_id(self):
|
||||
@@ -122,18 +120,30 @@ class WebChannel(ChatChannel):
|
||||
if session_id not in self.session_queues:
|
||||
self.session_queues[session_id] = Queue()
|
||||
|
||||
# Web channel 不需要前缀,确保消息能通过前缀检查
|
||||
trigger_prefixs = conf().get("single_chat_prefix", [""])
|
||||
if check_prefix(prompt, trigger_prefixs) is None:
|
||||
# 如果没有匹配到前缀,给消息加上第一个前缀
|
||||
if trigger_prefixs:
|
||||
prompt = trigger_prefixs[0] + prompt
|
||||
logger.debug(f"[WebChannel] Added prefix to message: {prompt}")
|
||||
|
||||
# 创建消息对象
|
||||
msg = WebMessage(self._generate_msg_id(), prompt)
|
||||
msg.from_user_id = session_id # 使用会话ID作为用户ID
|
||||
|
||||
# 创建上下文
|
||||
context = self._compose_context(ContextType.TEXT, prompt, msg=msg)
|
||||
# 创建上下文,明确指定 isgroup=False
|
||||
context = self._compose_context(ContextType.TEXT, prompt, msg=msg, isgroup=False)
|
||||
|
||||
# 检查 context 是否为 None(可能被插件过滤等)
|
||||
if context is None:
|
||||
logger.warning(f"[WebChannel] Context is None for session {session_id}, message may be filtered")
|
||||
return json.dumps({"status": "error", "message": "Message was filtered"})
|
||||
|
||||
# 添加必要的字段
|
||||
# 覆盖必要的字段(_compose_context 会设置默认值,但我们需要使用实际的 session_id)
|
||||
context["session_id"] = session_id
|
||||
context["receiver"] = session_id
|
||||
context["request_id"] = request_id
|
||||
context["isgroup"] = False # 添加 isgroup 字段
|
||||
context["receiver"] = session_id # 添加 receiver 字段
|
||||
|
||||
# 异步处理消息 - 只传递上下文
|
||||
threading.Thread(target=self.produce, args=(context,)).start()
|
||||
|
||||
@@ -185,7 +185,8 @@ available_setting = {
|
||||
"Minimax_base_url": "",
|
||||
"web_port": 9899,
|
||||
"agent": False, # 是否开启Agent模式
|
||||
"agent_workspace": "~/cow" # agent工作空间路径,用于存储skills、memory等
|
||||
"agent_workspace": "~/cow", # agent工作空间路径,用于存储skills、memory等
|
||||
"bocha_api_key": ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -68,10 +68,11 @@ skill-name/
|
||||
- Must test scripts before including
|
||||
|
||||
**references/** - When to include:
|
||||
- Documentation for agent to reference
|
||||
- Database schemas, API docs, domain knowledge
|
||||
- **ONLY** when documentation is too large for SKILL.md (>500 lines)
|
||||
- Database schemas, complex API specs that agent needs to reference
|
||||
- Agent reads these files into context as needed
|
||||
- For large files (>10k words), include grep patterns in SKILL.md
|
||||
- **NOT for**: API reference docs, usage examples, tutorials (put in SKILL.md instead)
|
||||
- **Rule of thumb**: If it fits in SKILL.md, don't create a separate reference file
|
||||
|
||||
**assets/** - When to include:
|
||||
- Files used in output (not loaded to context)
|
||||
@@ -82,11 +83,15 @@ skill-name/
|
||||
|
||||
### What NOT to Include
|
||||
|
||||
Do NOT create auxiliary documentation:
|
||||
- README.md
|
||||
- INSTALLATION_GUIDE.md
|
||||
- CHANGELOG.md
|
||||
- Other non-essential files
|
||||
Do NOT create auxiliary documentation files:
|
||||
- README.md - Instructions belong in SKILL.md
|
||||
- INSTALLATION_GUIDE.md - Setup info belongs in SKILL.md
|
||||
- CHANGELOG.md - Not needed for local skills
|
||||
- API_REFERENCE.md - Put API docs directly in SKILL.md
|
||||
- USAGE_EXAMPLES.md - Put examples directly in SKILL.md
|
||||
- Any other documentation files - Everything goes in SKILL.md unless it's too large
|
||||
|
||||
**Critical Rule**: Only create files that the agent will actually execute (scripts) or that are too large for SKILL.md (references). Documentation, examples, and guides ALL belong in SKILL.md.
|
||||
|
||||
## Skill Creation Process
|
||||
|
||||
@@ -133,22 +138,31 @@ To turn concrete examples into an effective skill, analyze each example by:
|
||||
1. Considering how to execute on the example from scratch
|
||||
2. Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly
|
||||
|
||||
**Planning Checklist**:
|
||||
- ✅ **Always needed**: SKILL.md with clear description and usage instructions
|
||||
- ✅ **scripts/**: Only if code needs to be executed (not just shown as examples)
|
||||
- ❌ **references/**: Rarely needed - only if documentation is >500 lines and can't fit in SKILL.md
|
||||
- ✅ **assets/**: Only if files are used in output (templates, boilerplate, etc.)
|
||||
|
||||
Example: When building a `pdf-editor` skill to handle queries like "Help me rotate this PDF," the analysis shows:
|
||||
|
||||
1. Rotating a PDF requires re-writing the same code each time
|
||||
2. A `scripts/rotate_pdf.py` script would be helpful to store in the skill
|
||||
3. ❌ Don't create `references/api-docs.md` - put API info in SKILL.md instead
|
||||
|
||||
Example: When designing a `frontend-webapp-builder` skill for queries like "Build me a todo app" or "Build me a dashboard to track my steps," the analysis shows:
|
||||
|
||||
1. Writing a frontend webapp requires the same boilerplate HTML/React each time
|
||||
2. An `assets/hello-world/` template containing the boilerplate HTML/React project files would be helpful to store in the skill
|
||||
3. ❌ Don't create `references/usage-examples.md` - put examples in SKILL.md instead
|
||||
|
||||
Example: When building a `big-query` skill to handle queries like "How many users have logged in today?" the analysis shows:
|
||||
|
||||
1. Querying BigQuery requires re-discovering the table schemas and relationships each time
|
||||
2. A `references/schema.md` file documenting the table schemas would be helpful to store in the skill
|
||||
2. A `references/schema.md` file documenting the table schemas would be helpful to store in the skill (ONLY because schemas are very large)
|
||||
3. ❌ Don't create separate `references/query-examples.md` - put examples in SKILL.md instead
|
||||
|
||||
To establish the skill's contents, analyze each concrete example to create a list of the reusable resources to include: scripts, references, and assets.
|
||||
To establish the skill's contents, analyze each concrete example to create a list of the reusable resources to include: scripts, references, and assets. **Default to putting everything in SKILL.md unless there's a compelling reason to separate it.**
|
||||
|
||||
### Step 3: Initialize the Skill
|
||||
|
||||
@@ -200,6 +214,12 @@ These files contain established best practices for effective skill design.
|
||||
|
||||
To begin implementation, start with the reusable resources identified above: `scripts/`, `references/`, and `assets/` files. Note that this step may require user input. For example, when implementing a `brand-guidelines` skill, the user may need to provide brand assets or templates to store in `assets/`, or documentation to store in `references/`.
|
||||
|
||||
**Important Guidelines**:
|
||||
- **scripts/**: Only create scripts that will be executed. Test all scripts before including.
|
||||
- **references/**: ONLY create if documentation is too large for SKILL.md (>500 lines). Most skills don't need this.
|
||||
- **assets/**: Only include files used in output (templates, icons, etc.)
|
||||
- **Default approach**: Put everything in SKILL.md unless there's a specific reason not to.
|
||||
|
||||
Added scripts must be tested by actually running them to ensure there are no bugs and that the output matches what is expected. If there are many similar scripts, only a representative sample needs to be tested to ensure confidence that they all work while balancing time to completion.
|
||||
|
||||
If you used `--examples`, delete any placeholder files that are not needed for the skill. Only create resource directories that are actually required.
|
||||
|
||||
Reference in New Issue
Block a user