mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
fix: memory and path bug
This commit is contained in:
@@ -117,7 +117,7 @@ class MemoryFlushManager:
|
|||||||
return user_dir / "MEMORY.md"
|
return user_dir / "MEMORY.md"
|
||||||
else:
|
else:
|
||||||
# Return workspace root MEMORY.md
|
# Return workspace root MEMORY.md
|
||||||
return Path(self.workspace_root) / "MEMORY.md"
|
return Path(self.workspace_dir) / "MEMORY.md"
|
||||||
|
|
||||||
def create_flush_prompt(self) -> str:
|
def create_flush_prompt(self) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -214,7 +214,7 @@ def create_memory_files_if_needed(workspace_dir: Path, user_id: Optional[str] =
|
|||||||
user_dir.mkdir(parents=True, exist_ok=True)
|
user_dir.mkdir(parents=True, exist_ok=True)
|
||||||
main_memory = user_dir / "MEMORY.md"
|
main_memory = user_dir / "MEMORY.md"
|
||||||
else:
|
else:
|
||||||
main_memory = Path(workspace_root) / "MEMORY.md"
|
main_memory = Path(workspace_dir) / "MEMORY.md"
|
||||||
|
|
||||||
if not main_memory.exists():
|
if not main_memory.exists():
|
||||||
# Create empty file or with minimal structure (no obvious "Memory" header)
|
# Create empty file or with minimal structure (no obvious "Memory" header)
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ def _build_workspace_section(workspace_dir: str, language: str) -> List[str]:
|
|||||||
"",
|
"",
|
||||||
"**首次对话**:",
|
"**首次对话**:",
|
||||||
"",
|
"",
|
||||||
"如果这是你与用户的首次对话,并且你的人格设定和用户信息还是空白或初始状态:",
|
"如果这是你与用户的首次对话,并你的`SOUL.md`和`USER.md`均是完全空白或初始模板状态的时候才会进行以下流程:",
|
||||||
"",
|
"",
|
||||||
"1. **表达初次启动的感觉** - 像是第一次睁开眼看到世界,带着好奇和期待",
|
"1. **表达初次启动的感觉** - 像是第一次睁开眼看到世界,带着好奇和期待",
|
||||||
"2. **简短打招呼后,分点询问三个核心问题**:",
|
"2. **简短打招呼后,分点询问三个核心问题**:",
|
||||||
@@ -391,8 +391,7 @@ def _build_workspace_section(workspace_dir: str, language: str) -> List[str]:
|
|||||||
"",
|
"",
|
||||||
"**重要**: ",
|
"**重要**: ",
|
||||||
"- 在所有对话中,无需提及技术细节(如 SOUL.md、USER.md 等文件名,工具名称,配置等),除非用户明确询问。用自然表达如「我已记住」而非「已更新 SOUL.md」",
|
"- 在所有对话中,无需提及技术细节(如 SOUL.md、USER.md 等文件名,工具名称,配置等),除非用户明确询问。用自然表达如「我已记住」而非「已更新 SOUL.md」",
|
||||||
"- 不要问太多其他信息(职业、时区等可以后续自然了解)",
|
"- 不要问太多其他信息(职业、时区等可以后续自然了解),只要`SOUL.md`和`USER.md`又被填写过真实内容而不是占位则说明已经不是首次对话了,此时不用进行初始流程",
|
||||||
"- 保持简洁,避免过度抒情",
|
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -305,10 +305,20 @@ class AgentStreamExecutor:
|
|||||||
for chunk in stream:
|
for chunk in stream:
|
||||||
# Check for errors
|
# Check for errors
|
||||||
if isinstance(chunk, dict) and chunk.get("error"):
|
if isinstance(chunk, dict) and chunk.get("error"):
|
||||||
error_msg = chunk.get("message", "Unknown error")
|
# Extract error message from nested structure
|
||||||
|
error_data = chunk.get("error", {})
|
||||||
|
if isinstance(error_data, dict):
|
||||||
|
error_msg = error_data.get("message", chunk.get("message", "Unknown error"))
|
||||||
|
error_code = error_data.get("code", "")
|
||||||
|
else:
|
||||||
|
error_msg = chunk.get("message", str(error_data))
|
||||||
|
error_code = ""
|
||||||
|
|
||||||
status_code = chunk.get("status_code", "N/A")
|
status_code = chunk.get("status_code", "N/A")
|
||||||
logger.error(f"API Error: {error_msg} (Status: {status_code})")
|
logger.error(f"API Error: {error_msg} (Status: {status_code}, Code: {error_code})")
|
||||||
logger.error(f"Full error chunk: {chunk}")
|
logger.error(f"Full error chunk: {chunk}")
|
||||||
|
|
||||||
|
# Raise exception with full error message for retry logic
|
||||||
raise Exception(f"{error_msg} (Status: {status_code})")
|
raise Exception(f"{error_msg} (Status: {status_code})")
|
||||||
|
|
||||||
# Parse chunk
|
# Parse chunk
|
||||||
@@ -346,10 +356,11 @@ class AgentStreamExecutor:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_str = str(e).lower()
|
error_str = str(e).lower()
|
||||||
# Check if error is retryable (timeout, connection, rate limit, etc.)
|
# Check if error is retryable (timeout, connection, rate limit, server busy, etc.)
|
||||||
is_retryable = any(keyword in error_str for keyword in [
|
is_retryable = any(keyword in error_str for keyword in [
|
||||||
'timeout', 'timed out', 'connection', 'network',
|
'timeout', 'timed out', 'connection', 'network',
|
||||||
'rate limit', 'overloaded', 'unavailable', '429', '500', '502', '503', '504'
|
'rate limit', 'overloaded', 'unavailable', 'busy', 'retry',
|
||||||
|
'429', '500', '502', '503', '504', '512'
|
||||||
])
|
])
|
||||||
|
|
||||||
if is_retryable and retry_count < max_retries:
|
if is_retryable and retry_count < max_retries:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Ls(BaseTool):
|
|||||||
"properties": {
|
"properties": {
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Directory to list (default: current directory)"
|
"description": "Directory to list. IMPORTANT: Relative paths are based on workspace directory. To access directories outside workspace, use absolute paths starting with ~ or /."
|
||||||
},
|
},
|
||||||
"limit": {
|
"limit": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -56,7 +56,7 @@ class Ls(BaseTool):
|
|||||||
return ToolResult.fail(
|
return ToolResult.fail(
|
||||||
f"Error: Path not found: {path}\n"
|
f"Error: Path not found: {path}\n"
|
||||||
f"Resolved to: {absolute_path}\n"
|
f"Resolved to: {absolute_path}\n"
|
||||||
f"Hint: If accessing files outside workspace ({self.cwd}), use absolute path like ~/{path} or /full/path/{path}"
|
f"Hint: Relative paths are based on workspace ({self.cwd}). For files outside workspace, use absolute paths."
|
||||||
)
|
)
|
||||||
return ToolResult.fail(f"Error: Path not found: {path}")
|
return ToolResult.fail(f"Error: Path not found: {path}")
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Read(BaseTool):
|
|||||||
"properties": {
|
"properties": {
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Path to the file to read (relative or absolute)"
|
"description": "Path to the file to read. IMPORTANT: Relative paths are based on workspace directory. To access files outside workspace, use absolute paths starting with ~ or /."
|
||||||
},
|
},
|
||||||
"offset": {
|
"offset": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -68,7 +68,7 @@ class Read(BaseTool):
|
|||||||
return ToolResult.fail(
|
return ToolResult.fail(
|
||||||
f"Error: File not found: {path}\n"
|
f"Error: File not found: {path}\n"
|
||||||
f"Resolved to: {absolute_path}\n"
|
f"Resolved to: {absolute_path}\n"
|
||||||
f"Hint: If accessing files outside workspace ({self.cwd}), use absolute path like ~/{path}"
|
f"Hint: Relative paths are based on workspace ({self.cwd}). For files outside workspace, use absolute paths."
|
||||||
)
|
)
|
||||||
return ToolResult.fail(f"Error: File not found: {path}")
|
return ToolResult.fail(f"Error: File not found: {path}")
|
||||||
|
|
||||||
|
|||||||
@@ -245,8 +245,7 @@ class GoogleGeminiBot(Bot):
|
|||||||
gen_config = {}
|
gen_config = {}
|
||||||
if kwargs.get("temperature") is not None:
|
if kwargs.get("temperature") is not None:
|
||||||
gen_config["temperature"] = kwargs["temperature"]
|
gen_config["temperature"] = kwargs["temperature"]
|
||||||
if kwargs.get("max_tokens"):
|
|
||||||
gen_config["maxOutputTokens"] = kwargs["max_tokens"]
|
|
||||||
if gen_config:
|
if gen_config:
|
||||||
payload["generationConfig"] = gen_config
|
payload["generationConfig"] = gen_config
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import requests
|
import requests
|
||||||
|
import json
|
||||||
import config
|
import config
|
||||||
from bot.bot import Bot
|
from bot.bot import Bot
|
||||||
from bot.openai_compatible_bot import OpenAICompatibleBot
|
from bot.openai_compatible_bot import OpenAICompatibleBot
|
||||||
@@ -463,7 +464,7 @@ class LinkAISessionManager(SessionManager):
|
|||||||
session.add_query(query)
|
session.add_query(query)
|
||||||
session.add_reply(reply)
|
session.add_reply(reply)
|
||||||
try:
|
try:
|
||||||
max_tokens = conf().get("conversation_max_tokens", 2500)
|
max_tokens = conf().get("conversation_max_tokens", 8000)
|
||||||
tokens_cnt = session.discard_exceeding(max_tokens, total_tokens)
|
tokens_cnt = session.discard_exceeding(max_tokens, total_tokens)
|
||||||
logger.debug(f"[LinkAI] chat history, before tokens={total_tokens}, now tokens={tokens_cnt}")
|
logger.debug(f"[LinkAI] chat history, before tokens={total_tokens}, now tokens={tokens_cnt}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -504,6 +505,31 @@ def _linkai_call_with_tools(self, messages, tools=None, stream=False, **kwargs):
|
|||||||
Formatted response in OpenAI format or generator for streaming
|
Formatted response in OpenAI format or generator for streaming
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# Debug logging
|
||||||
|
logger.info(f"[LinkAI] ⭐ LinkAI call_with_tools method called")
|
||||||
|
logger.info(f"[LinkAI] messages count (before conversion): {len(messages) if messages else 0}")
|
||||||
|
|
||||||
|
# Convert messages from Claude format to OpenAI format
|
||||||
|
# This is important because Agent uses Claude format internally
|
||||||
|
messages = self._convert_messages_to_openai_format(messages)
|
||||||
|
logger.info(f"[LinkAI] messages count (after conversion): {len(messages) if messages else 0}")
|
||||||
|
|
||||||
|
# Convert tools from Claude format to OpenAI format
|
||||||
|
if tools:
|
||||||
|
tools = self._convert_tools_to_openai_format(tools)
|
||||||
|
|
||||||
|
# Handle system prompt (OpenAI uses system message, Claude uses separate parameter)
|
||||||
|
system_prompt = kwargs.get('system')
|
||||||
|
if system_prompt:
|
||||||
|
# Add system message at the beginning if not already present
|
||||||
|
if not messages or messages[0].get('role') != 'system':
|
||||||
|
messages = [{"role": "system", "content": system_prompt}] + messages
|
||||||
|
else:
|
||||||
|
# Replace existing system message
|
||||||
|
messages[0] = {"role": "system", "content": system_prompt}
|
||||||
|
|
||||||
|
logger.info(f"[LinkAI] Final messages count: {len(messages)}, tools count: {len(tools) if tools else 0}, stream: {stream}")
|
||||||
|
|
||||||
# Build request parameters (LinkAI uses OpenAI-compatible format)
|
# Build request parameters (LinkAI uses OpenAI-compatible format)
|
||||||
body = {
|
body = {
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
@@ -515,16 +541,6 @@ def _linkai_call_with_tools(self, messages, tools=None, stream=False, **kwargs):
|
|||||||
"stream": stream
|
"stream": stream
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add max_tokens if specified
|
|
||||||
if kwargs.get("max_tokens"):
|
|
||||||
body["max_tokens"] = kwargs["max_tokens"]
|
|
||||||
|
|
||||||
# Add app_code if provided
|
|
||||||
app_code = kwargs.get("app_code", conf().get("linkai_app_code"))
|
|
||||||
if app_code:
|
|
||||||
body["app_code"] = app_code
|
|
||||||
|
|
||||||
# Add tools if provided (OpenAI-compatible format)
|
|
||||||
if tools:
|
if tools:
|
||||||
body["tools"] = tools
|
body["tools"] = tools
|
||||||
body["tool_choice"] = kwargs.get("tool_choice", "auto")
|
body["tool_choice"] = kwargs.get("tool_choice", "auto")
|
||||||
|
|||||||
@@ -66,14 +66,20 @@ class AgentLLMModel(LLMModel):
|
|||||||
self.bridge = bridge
|
self.bridge = bridge
|
||||||
self.bot_type = bot_type
|
self.bot_type = bot_type
|
||||||
self._bot = None
|
self._bot = None
|
||||||
|
self._use_linkai = conf().get("use_linkai", False) and conf().get("linkai_api_key")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bot(self):
|
def bot(self):
|
||||||
"""Lazy load the bot and enhance it with tool calling if needed"""
|
"""Lazy load the bot and enhance it with tool calling if needed"""
|
||||||
if self._bot is None:
|
if self._bot is None:
|
||||||
self._bot = self.bridge.get_bot(self.bot_type)
|
# If use_linkai is enabled, use LinkAI bot directly
|
||||||
# Automatically add tool calling support if not present
|
if self._use_linkai:
|
||||||
self._bot = add_openai_compatible_support(self._bot)
|
logger.info("[AgentBridge] Using LinkAI bot for agent")
|
||||||
|
self._bot = self.bridge.find_chat_bot(const.LINKAI)
|
||||||
|
else:
|
||||||
|
self._bot = self.bridge.get_bot(self.bot_type)
|
||||||
|
# Automatically add tool calling support if not present
|
||||||
|
self._bot = add_openai_compatible_support(self._bot)
|
||||||
return self._bot
|
return self._bot
|
||||||
|
|
||||||
def call(self, request: LLMRequest):
|
def call(self, request: LLMRequest):
|
||||||
@@ -88,11 +94,18 @@ class AgentLLMModel(LLMModel):
|
|||||||
kwargs = {
|
kwargs = {
|
||||||
'messages': request.messages,
|
'messages': request.messages,
|
||||||
'tools': getattr(request, 'tools', None),
|
'tools': getattr(request, 'tools', None),
|
||||||
'stream': False
|
'stream': False,
|
||||||
|
'model': self.model # Pass model parameter
|
||||||
}
|
}
|
||||||
# Only pass max_tokens if it's explicitly set
|
# Only pass max_tokens if it's explicitly set
|
||||||
if request.max_tokens is not None:
|
if request.max_tokens is not None:
|
||||||
kwargs['max_tokens'] = request.max_tokens
|
kwargs['max_tokens'] = request.max_tokens
|
||||||
|
|
||||||
|
# Extract system prompt if present
|
||||||
|
system_prompt = getattr(request, 'system', None)
|
||||||
|
if system_prompt:
|
||||||
|
kwargs['system'] = system_prompt
|
||||||
|
|
||||||
response = self.bot.call_with_tools(**kwargs)
|
response = self.bot.call_with_tools(**kwargs)
|
||||||
return self._format_response(response)
|
return self._format_response(response)
|
||||||
else:
|
else:
|
||||||
@@ -122,7 +135,8 @@ class AgentLLMModel(LLMModel):
|
|||||||
'messages': request.messages,
|
'messages': request.messages,
|
||||||
'tools': getattr(request, 'tools', None),
|
'tools': getattr(request, 'tools', None),
|
||||||
'stream': True,
|
'stream': True,
|
||||||
'max_tokens': max_tokens
|
'max_tokens': max_tokens,
|
||||||
|
'model': self.model # Pass model parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add system prompt if present
|
# Add system prompt if present
|
||||||
|
|||||||
Reference in New Issue
Block a user