mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
fix: tool call failed problem
This commit is contained in:
@@ -171,54 +171,75 @@ class AgentStreamExecutor:
|
|||||||
tool_results = []
|
tool_results = []
|
||||||
tool_result_blocks = []
|
tool_result_blocks = []
|
||||||
|
|
||||||
for tool_call in tool_calls:
|
try:
|
||||||
result = self._execute_tool(tool_call)
|
for tool_call in tool_calls:
|
||||||
tool_results.append(result)
|
result = self._execute_tool(tool_call)
|
||||||
|
tool_results.append(result)
|
||||||
# Log tool result in compact format
|
|
||||||
status_emoji = "✅" if result.get("status") == "success" else "❌"
|
# Log tool result in compact format
|
||||||
result_data = result.get('result', '')
|
status_emoji = "✅" if result.get("status") == "success" else "❌"
|
||||||
# Format result string with proper Chinese character support
|
result_data = result.get('result', '')
|
||||||
if isinstance(result_data, (dict, list)):
|
# Format result string with proper Chinese character support
|
||||||
result_str = json.dumps(result_data, ensure_ascii=False)
|
if isinstance(result_data, (dict, list)):
|
||||||
else:
|
result_str = json.dumps(result_data, ensure_ascii=False)
|
||||||
result_str = str(result_data)
|
else:
|
||||||
logger.info(f" {status_emoji} {tool_call['name']} ({result.get('execution_time', 0):.2f}s): {result_str[:200]}{'...' if len(result_str) > 200 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)
|
# Build tool result block (Claude format)
|
||||||
# Format content in a way that's easy for LLM to understand
|
# Format content in a way that's easy for LLM to understand
|
||||||
is_error = result.get("status") == "error"
|
is_error = result.get("status") == "error"
|
||||||
|
|
||||||
if is_error:
|
if is_error:
|
||||||
# For errors, provide clear error message
|
# For errors, provide clear error message
|
||||||
result_content = f"Error: {result.get('result', 'Unknown error')}"
|
result_content = f"Error: {result.get('result', 'Unknown error')}"
|
||||||
elif isinstance(result.get('result'), dict):
|
elif isinstance(result.get('result'), dict):
|
||||||
# For dict results, use JSON format
|
# For dict results, use JSON format
|
||||||
result_content = json.dumps(result.get('result'), ensure_ascii=False)
|
result_content = json.dumps(result.get('result'), ensure_ascii=False)
|
||||||
elif isinstance(result.get('result'), str):
|
elif isinstance(result.get('result'), str):
|
||||||
# For string results, use directly
|
# For string results, use directly
|
||||||
result_content = result.get('result')
|
result_content = result.get('result')
|
||||||
else:
|
else:
|
||||||
# Fallback to full JSON
|
# Fallback to full JSON
|
||||||
result_content = json.dumps(result, ensure_ascii=False)
|
result_content = json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
tool_result_block = {
|
tool_result_block = {
|
||||||
"type": "tool_result",
|
"type": "tool_result",
|
||||||
"tool_use_id": tool_call["id"],
|
"tool_use_id": tool_call["id"],
|
||||||
"content": result_content
|
"content": result_content
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add is_error field for Claude API (helps model understand failures)
|
# Add is_error field for Claude API (helps model understand failures)
|
||||||
if is_error:
|
if is_error:
|
||||||
tool_result_block["is_error"] = True
|
tool_result_block["is_error"] = True
|
||||||
|
|
||||||
tool_result_blocks.append(tool_result_block)
|
tool_result_blocks.append(tool_result_block)
|
||||||
|
|
||||||
# Add tool results to message history as user message (Claude format)
|
finally:
|
||||||
self.messages.append({
|
# CRITICAL: Always add tool_result to maintain message history integrity
|
||||||
"role": "user",
|
# Even if tool execution fails, we must add error results to match tool_use
|
||||||
"content": tool_result_blocks
|
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", {
|
self._emit_event("turn_end", {
|
||||||
"turn": turn,
|
"turn": turn,
|
||||||
@@ -257,6 +278,9 @@ class AgentStreamExecutor:
|
|||||||
Returns:
|
Returns:
|
||||||
(response_text, tool_calls)
|
(response_text, tool_calls)
|
||||||
"""
|
"""
|
||||||
|
# Validate and fix message history first
|
||||||
|
self._validate_and_fix_messages()
|
||||||
|
|
||||||
# Trim messages if needed (using agent's context management)
|
# Trim messages if needed (using agent's context management)
|
||||||
self._trim_messages()
|
self._trim_messages()
|
||||||
|
|
||||||
@@ -513,6 +537,27 @@ class AgentStreamExecutor:
|
|||||||
})
|
})
|
||||||
return error_result
|
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):
|
def _trim_messages(self):
|
||||||
"""
|
"""
|
||||||
Trim message history to stay within context limits.
|
Trim message history to stay within context limits.
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from agent.tools.memory.memory_get import MemoryGetTool
|
|||||||
|
|
||||||
# Import web tools
|
# Import web tools
|
||||||
from agent.tools.web_fetch.web_fetch import WebFetch
|
from agent.tools.web_fetch.web_fetch import WebFetch
|
||||||
|
from agent.tools.bocha_search.bocha_search import BochaSearch
|
||||||
|
|
||||||
# Import tools with optional dependencies
|
# Import tools with optional dependencies
|
||||||
def _import_optional_tools():
|
def _import_optional_tools():
|
||||||
@@ -93,6 +94,7 @@ __all__ = [
|
|||||||
'MemorySearchTool',
|
'MemorySearchTool',
|
||||||
'MemoryGetTool',
|
'MemoryGetTool',
|
||||||
'WebFetch',
|
'WebFetch',
|
||||||
|
'BochaSearch',
|
||||||
# Optional tools (may be None if dependencies not available)
|
# Optional tools (may be None if dependencies not available)
|
||||||
'GoogleSearch',
|
'GoogleSearch',
|
||||||
'FileSave',
|
'FileSave',
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class WebFetch(BaseTool):
|
|||||||
|
|
||||||
def __init__(self, config: dict = None):
|
def __init__(self, config: dict = None):
|
||||||
self.config = config or {}
|
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.max_redirects = self.config.get("max_redirects", 3)
|
||||||
self.user_agent = self.config.get(
|
self.user_agent = self.config.get(
|
||||||
"user_agent",
|
"user_agent",
|
||||||
|
|||||||
@@ -171,13 +171,15 @@ class AgentLLMModel(LLMModel):
|
|||||||
|
|
||||||
class AgentBridge:
|
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):
|
def __init__(self, bridge: Bridge):
|
||||||
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
|
self.agent: Optional[Agent] = None
|
||||||
|
|
||||||
def create_agent(self, system_prompt: str, tools: List = None, **kwargs) -> Agent:
|
def create_agent(self, system_prompt: str, tools: List = None, **kwargs) -> Agent:
|
||||||
"""
|
"""
|
||||||
Create the super agent with COW integration
|
Create the super agent with COW integration
|
||||||
@@ -209,8 +211,8 @@ class AgentBridge:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[AgentBridge] Failed to load tool {tool_name}: {e}")
|
logger.warning(f"[AgentBridge] Failed to load tool {tool_name}: {e}")
|
||||||
|
|
||||||
# Create the single super agent
|
# Create agent instance
|
||||||
self.agent = Agent(
|
agent = Agent(
|
||||||
system_prompt=system_prompt,
|
system_prompt=system_prompt,
|
||||||
description=kwargs.get("description", "AI Super Agent"),
|
description=kwargs.get("description", "AI Super Agent"),
|
||||||
model=model,
|
model=model,
|
||||||
@@ -225,21 +227,38 @@ class AgentBridge:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Log skill loading details
|
# Log skill loading details
|
||||||
if self.agent.skill_manager:
|
if agent.skill_manager:
|
||||||
logger.info(f"[AgentBridge] SkillManager initialized:")
|
logger.info(f"[AgentBridge] SkillManager initialized:")
|
||||||
logger.info(f"[AgentBridge] - Managed dir: {self.agent.skill_manager.managed_skills_dir}")
|
logger.info(f"[AgentBridge] - Managed dir: {agent.skill_manager.managed_skills_dir}")
|
||||||
logger.info(f"[AgentBridge] - Workspace dir: {self.agent.skill_manager.workspace_dir}")
|
logger.info(f"[AgentBridge] - Workspace dir: {agent.skill_manager.workspace_dir}")
|
||||||
logger.info(f"[AgentBridge] - Total skills: {len(self.agent.skill_manager.skills)}")
|
logger.info(f"[AgentBridge] - Total skills: {len(agent.skill_manager.skills)}")
|
||||||
for skill_name in self.agent.skill_manager.skills.keys():
|
for skill_name in agent.skill_manager.skills.keys():
|
||||||
logger.info(f"[AgentBridge] * {skill_name}")
|
logger.info(f"[AgentBridge] * {skill_name}")
|
||||||
|
|
||||||
return self.agent
|
return agent
|
||||||
|
|
||||||
def get_agent(self) -> Optional[Agent]:
|
def get_agent(self, session_id: str = None) -> Optional[Agent]:
|
||||||
"""Get the super agent, create if not exists"""
|
"""
|
||||||
if self.agent is None:
|
Get agent instance for the given session
|
||||||
self._init_default_agent()
|
|
||||||
return self.agent
|
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):
|
def _init_default_agent(self):
|
||||||
"""Initialize default super agent with new prompt system"""
|
"""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)
|
tool.cwd = file_config.get("cwd", tool.cwd if hasattr(tool, 'cwd') else None)
|
||||||
if 'memory_manager' in file_config:
|
if 'memory_manager' in file_config:
|
||||||
tool.memory_manager = file_config['memory_manager']
|
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)
|
tools.append(tool)
|
||||||
logger.debug(f"[AgentBridge] Loaded tool: {tool_name}")
|
logger.debug(f"[AgentBridge] Loaded tool: {tool_name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -370,6 +395,127 @@ class AgentBridge:
|
|||||||
if memory_manager:
|
if memory_manager:
|
||||||
agent.memory_manager = memory_manager
|
agent.memory_manager = memory_manager
|
||||||
logger.info(f"[AgentBridge] Memory manager attached to agent")
|
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,
|
def agent_reply(self, query: str, context: Context = None,
|
||||||
on_event=None, clear_history: bool = False) -> Reply:
|
on_event=None, clear_history: bool = False) -> Reply:
|
||||||
@@ -378,7 +524,7 @@ class AgentBridge:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: User query
|
query: User query
|
||||||
context: COW context (optional)
|
context: COW context (optional, contains session_id for user isolation)
|
||||||
on_event: Event callback (optional)
|
on_event: Event callback (optional)
|
||||||
clear_history: Whether to clear conversation history
|
clear_history: Whether to clear conversation history
|
||||||
|
|
||||||
@@ -386,8 +532,13 @@ class AgentBridge:
|
|||||||
Reply object
|
Reply object
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get agent (will auto-initialize if needed)
|
# Extract session_id from context for user isolation
|
||||||
agent = self.get_agent()
|
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:
|
if not agent:
|
||||||
return Reply(ReplyType.ERROR, "Failed to initialize super agent")
|
return Reply(ReplyType.ERROR, "Failed to initialize super agent")
|
||||||
|
|
||||||
@@ -402,4 +553,21 @@ class AgentBridge:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Agent reply error: {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.msg_id_counter = 0 # 添加消息ID计数器
|
||||||
self.session_queues = {} # 存储session_id到队列的映射
|
self.session_queues = {} # 存储session_id到队列的映射
|
||||||
self.request_to_session = {} # 存储request_id到session_id的映射
|
self.request_to_session = {} # 存储request_id到session_id的映射
|
||||||
# web channel无需前缀
|
|
||||||
conf()["single_chat_prefix"] = [""]
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_msg_id(self):
|
def _generate_msg_id(self):
|
||||||
@@ -122,18 +120,30 @@ class WebChannel(ChatChannel):
|
|||||||
if session_id not in self.session_queues:
|
if session_id not in self.session_queues:
|
||||||
self.session_queues[session_id] = Queue()
|
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 = WebMessage(self._generate_msg_id(), prompt)
|
||||||
msg.from_user_id = session_id # 使用会话ID作为用户ID
|
msg.from_user_id = session_id # 使用会话ID作为用户ID
|
||||||
|
|
||||||
# 创建上下文
|
# 创建上下文,明确指定 isgroup=False
|
||||||
context = self._compose_context(ContextType.TEXT, prompt, msg=msg)
|
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["session_id"] = session_id
|
||||||
|
context["receiver"] = session_id
|
||||||
context["request_id"] = request_id
|
context["request_id"] = request_id
|
||||||
context["isgroup"] = False # 添加 isgroup 字段
|
|
||||||
context["receiver"] = session_id # 添加 receiver 字段
|
|
||||||
|
|
||||||
# 异步处理消息 - 只传递上下文
|
# 异步处理消息 - 只传递上下文
|
||||||
threading.Thread(target=self.produce, args=(context,)).start()
|
threading.Thread(target=self.produce, args=(context,)).start()
|
||||||
|
|||||||
@@ -185,7 +185,8 @@ available_setting = {
|
|||||||
"Minimax_base_url": "",
|
"Minimax_base_url": "",
|
||||||
"web_port": 9899,
|
"web_port": 9899,
|
||||||
"agent": False, # 是否开启Agent模式
|
"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
|
- Must test scripts before including
|
||||||
|
|
||||||
**references/** - When to include:
|
**references/** - When to include:
|
||||||
- Documentation for agent to reference
|
- **ONLY** when documentation is too large for SKILL.md (>500 lines)
|
||||||
- Database schemas, API docs, domain knowledge
|
- Database schemas, complex API specs that agent needs to reference
|
||||||
- Agent reads these files into context as needed
|
- 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:
|
**assets/** - When to include:
|
||||||
- Files used in output (not loaded to context)
|
- Files used in output (not loaded to context)
|
||||||
@@ -82,11 +83,15 @@ skill-name/
|
|||||||
|
|
||||||
### What NOT to Include
|
### What NOT to Include
|
||||||
|
|
||||||
Do NOT create auxiliary documentation:
|
Do NOT create auxiliary documentation files:
|
||||||
- README.md
|
- README.md - Instructions belong in SKILL.md
|
||||||
- INSTALLATION_GUIDE.md
|
- INSTALLATION_GUIDE.md - Setup info belongs in SKILL.md
|
||||||
- CHANGELOG.md
|
- CHANGELOG.md - Not needed for local skills
|
||||||
- Other non-essential files
|
- 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
|
## 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
|
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
|
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:
|
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
|
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
|
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:
|
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
|
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
|
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:
|
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
|
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
|
### 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/`.
|
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.
|
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.
|
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