forked from HAL9000/cleveragents-core
212 lines
7.0 KiB
Python
212 lines
7.0 KiB
Python
"""
|
|
Interactive session module for CleverAgents.
|
|
|
|
This module defines the InteractiveSession class which handles an interactive command-line session
|
|
with the agent network.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import os
|
|
import readline
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from typing import Dict
|
|
from typing import List
|
|
from typing import Optional
|
|
|
|
from cleveragents.core.exceptions import InteractiveSessionError
|
|
from cleveragents.interactive.commands import execute_command
|
|
from cleveragents.interactive.commands import parse_command
|
|
from cleveragents.routing.router import Router
|
|
|
|
|
|
class InteractiveSession:
|
|
"""
|
|
InteractiveSession manages an interactive chat session with the agent network.
|
|
|
|
Attributes:
|
|
router (Router): The router for processing messages.
|
|
history_file (Optional[Path]): Path to the history file.
|
|
history (List[Dict[str, Any]]): The conversation history.
|
|
context (Dict[str, Any]): The current context for message processing.
|
|
verbose (bool): Whether to enable verbose output.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
routers: Dict[str, Router],
|
|
initial_route_name: str,
|
|
history_file: Optional[Path] = None,
|
|
verbose: bool = False,
|
|
initial_context: Optional[Dict[str, Any]] = None,
|
|
):
|
|
"""
|
|
Initialize the interactive session.
|
|
|
|
Args:
|
|
routers: Dictionary of routers keyed by route name.
|
|
initial_route_name: Route to start the session with.
|
|
history_file: Conversation history file.
|
|
verbose: Verbose flag.
|
|
"""
|
|
self.routers = routers
|
|
self.active_route_name = initial_route_name
|
|
self.router = self.routers[self.active_route_name]
|
|
self.history_file = history_file
|
|
self.history: List[Dict[str, Any]] = []
|
|
# Use supplied initial context (with routers, globals, etc.) or an empty dict
|
|
self.context: Dict[str, Any] = initial_context or {}
|
|
self.verbose = verbose
|
|
self.running = False
|
|
|
|
def load_history(self) -> None:
|
|
"""
|
|
Load conversation history from the history file if it exists.
|
|
|
|
Raises:
|
|
InteractiveSessionError: If the history file cannot be loaded.
|
|
"""
|
|
if self.history_file and self.history_file.exists():
|
|
try:
|
|
with open(self.history_file, "r") as f:
|
|
self.history = json.load(f)
|
|
except Exception as e:
|
|
raise InteractiveSessionError(f"Failed to load history file: {str(e)}")
|
|
|
|
def save_history(self) -> None:
|
|
"""
|
|
Save conversation history to the history file.
|
|
|
|
Raises:
|
|
InteractiveSessionError: If the history file cannot be saved.
|
|
"""
|
|
if self.history_file:
|
|
try:
|
|
with open(self.history_file, "w") as f:
|
|
json.dump(self.history, f, indent=2)
|
|
except Exception as e:
|
|
raise InteractiveSessionError(f"Failed to save history file: {str(e)}")
|
|
|
|
async def process_message(self, message: str) -> str:
|
|
"""
|
|
Process a message through the agent network.
|
|
|
|
Args:
|
|
message: The message to process.
|
|
|
|
Returns:
|
|
The response from the agent network.
|
|
|
|
Raises:
|
|
InteractiveSessionError: If message processing fails.
|
|
"""
|
|
try:
|
|
return await self.router.process_message(message, self.context)
|
|
except Exception as e:
|
|
raise InteractiveSessionError(f"Failed to process message: {str(e)}") from e
|
|
|
|
def add_to_history(self, role: str, content: str) -> None:
|
|
"""
|
|
Add a message to the conversation history.
|
|
|
|
Args:
|
|
role: The role of the message sender ('user' or 'assistant').
|
|
content: The content of the message.
|
|
"""
|
|
self.history.append({"role": role, "content": content})
|
|
|
|
def display_history(self, limit: int = 10) -> None:
|
|
"""
|
|
Display the conversation history.
|
|
|
|
Args:
|
|
limit: Maximum number of messages to display.
|
|
"""
|
|
start = max(0, len(self.history) - limit)
|
|
for i, entry in enumerate(self.history[start:], start=start + 1):
|
|
role = entry["role"]
|
|
content = entry["content"]
|
|
prefix = "You: " if role == "user" else "Agent: "
|
|
print(f"{i}. {prefix}{content}")
|
|
|
|
def process_command(self, command_str: str) -> bool:
|
|
"""
|
|
Parse & execute a slash-command.
|
|
|
|
Args:
|
|
command_str: Raw user input beginning with '/'.
|
|
|
|
Returns:
|
|
True if the session should continue, False otherwise.
|
|
"""
|
|
command_dict = parse_command(command_str)
|
|
result = execute_command(command_dict, {"session": self})
|
|
|
|
if result.get("message"):
|
|
print(result["message"])
|
|
|
|
return result.get("continue", True)
|
|
|
|
async def run(self) -> None:
|
|
"""
|
|
Start the interactive session.
|
|
|
|
This method starts a text-based interactive loop.
|
|
|
|
Raises:
|
|
InteractiveSessionError: If the session cannot be started or an error occurs during the session.
|
|
"""
|
|
try:
|
|
self.load_history()
|
|
|
|
print("Starting interactive session.")
|
|
print(f"Active route: '{self.active_route_name}'")
|
|
print("Type '/help' for commands, '/exit' to quit.")
|
|
self.running = True
|
|
|
|
while self.running:
|
|
try:
|
|
user_input = input("You: ")
|
|
|
|
# Check if this is a command
|
|
if user_input.startswith("/"):
|
|
self.running = self.process_command(user_input)
|
|
continue
|
|
|
|
# Process the message
|
|
self.add_to_history("user", user_input)
|
|
|
|
# Update the per-turn initial message so that routers and
|
|
# template transforms always reference the latest user input.
|
|
self.context["initial_message"] = user_input
|
|
|
|
if self.verbose:
|
|
print("Processing message...")
|
|
|
|
response = await self.process_message(user_input)
|
|
|
|
print(f"Agent: {response}")
|
|
self.add_to_history("assistant", response)
|
|
|
|
except KeyboardInterrupt:
|
|
print("\nInterrupted by user.")
|
|
self.running = False
|
|
break
|
|
except InteractiveSessionError as e:
|
|
if e.__cause__:
|
|
print(f"Error: {str(e.__cause__)}")
|
|
else:
|
|
print(f"Error: {str(e)}")
|
|
except Exception as e:
|
|
print(f"An unexpected error occurred: {e}")
|
|
|
|
self.save_history()
|
|
print("Session ended.")
|
|
|
|
except Exception as e:
|
|
raise InteractiveSessionError(
|
|
f"Failed to run interactive session: {str(e)}"
|
|
)
|