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:
Fahad
2025-06-14 13:27:19 +04:00
parent a569e316af
commit bc3f98a291
5 changed files with 31 additions and 7 deletions

View File

@@ -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

View File

@@ -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:**

View File

@@ -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:-}

View File

@@ -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):

View File

@@ -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__}")