Make conversation timeout configuration (so that you're able to resume a discussion manually with another model with a gap of several hours in case you stepped away)
This commit is contained in:
@@ -86,6 +86,12 @@ DEFAULT_THINKING_MODE_THINKDEEP=high
|
||||
# The Redis URL for conversation threading - typically managed by docker-compose
|
||||
# REDIS_URL=redis://redis:6379/0
|
||||
|
||||
# Optional: Conversation timeout (hours)
|
||||
# How long AI-to-AI conversation threads persist before expiring
|
||||
# Longer timeouts use more Redis memory but allow resuming conversations later
|
||||
# Defaults to 3 hours if not specified
|
||||
CONVERSATION_TIMEOUT_HOURS=3
|
||||
|
||||
# Optional: Logging level (DEBUG, INFO, WARNING, ERROR)
|
||||
# DEBUG: Shows detailed operational messages for troubleshooting (default)
|
||||
# INFO: Shows general operational messages
|
||||
|
||||
@@ -470,7 +470,7 @@ This server enables **true AI collaboration** between Claude and multiple AI mod
|
||||
- **Asynchronous workflow**: Conversations don't need to be sequential - Claude can work on tasks between exchanges, then return to Gemini with additional context and progress updates
|
||||
- **Incremental updates**: Share only new information in each exchange while maintaining full conversation history
|
||||
- **Automatic 25K limit bypass**: Each exchange sends only incremental context, allowing unlimited total conversation size
|
||||
- Up to 5 exchanges per conversation with 1-hour expiry
|
||||
- Up to 5 exchanges per conversation with 3-hour expiry (configurable)
|
||||
- Thread-safe with Redis persistence across all tools
|
||||
|
||||
**Cross-tool & Cross-Model Continuation Example:**
|
||||
|
||||
@@ -40,6 +40,7 @@ services:
|
||||
- CUSTOM_MODEL_NAME=${CUSTOM_MODEL_NAME:-llama3.2}
|
||||
- DEFAULT_MODEL=${DEFAULT_MODEL:-auto}
|
||||
- DEFAULT_THINKING_MODE_THINKDEEP=${DEFAULT_THINKING_MODE_THINKDEEP:-high}
|
||||
- CONVERSATION_TIMEOUT_HOURS=${CONVERSATION_TIMEOUT_HOURS:-3}
|
||||
# Model usage restrictions
|
||||
- OPENAI_ALLOWED_MODELS=${OPENAI_ALLOWED_MODELS:-}
|
||||
- GOOGLE_ALLOWED_MODELS=${GOOGLE_ALLOWED_MODELS:-}
|
||||
|
||||
@@ -12,6 +12,7 @@ import pytest
|
||||
|
||||
from server import get_follow_up_instructions
|
||||
from utils.conversation_memory import (
|
||||
CONVERSATION_TIMEOUT_SECONDS,
|
||||
MAX_CONVERSATION_TURNS,
|
||||
ConversationTurn,
|
||||
ThreadContext,
|
||||
@@ -40,7 +41,7 @@ class TestConversationMemory:
|
||||
mock_client.setex.assert_called_once()
|
||||
call_args = mock_client.setex.call_args
|
||||
assert call_args[0][0] == f"thread:{thread_id}" # key
|
||||
assert call_args[0][1] == 3600 # TTL
|
||||
assert call_args[0][1] == CONVERSATION_TIMEOUT_SECONDS # TTL from configuration
|
||||
|
||||
@patch("utils.conversation_memory.get_redis_client")
|
||||
def test_get_thread_valid(self, mock_redis):
|
||||
|
||||
@@ -59,6 +59,22 @@ logger = logging.getLogger(__name__)
|
||||
# Configuration constants
|
||||
MAX_CONVERSATION_TURNS = 10 # Maximum turns allowed per conversation thread
|
||||
|
||||
# Get conversation timeout from environment (in hours), default to 3 hours
|
||||
try:
|
||||
CONVERSATION_TIMEOUT_HOURS = int(os.getenv("CONVERSATION_TIMEOUT_HOURS", "3"))
|
||||
if CONVERSATION_TIMEOUT_HOURS <= 0:
|
||||
logger.warning(
|
||||
f"Invalid CONVERSATION_TIMEOUT_HOURS value ({CONVERSATION_TIMEOUT_HOURS}), using default of 3 hours"
|
||||
)
|
||||
CONVERSATION_TIMEOUT_HOURS = 3
|
||||
except ValueError:
|
||||
logger.warning(
|
||||
f"Invalid CONVERSATION_TIMEOUT_HOURS value ('{os.getenv('CONVERSATION_TIMEOUT_HOURS')}'), using default of 3 hours"
|
||||
)
|
||||
CONVERSATION_TIMEOUT_HOURS = 3
|
||||
|
||||
CONVERSATION_TIMEOUT_SECONDS = CONVERSATION_TIMEOUT_HOURS * 3600
|
||||
|
||||
|
||||
class ConversationTurn(BaseModel):
|
||||
"""
|
||||
@@ -154,7 +170,7 @@ def create_thread(tool_name: str, initial_request: dict[str, Any], parent_thread
|
||||
str: UUID thread identifier that can be used for continuation
|
||||
|
||||
Note:
|
||||
- Thread expires after 1 hour (3600 seconds)
|
||||
- Thread expires after the configured timeout (default: 3 hours)
|
||||
- Non-serializable parameters are filtered out automatically
|
||||
- Thread can be continued by any tool using the returned UUID
|
||||
- Parent thread creates a chain for conversation history traversal
|
||||
@@ -179,10 +195,10 @@ def create_thread(tool_name: str, initial_request: dict[str, Any], parent_thread
|
||||
initial_context=filtered_context,
|
||||
)
|
||||
|
||||
# Store in Redis with 1 hour TTL to prevent indefinite accumulation
|
||||
# Store in Redis with configurable TTL to prevent indefinite accumulation
|
||||
client = get_redis_client()
|
||||
key = f"thread:{thread_id}"
|
||||
client.setex(key, 3600, context.model_dump_json())
|
||||
client.setex(key, CONVERSATION_TIMEOUT_SECONDS, context.model_dump_json())
|
||||
|
||||
logger.debug(f"[THREAD] Created new thread {thread_id} with parent {parent_thread_id}")
|
||||
|
||||
@@ -261,7 +277,7 @@ def add_turn(
|
||||
- Redis connection failure
|
||||
|
||||
Note:
|
||||
- Refreshes thread TTL to 1 hour on successful update
|
||||
- Refreshes thread TTL to configured timeout on successful update
|
||||
- Turn limits prevent runaway conversations
|
||||
- File references are preserved for cross-tool access
|
||||
- Model information enables cross-provider conversations
|
||||
@@ -297,7 +313,7 @@ def add_turn(
|
||||
try:
|
||||
client = get_redis_client()
|
||||
key = f"thread:{thread_id}"
|
||||
client.setex(key, 3600, context.model_dump_json()) # Refresh TTL to 1 hour
|
||||
client.setex(key, CONVERSATION_TIMEOUT_SECONDS, context.model_dump_json()) # Refresh TTL to configured timeout
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(f"[FLOW] Failed to save turn to Redis: {type(e).__name__}")
|
||||
|
||||
Reference in New Issue
Block a user