Remove MCP server dependencies, Add LLM test script, Refactor Slightly adjust UI navigation speed
This commit is contained in:
parent
31ed1da190
commit
cca194160d
@ -18,6 +18,7 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機
|
|||||||
1. **主控模塊 (main.py)**
|
1. **主控模塊 (main.py)**
|
||||||
- 協調各模塊的工作
|
- 協調各模塊的工作
|
||||||
- 初始化 MCP 連接
|
- 初始化 MCP 連接
|
||||||
|
- **容錯處理**:即使 `config.py` 中未配置 MCP 伺服器,或所有伺服器連接失敗,程式現在也會繼續執行,僅打印警告訊息,MCP 功能將不可用。 (Added 2025-04-21)
|
||||||
- 設置並管理主要事件循環
|
- 設置並管理主要事件循環
|
||||||
- 處理程式生命週期管理和資源清理
|
- 處理程式生命週期管理和資源清理
|
||||||
|
|
||||||
|
|||||||
46
config.py
46
config.py
@ -35,29 +35,29 @@ exa_config_arg_string_single_dump = json.dumps(exa_config_dict) # Use this one
|
|||||||
|
|
||||||
# --- MCP Server Configuration ---
|
# --- MCP Server Configuration ---
|
||||||
MCP_SERVERS = {
|
MCP_SERVERS = {
|
||||||
"exa": { # Temporarily commented out to prevent blocking startup
|
#"exa": { # Temporarily commented out to prevent blocking startup
|
||||||
"command": "cmd",
|
# "command": "cmd",
|
||||||
"args": [
|
## "args": [
|
||||||
"/c",
|
# "/c",
|
||||||
"npx",
|
# "npx",
|
||||||
"-y",
|
# "-y",
|
||||||
"@smithery/cli@latest",
|
# "@smithery/cli@latest",
|
||||||
"run",
|
# "run",
|
||||||
"exa",
|
# "exa",
|
||||||
"--config",
|
# "--config",
|
||||||
# Pass the dynamically created config string with the environment variable key
|
# # Pass the dynamically created config string with the environment variable key
|
||||||
exa_config_arg_string_single_dump # Use the single dump variable
|
# exa_config_arg_string_single_dump # Use the single dump variable
|
||||||
],
|
# ],
|
||||||
},
|
#},
|
||||||
"github.com/modelcontextprotocol/servers/tree/main/src/memory": {
|
#"github.com/modelcontextprotocol/servers/tree/main/src/memory": {
|
||||||
"command": "npx",
|
# "command": "npx",
|
||||||
"args": [
|
# "args": [
|
||||||
"-y",
|
# "-y",
|
||||||
"@modelcontextprotocol/server-memory"
|
# "@modelcontextprotocol/server-memory"
|
||||||
],
|
# ],
|
||||||
"disabled": False
|
# "disabled": False
|
||||||
}
|
#}
|
||||||
# Add or remove servers as needed
|
## Add or remove servers as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
# MCP Client Configuration
|
# MCP Client Configuration
|
||||||
|
|||||||
12
main.py
12
main.py
@ -285,7 +285,7 @@ async def initialize_mcp_connections():
|
|||||||
# print(f"Exception caught when connecting to Server '{server_key}': {result}")
|
# print(f"Exception caught when connecting to Server '{server_key}': {result}")
|
||||||
print("\n--- All MCP connection initialization attempts completed ---")
|
print("\n--- All MCP connection initialization attempts completed ---")
|
||||||
print(f"Total discovered MCP tools: {len(all_discovered_mcp_tools)}.")
|
print(f"Total discovered MCP tools: {len(all_discovered_mcp_tools)}.")
|
||||||
print(f"Currently active MCP Sessions: {list(active_mcp_sessions.keys())}")
|
# Removed print statement for active sessions
|
||||||
|
|
||||||
|
|
||||||
# --- Load Persona Function (with corrected syntax) ---
|
# --- Load Persona Function (with corrected syntax) ---
|
||||||
@ -329,12 +329,14 @@ async def run_main_with_exit_stack():
|
|||||||
# 2. Initialize MCP Connections Asynchronously
|
# 2. Initialize MCP Connections Asynchronously
|
||||||
await initialize_mcp_connections()
|
await initialize_mcp_connections()
|
||||||
|
|
||||||
# Exit if no servers connected successfully
|
# Warn if no servers connected successfully, but continue
|
||||||
if not active_mcp_sessions:
|
if not active_mcp_sessions:
|
||||||
print("\nFailed to connect to any MCP Server, program will exit.")
|
print("\n\033[93m[!]\033[0m Unable to connect to any MCP server, or no server is configured.")
|
||||||
return
|
# Removed 'return' statement to allow continuation
|
||||||
|
else:
|
||||||
|
print(f"Successfully connected to {len(active_mcp_sessions)} MCP server(s): {list(active_mcp_sessions.keys())}")
|
||||||
|
|
||||||
initialization_successful = True
|
initialization_successful = True # Keep this, might be useful elsewhere
|
||||||
|
|
||||||
# 3. Get loop and set it for keyboard handlers
|
# 3. Get loop and set it for keyboard handlers
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|||||||
271
test/llm_debug_script.py
Normal file
271
test/llm_debug_script.py
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
# test/llm_debug_script.py
|
||||||
|
# Purpose: Directly interact with the LLM for debugging, bypassing UI interaction.
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import collections
|
||||||
|
import datetime
|
||||||
|
from contextlib import AsyncExitStack
|
||||||
|
|
||||||
|
# Assume these modules are in the parent directory or accessible via PYTHONPATH
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
import config
|
||||||
|
import mcp_client
|
||||||
|
import llm_interaction
|
||||||
|
from mcp import ClientSession, StdioServerParameters, types
|
||||||
|
from mcp.client.stdio import stdio_client
|
||||||
|
|
||||||
|
# --- Global Variables ---
|
||||||
|
active_mcp_sessions: dict[str, ClientSession] = {}
|
||||||
|
all_discovered_mcp_tools: list[dict] = []
|
||||||
|
exit_stack = AsyncExitStack()
|
||||||
|
wolfhart_persona_details: str | None = None
|
||||||
|
conversation_history = collections.deque(maxlen=20) # Shorter history for debugging
|
||||||
|
shutdown_requested = False
|
||||||
|
|
||||||
|
# --- Load Persona Function (Adapted from main.py) ---
|
||||||
|
def load_persona_from_file(filename="persona.json"):
|
||||||
|
"""Loads persona data from a local JSON file relative to the main script dir."""
|
||||||
|
global wolfhart_persona_details
|
||||||
|
try:
|
||||||
|
# Get the directory of the main project, not the test directory
|
||||||
|
project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
filepath = os.path.join(project_dir, filename)
|
||||||
|
print(f"\nAttempting to load Persona data from: {filepath}")
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
raise FileNotFoundError(f"Persona file not found at {filepath}")
|
||||||
|
|
||||||
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
|
persona_data = json.load(f)
|
||||||
|
wolfhart_persona_details = json.dumps(persona_data, ensure_ascii=False, indent=2)
|
||||||
|
print(f"Successfully loaded Persona from '{filename}'.")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Warning: Persona configuration file '{filename}' not found.")
|
||||||
|
wolfhart_persona_details = None
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Error: Failed to parse Persona configuration file '{filename}'.")
|
||||||
|
wolfhart_persona_details = None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Unknown error loading Persona configuration file '{filename}': {e}")
|
||||||
|
wolfhart_persona_details = None
|
||||||
|
|
||||||
|
# --- Initialization Functions (Adapted from main.py) ---
|
||||||
|
async def connect_and_discover(key: str, server_config: dict):
|
||||||
|
"""Connects to a single MCP server, initializes, and discovers tools."""
|
||||||
|
global all_discovered_mcp_tools, active_mcp_sessions, exit_stack
|
||||||
|
print(f"\nProcessing Server: '{key}'")
|
||||||
|
command = server_config.get("command")
|
||||||
|
args = server_config.get("args", [])
|
||||||
|
process_env = os.environ.copy()
|
||||||
|
if server_config.get("env") and isinstance(server_config["env"], dict):
|
||||||
|
process_env.update(server_config["env"])
|
||||||
|
|
||||||
|
if not command:
|
||||||
|
print(f"==> Error: Missing 'command' in Server '{key}' configuration. <==")
|
||||||
|
return
|
||||||
|
|
||||||
|
server_params = StdioServerParameters(
|
||||||
|
command=command, args=args, env=process_env,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"Starting stdio_client for Server '{key}'...")
|
||||||
|
read, write = await exit_stack.enter_async_context(
|
||||||
|
stdio_client(server_params)
|
||||||
|
)
|
||||||
|
print(f"stdio_client for '{key}' active.")
|
||||||
|
|
||||||
|
session = await exit_stack.enter_async_context(
|
||||||
|
ClientSession(read, write)
|
||||||
|
)
|
||||||
|
print(f"ClientSession for '{key}' context entered.")
|
||||||
|
|
||||||
|
print(f"Initializing Session '{key}'...")
|
||||||
|
await session.initialize()
|
||||||
|
print(f"Session '{key}' initialized successfully.")
|
||||||
|
|
||||||
|
active_mcp_sessions[key] = session
|
||||||
|
|
||||||
|
print(f"Discovering tools for Server '{key}'...")
|
||||||
|
tools_as_dicts = await mcp_client.list_mcp_tools(session)
|
||||||
|
if tools_as_dicts:
|
||||||
|
processed_tools = []
|
||||||
|
for tool_dict in tools_as_dicts:
|
||||||
|
if isinstance(tool_dict, dict) and 'name' in tool_dict:
|
||||||
|
tool_dict['_server_key'] = key
|
||||||
|
processed_tools.append(tool_dict)
|
||||||
|
else:
|
||||||
|
print(f"Warning: Unexpected tool format from '{key}': {tool_dict}")
|
||||||
|
all_discovered_mcp_tools.extend(processed_tools)
|
||||||
|
print(f"Processed {len(processed_tools)} tools from Server '{key}'.")
|
||||||
|
else:
|
||||||
|
print(f"Server '{key}' has no available tools.")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"==> Error: Command '{command}' for Server '{key}' not found. Check config.py. <==")
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
print(f"==> Error: Connection to Server '{key}' refused. Is it running? <==")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"==> Critical error initializing connection to Server '{key}': {e} <==")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
async def initialize_mcp_connections():
|
||||||
|
"""Concurrently starts and connects to all configured MCP servers."""
|
||||||
|
print("--- Initializing MCP connections ---")
|
||||||
|
connection_tasks = [
|
||||||
|
asyncio.create_task(connect_and_discover(key, server_config), name=f"connect_{key}")
|
||||||
|
for key, server_config in config.MCP_SERVERS.items()
|
||||||
|
]
|
||||||
|
if connection_tasks:
|
||||||
|
await asyncio.gather(*connection_tasks, return_exceptions=True)
|
||||||
|
print("\n--- MCP connection initialization complete ---")
|
||||||
|
print(f"Total discovered tools: {len(all_discovered_mcp_tools)}")
|
||||||
|
print(f"Active Sessions: {list(active_mcp_sessions.keys())}")
|
||||||
|
|
||||||
|
# --- Cleanup Function (Adapted from main.py) ---
|
||||||
|
async def shutdown():
|
||||||
|
"""Gracefully closes MCP connections."""
|
||||||
|
global shutdown_requested
|
||||||
|
if not shutdown_requested:
|
||||||
|
print("Shutdown initiated.")
|
||||||
|
shutdown_requested = True
|
||||||
|
|
||||||
|
print(f"\nClosing MCP Server connections...")
|
||||||
|
try:
|
||||||
|
await exit_stack.aclose()
|
||||||
|
print("AsyncExitStack closed.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error closing AsyncExitStack: {e}")
|
||||||
|
finally:
|
||||||
|
active_mcp_sessions.clear()
|
||||||
|
all_discovered_mcp_tools.clear()
|
||||||
|
print("Cleanup completed.")
|
||||||
|
|
||||||
|
# --- Main Debug Loop ---
|
||||||
|
async def debug_loop():
|
||||||
|
"""Main loop for interactive LLM debugging."""
|
||||||
|
global shutdown_requested, conversation_history
|
||||||
|
|
||||||
|
# 1. Load Persona
|
||||||
|
load_persona_from_file()
|
||||||
|
|
||||||
|
# 2. Initialize MCP
|
||||||
|
await initialize_mcp_connections()
|
||||||
|
if not active_mcp_sessions:
|
||||||
|
print("\nNo MCP servers connected. LLM tool usage will be limited. Continue? (y/n)")
|
||||||
|
confirm = await asyncio.get_event_loop().run_in_executor(None, sys.stdin.readline)
|
||||||
|
if confirm.strip().lower() != 'y':
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n--- LLM Debug Interface ---")
|
||||||
|
print("Enter your message to the LLM.")
|
||||||
|
print("Type 'quit' or 'exit' to stop.")
|
||||||
|
print("-----------------------------")
|
||||||
|
|
||||||
|
user_name = "Debugger" # Fixed user name for this script
|
||||||
|
|
||||||
|
while not shutdown_requested:
|
||||||
|
try:
|
||||||
|
# Get user input asynchronously
|
||||||
|
print(f"\n{user_name}: ", end="")
|
||||||
|
user_input_line = await asyncio.get_event_loop().run_in_executor(
|
||||||
|
None, sys.stdin.readline
|
||||||
|
)
|
||||||
|
user_input = user_input_line.strip()
|
||||||
|
|
||||||
|
if not user_input:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user_input.lower() in ['quit', 'exit']:
|
||||||
|
shutdown_requested = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# Add user message to history
|
||||||
|
timestamp = datetime.datetime.now()
|
||||||
|
conversation_history.append((timestamp, 'user', user_name, user_input))
|
||||||
|
|
||||||
|
print(f"\n{config.PERSONA_NAME} is thinking...")
|
||||||
|
|
||||||
|
# Call LLM interaction function
|
||||||
|
bot_response_data = await llm_interaction.get_llm_response(
|
||||||
|
current_sender_name=user_name,
|
||||||
|
history=list(conversation_history),
|
||||||
|
mcp_sessions=active_mcp_sessions,
|
||||||
|
available_mcp_tools=all_discovered_mcp_tools,
|
||||||
|
persona_details=wolfhart_persona_details
|
||||||
|
)
|
||||||
|
|
||||||
|
# Print the full response structure for debugging
|
||||||
|
print("\n--- LLM Response Data ---")
|
||||||
|
print(json.dumps(bot_response_data, indent=2, ensure_ascii=False))
|
||||||
|
print("-------------------------")
|
||||||
|
|
||||||
|
# Extract and print key parts
|
||||||
|
bot_dialogue = bot_response_data.get("dialogue", "")
|
||||||
|
thoughts = bot_response_data.get("thoughts", "")
|
||||||
|
commands = bot_response_data.get("commands", [])
|
||||||
|
valid_response = bot_response_data.get("valid_response", False)
|
||||||
|
|
||||||
|
if thoughts:
|
||||||
|
print(f"\nThoughts: {thoughts}")
|
||||||
|
if commands:
|
||||||
|
print(f"\nCommands:")
|
||||||
|
for cmd in commands:
|
||||||
|
print(f" - Type: {cmd.get('type')}, Params: {cmd.get('parameters')}")
|
||||||
|
if bot_dialogue:
|
||||||
|
print(f"\n{config.PERSONA_NAME}: {bot_dialogue}")
|
||||||
|
if valid_response:
|
||||||
|
# Add valid bot response to history
|
||||||
|
timestamp = datetime.datetime.now()
|
||||||
|
conversation_history.append((timestamp, 'bot', config.PERSONA_NAME, bot_dialogue))
|
||||||
|
else:
|
||||||
|
print("(Note: LLM marked this dialogue as potentially invalid/incomplete)")
|
||||||
|
else:
|
||||||
|
print(f"\n{config.PERSONA_NAME}: (No dialogue content)")
|
||||||
|
|
||||||
|
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
print("\nInterrupted. Shutting down...")
|
||||||
|
shutdown_requested = True
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nError during interaction: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
# Optionally break or continue after error
|
||||||
|
# break
|
||||||
|
|
||||||
|
print("\nExiting debug loop.")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Program Entry Point ---
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Starting LLM Debug Script...")
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
main_task = None
|
||||||
|
try:
|
||||||
|
main_task = loop.create_task(debug_loop())
|
||||||
|
loop.run_until_complete(main_task)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nCtrl+C detected. Initiating shutdown...")
|
||||||
|
shutdown_requested = True
|
||||||
|
if main_task and not main_task.done():
|
||||||
|
main_task.cancel()
|
||||||
|
# Allow cancellation to propagate
|
||||||
|
loop.run_until_complete(main_task)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Top-level error: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
# Ensure shutdown runs even if loop was interrupted
|
||||||
|
if not exit_stack.is_active: # Check if already closed
|
||||||
|
print("Running final shutdown...")
|
||||||
|
loop.run_until_complete(shutdown())
|
||||||
|
loop.close()
|
||||||
|
print("LLM Debug Script finished.")
|
||||||
@ -979,7 +979,7 @@ def remove_user_position(detector: DetectionModule,
|
|||||||
return False # Indicate failure, but let main loop decide next step
|
return False # Indicate failure, but let main loop decide next step
|
||||||
interactor.click_at(confirm_locs[0][0], confirm_locs[0][1])
|
interactor.click_at(confirm_locs[0][0], confirm_locs[0][1])
|
||||||
print("Clicked Confirm button. Position should be dismissed.")
|
print("Clicked Confirm button. Position should be dismissed.")
|
||||||
time.sleep(0.1) # Wait for action to complete
|
time.sleep(0.05) # Wait for action to complete (Reduced from 0.1)
|
||||||
|
|
||||||
# 9. Cleanup: Return to Chat Room
|
# 9. Cleanup: Return to Chat Room
|
||||||
# Click Close on position page (should now be back on capitol page implicitly)
|
# Click Close on position page (should now be back on capitol page implicitly)
|
||||||
@ -987,7 +987,7 @@ def remove_user_position(detector: DetectionModule,
|
|||||||
if close_locs:
|
if close_locs:
|
||||||
interactor.click_at(close_locs[0][0], close_locs[0][1])
|
interactor.click_at(close_locs[0][0], close_locs[0][1])
|
||||||
print("Clicked Close button (returning to Capitol).")
|
print("Clicked Close button (returning to Capitol).")
|
||||||
time.sleep(0.1)
|
time.sleep(0.05) # Reduced from 0.1
|
||||||
else:
|
else:
|
||||||
print("Warning: Close button not found after confirm, attempting back arrow anyway.")
|
print("Warning: Close button not found after confirm, attempting back arrow anyway.")
|
||||||
|
|
||||||
@ -996,7 +996,7 @@ def remove_user_position(detector: DetectionModule,
|
|||||||
if back_arrow_locs:
|
if back_arrow_locs:
|
||||||
interactor.click_at(back_arrow_locs[0][0], back_arrow_locs[0][1])
|
interactor.click_at(back_arrow_locs[0][0], back_arrow_locs[0][1])
|
||||||
print("Clicked Back Arrow (returning to Profile).")
|
print("Clicked Back Arrow (returning to Profile).")
|
||||||
time.sleep(0.1)
|
time.sleep(0.05) # Reduced from 0.1
|
||||||
else:
|
else:
|
||||||
print("Warning: Back arrow not found on Capitol page, attempting ESC cleanup.")
|
print("Warning: Back arrow not found on Capitol page, attempting ESC cleanup.")
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user