Skip to content

Commit ff061e0

Browse files
committed
Enhance chat configuration management and UI integration
- Updated `services.py` to allow service restart with an option to recreate containers, addressing WSL2 bind mount issues. - Added new chat configuration management functions in `system_controller.py` for loading, saving, and validating chat prompts. - Introduced `ChatSettings` component in the web UI for admin users to manage chat configurations easily. - Updated API service methods in `api.ts` to support chat configuration endpoints. - Integrated chat settings into the system management page for better accessibility.
1 parent ad4b1f9 commit ff061e0

File tree

8 files changed

+475
-14
lines changed

8 files changed

+475
-14
lines changed

backends/advanced/src/advanced_omi_backend/chat_service.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from advanced_omi_backend.database import get_database
2424
from advanced_omi_backend.llm_client import get_llm_client
25+
from advanced_omi_backend.model_registry import get_models_registry
2526
from advanced_omi_backend.services.memory import get_memory_service
2627
from advanced_omi_backend.services.memory.base import MemoryEntry
2728
from advanced_omi_backend.services.obsidian_service import (
@@ -133,7 +134,7 @@ def from_dict(cls, data: Dict) -> "ChatSession":
133134

134135
class ChatService:
135136
"""Service for managing chat sessions and memory-enhanced conversations."""
136-
137+
137138
def __init__(self):
138139
self.db = None
139140
self.sessions_collection: Optional[AsyncIOMotorCollection] = None
@@ -142,6 +143,32 @@ def __init__(self):
142143
self.memory_service = None
143144
self._initialized = False
144145

146+
def _get_system_prompt(self) -> str:
147+
"""
148+
Get system prompt from config with fallback to default.
149+
150+
Returns:
151+
str: System prompt for chat interactions
152+
"""
153+
try:
154+
reg = get_models_registry()
155+
if reg and hasattr(reg, 'config'):
156+
chat_config = reg.config.get('chat', {})
157+
prompt = chat_config.get('system_prompt')
158+
if prompt:
159+
logger.debug("Loaded chat system prompt from config")
160+
return prompt
161+
except Exception as e:
162+
logger.warning(f"Failed to load chat system prompt from config: {e}")
163+
164+
# Fallback to default
165+
logger.debug("Using default chat system prompt")
166+
return """You are a helpful AI assistant with access to the user's personal memories and conversation history.
167+
168+
Use the provided memories and conversation context to give personalized, contextual responses. If memories are relevant, reference them naturally in your response. Be conversational and helpful.
169+
170+
If no relevant memories are available, respond normally based on the conversation context."""
171+
145172
async def initialize(self):
146173
"""Initialize the chat service with database connections."""
147174
if self._initialized:
@@ -392,12 +419,8 @@ async def generate_response_stream(
392419
"timestamp": time.time()
393420
}
394421

395-
# Create system prompt
396-
system_prompt = """You are a helpful AI assistant with access to the user's personal memories and conversation history.
397-
398-
Use the provided memories and conversation context to give personalized, contextual responses. If memories are relevant, reference them naturally in your response. Be conversational and helpful.
399-
400-
If no relevant memories are available, respond normally based on the conversation context."""
422+
# Get system prompt from config
423+
system_prompt = self._get_system_prompt()
401424

402425
# Prepare full prompt
403426
full_prompt = f"{system_prompt}\n\n{context}"

backends/advanced/src/advanced_omi_backend/controllers/system_controller.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,3 +455,103 @@ async def set_memory_provider(provider: str):
455455
except Exception as e:
456456
logger.exception("Error setting memory provider")
457457
raise e
458+
459+
460+
# Chat Configuration Management Functions
461+
462+
async def get_chat_config_yaml() -> str:
463+
"""Get chat system prompt as plain text."""
464+
try:
465+
config_path = _find_config_path()
466+
467+
default_prompt = """You are a helpful AI assistant with access to the user's personal memories and conversation history.
468+
469+
Use the provided memories and conversation context to give personalized, contextual responses. If memories are relevant, reference them naturally in your response. Be conversational and helpful.
470+
471+
If no relevant memories are available, respond normally based on the conversation context."""
472+
473+
if not os.path.exists(config_path):
474+
return default_prompt
475+
476+
with open(config_path, 'r') as f:
477+
full_config = yaml.safe_load(f) or {}
478+
479+
chat_config = full_config.get('chat', {})
480+
system_prompt = chat_config.get('system_prompt', default_prompt)
481+
482+
# Return just the prompt text, not the YAML structure
483+
return system_prompt
484+
485+
except Exception as e:
486+
logger.error(f"Error loading chat config: {e}")
487+
raise
488+
489+
490+
async def save_chat_config_yaml(prompt_text: str) -> dict:
491+
"""Save chat system prompt from plain text."""
492+
try:
493+
config_path = _find_config_path()
494+
495+
# Validate plain text prompt
496+
if not prompt_text or not isinstance(prompt_text, str):
497+
raise ValueError("Prompt must be a non-empty string")
498+
499+
prompt_text = prompt_text.strip()
500+
if len(prompt_text) < 10:
501+
raise ValueError("Prompt too short (minimum 10 characters)")
502+
if len(prompt_text) > 10000:
503+
raise ValueError("Prompt too long (maximum 10000 characters)")
504+
505+
# Create chat config dict
506+
chat_config = {'system_prompt': prompt_text}
507+
508+
# Load full config
509+
if os.path.exists(config_path):
510+
with open(config_path, 'r') as f:
511+
full_config = yaml.safe_load(f) or {}
512+
else:
513+
full_config = {}
514+
515+
# Backup existing config
516+
if os.path.exists(config_path):
517+
backup_path = str(config_path) + '.backup'
518+
shutil.copy2(config_path, backup_path)
519+
logger.info(f"Created config backup at {backup_path}")
520+
521+
# Update chat section
522+
full_config['chat'] = chat_config
523+
524+
# Save
525+
with open(config_path, 'w') as f:
526+
yaml.dump(full_config, f, default_flow_style=False, allow_unicode=True)
527+
528+
# Reload config in memory (hot-reload)
529+
load_models_config(force_reload=True)
530+
531+
logger.info("Chat configuration updated successfully")
532+
533+
return {"success": True, "message": "Chat configuration updated successfully"}
534+
535+
except Exception as e:
536+
logger.error(f"Error saving chat config: {e}")
537+
raise
538+
539+
540+
async def validate_chat_config_yaml(prompt_text: str) -> dict:
541+
"""Validate chat system prompt plain text."""
542+
try:
543+
# Validate plain text prompt
544+
if not isinstance(prompt_text, str):
545+
return {"valid": False, "error": "Prompt must be a string"}
546+
547+
prompt_text = prompt_text.strip()
548+
if len(prompt_text) < 10:
549+
return {"valid": False, "error": "Prompt too short (minimum 10 characters)"}
550+
if len(prompt_text) > 10000:
551+
return {"valid": False, "error": "Prompt too long (maximum 10000 characters)"}
552+
553+
return {"valid": True, "message": "Configuration is valid"}
554+
555+
except Exception as e:
556+
logger.error(f"Error validating chat config: {e}")
557+
return {"valid": False, "error": f"Validation error: {str(e)}"}

backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import logging
88
from typing import Optional
99

10-
from fastapi import APIRouter, Body, Depends, Request
10+
from fastapi import APIRouter, Body, Depends, HTTPException, Request
11+
from fastapi.responses import Response
1112
from pydantic import BaseModel
1213

1314
from advanced_omi_backend.auth import current_active_user, current_superuser
@@ -128,6 +129,53 @@ async def delete_all_user_memories(current_user: User = Depends(current_active_u
128129
return await system_controller.delete_all_user_memories(current_user)
129130

130131

132+
# Chat Configuration Management Endpoints
133+
134+
@router.get("/admin/chat/config", response_class=Response)
135+
async def get_chat_config(current_user: User = Depends(current_superuser)):
136+
"""Get chat configuration as YAML. Admin only."""
137+
try:
138+
yaml_content = await system_controller.get_chat_config_yaml()
139+
return Response(content=yaml_content, media_type="text/plain")
140+
except Exception as e:
141+
logger.error(f"Failed to get chat config: {e}")
142+
raise HTTPException(status_code=500, detail=str(e))
143+
144+
145+
@router.post("/admin/chat/config")
146+
async def save_chat_config(
147+
request: Request,
148+
current_user: User = Depends(current_superuser)
149+
):
150+
"""Save chat configuration from YAML. Admin only."""
151+
try:
152+
yaml_content = await request.body()
153+
yaml_str = yaml_content.decode('utf-8')
154+
result = await system_controller.save_chat_config_yaml(yaml_str)
155+
return result
156+
except ValueError as e:
157+
raise HTTPException(status_code=400, detail=str(e))
158+
except Exception as e:
159+
logger.error(f"Failed to save chat config: {e}")
160+
raise HTTPException(status_code=500, detail=str(e))
161+
162+
163+
@router.post("/admin/chat/config/validate")
164+
async def validate_chat_config(
165+
request: Request,
166+
current_user: User = Depends(current_superuser)
167+
):
168+
"""Validate chat configuration YAML. Admin only."""
169+
try:
170+
yaml_content = await request.body()
171+
yaml_str = yaml_content.decode('utf-8')
172+
result = await system_controller.validate_chat_config_yaml(yaml_str)
173+
return result
174+
except Exception as e:
175+
logger.error(f"Failed to validate chat config: {e}")
176+
raise HTTPException(status_code=500, detail=str(e))
177+
178+
131179
@router.get("/streaming/status")
132180
async def get_streaming_status(request: Request, current_user: User = Depends(current_superuser)):
133181
"""Get status of active streaming sessions and Redis Streams health. Admin only."""

0 commit comments

Comments
 (0)