mirror of
https://github.com/open-webui/functions.git
synced 2026-07-01 17:58:56 -04:00
refac
- Added support for changing agents mid-prompt/workflow. - Removed download command for personas (all auto now) - Added support for future auto-updating of personas
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# 🎭 Agent Hotswap
|
||||
|
||||
> **Transform your OpenWebUI experience with intelligent AI persona switching**
|
||||
> **Revolutionary AI persona switching with dynamic multi-persona capabilities**
|
||||
|
||||
[](https://github.com/open-webui/functions)
|
||||
[](https://github.com/open-webui/functions)
|
||||
[](https://github.com/open-webui/open-webui)
|
||||
[](LICENSE)
|
||||
|
||||
@@ -10,15 +10,17 @@
|
||||
|
||||
## 🌟 Overview
|
||||
|
||||
**Agent Hotswap** is a powerful OpenWebUI filter that enables seamless switching between 50+ specialized AI personas with a simple command system. Each persona comes with unique capabilities, expertise, and communication styles, all built on a **Master Controller** foundation that provides universal OpenWebUI-native features.
|
||||
**Agent Hotswap** is the most advanced OpenWebUI filter for AI persona management, enabling seamless switching between 50+ specialized AI personas with breakthrough **dynamic multi-persona capabilities**. Execute complex workflows involving multiple experts in a single conversation, with automatic persona discovery and just-in-time loading.
|
||||
|
||||
### ✨ Key Features
|
||||
### ✨ Revolutionary Features
|
||||
|
||||
- 🎛️ **Master Controller System** - Transparent foundation providing OpenWebUI capabilities to all personas
|
||||
- 🎛️ **Master Controller System** - Universal OpenWebUI capabilities foundation for all personas
|
||||
- 🔄 **Dynamic Multi-Persona Sequences** - Multiple persona switches within a single prompt
|
||||
- 🔍 **Universal Persona Detection** - Automatically works with any current or future personas
|
||||
- ⚡ **Just-In-Time Loading** - Only loads personas actually requested for optimal performance
|
||||
- 🚀 **Instant Persona Switching** - Simple `!command` syntax for immediate role changes
|
||||
- 📦 **Auto-Download Collection** - Automatically fetches the complete 50+ persona collection on first run
|
||||
- 📦 **Auto-Download Collection** - Automatically fetches the complete 50+ persona collection
|
||||
- 🔄 **Auto-Updates** - Keeps persona collection current with weekly checks
|
||||
- ⚡ **Performance Optimized** - Smart caching, pre-compiled patterns, and efficient loading
|
||||
- 🎨 **Rich Rendering Support** - LaTeX math, Mermaid diagrams, HTML artifacts built-in
|
||||
- 💾 **Automatic Backups** - Safe persona management with rollback capabilities
|
||||
- 🔧 **Cross-Platform** - Works with both Docker and native OpenWebUI installations
|
||||
@@ -44,15 +46,89 @@ The plugin automatically:
|
||||
|
||||
### 3️⃣ Start Using Personas
|
||||
```bash
|
||||
# Single persona switching
|
||||
!list # See all available personas
|
||||
!coder # Become a programming expert
|
||||
!writer # Transform into a creative writer
|
||||
!analyst # Switch to data analysis mode
|
||||
!reset # Return to default assistant
|
||||
|
||||
# Revolutionary multi-persona sequences
|
||||
!writer create a story about AI !physicist explain the science !teacher create study questions !artist design cover art
|
||||
|
||||
# Reset to default
|
||||
!reset # Return to standard assistant
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔥 **NEW: Dynamic Multi-Persona System**
|
||||
|
||||
### **Multi-Persona Sequences**
|
||||
Execute complex workflows with multiple experts in a single prompt:
|
||||
|
||||
```bash
|
||||
# Creative collaboration
|
||||
!writer start a sci-fi story !physicist verify the science !historian add historical context !artist describe the visuals !writer conclude the story
|
||||
|
||||
# Educational deep-dive
|
||||
!teacher introduce quantum mechanics !physicist explain the theory !engineer show applications !philosopher discuss implications
|
||||
|
||||
# Business analysis
|
||||
!analyst present market data !economist add economic context !consultant recommend strategy !projectmanager create implementation timeline
|
||||
```
|
||||
|
||||
### **How Multi-Persona Hotswapping Works**
|
||||
|
||||
#### **1. Universal Discovery Phase**
|
||||
```
|
||||
User Input: "!writer create story !teacher explain techniques !physicist add science"
|
||||
|
||||
↓ Universal Pattern Detection ↓
|
||||
|
||||
Discovered Commands: ['writer', 'teacher', 'physicist']
|
||||
```
|
||||
|
||||
#### **2. Just-In-Time Loading**
|
||||
```
|
||||
Available Personas: 50+ in collection
|
||||
↓ Smart Loading ↓
|
||||
Loaded: Only 3 requested personas + Master Controller
|
||||
Memory Usage: Minimal (only what's needed)
|
||||
```
|
||||
|
||||
#### **3. Dynamic System Construction**
|
||||
```
|
||||
System Message Built:
|
||||
├── Master Controller (OpenWebUI capabilities)
|
||||
├── Writer Persona Definition
|
||||
├── Teacher Persona Definition
|
||||
├── Physicist Persona Definition
|
||||
└── Multi-Persona Execution Framework
|
||||
```
|
||||
|
||||
#### **4. Sequence Parsing & Instruction Building**
|
||||
```
|
||||
Original: "!writer create story !teacher explain techniques !physicist add science"
|
||||
|
||||
↓ Parsed Into Structured Sequence ↓
|
||||
|
||||
Step 1 - Creative Writer: create story
|
||||
Step 2 - Educator: explain techniques
|
||||
Step 3 - Quantum Physicist: add science
|
||||
```
|
||||
|
||||
#### **5. Intelligent Execution**
|
||||
The LLM receives comprehensive instructions and executes each persona switch seamlessly, maintaining context and flow throughout the entire sequence.
|
||||
|
||||
### **Universal Compatibility**
|
||||
Works with **ANY** persona combination:
|
||||
- **Current 50+ personas**: `!coder !analyst !economist`
|
||||
- **Future personas**: Automatically detects new additions
|
||||
- **Mixed combinations**: `!existing_persona !future_persona !another_new_one`
|
||||
- **Unlimited sequences**: `!a !b !c !d !e !f !g !h !i !j...`
|
||||
|
||||
---
|
||||
|
||||
## 💡 Usage Guide
|
||||
|
||||
### Core Commands
|
||||
@@ -62,6 +138,28 @@ The plugin automatically:
|
||||
| `!list` | Display all available personas in a formatted table |
|
||||
| `!reset`, `!default`, `!normal` | Return to standard assistant mode |
|
||||
| `!{persona_name}` | Switch to any specific persona |
|
||||
| `!{persona1} task1 !{persona2} task2` | **NEW:** Multi-persona sequences |
|
||||
|
||||
### Single Persona Switching
|
||||
|
||||
```bash
|
||||
!coder # Switch to programming expert
|
||||
!writer # Become creative writer
|
||||
!analyst # Transform into data analyst
|
||||
```
|
||||
|
||||
### Multi-Persona Workflows
|
||||
|
||||
```bash
|
||||
# Content creation pipeline
|
||||
!writer draft blog post !researcher fact-check claims !editor polish prose !marketer add compelling headlines
|
||||
|
||||
# Technical analysis
|
||||
!analyst examine data !statistician run tests !consultant interpret results !presenter create executive summary
|
||||
|
||||
# Creative projects
|
||||
!novelist create plot !historian verify period details !scientist explain technology !artist design concepts
|
||||
```
|
||||
|
||||
### Available Personas
|
||||
|
||||
@@ -107,9 +205,10 @@ The plugin includes 50+ specialized personas covering:
|
||||
### Persona Features
|
||||
|
||||
- **Automatic Introduction** - Each persona introduces itself on activation
|
||||
- **Persistent Context** - Persona remains active across messages until changed
|
||||
- **Persistent Context** - Single personas remain active across messages until changed
|
||||
- **Specialized Knowledge** - Tailored expertise and communication style
|
||||
- **OpenWebUI Integration** - Full access to LaTeX, Mermaid, artifacts, and more
|
||||
- ****NEW:** Multi-Persona Transitions** - Smooth handoffs between experts in sequences
|
||||
|
||||
---
|
||||
|
||||
@@ -124,13 +223,55 @@ The **Master Controller** is the invisible foundation that powers every persona:
|
||||
- **Transparent** - Users never see or interact with it directly
|
||||
- **Smart Persistence** - Only removed on reset/default commands
|
||||
|
||||
### Persona Architecture
|
||||
### Dynamic Multi-Persona Architecture
|
||||
|
||||
Each persona includes:
|
||||
- **Name** - Display name with emoji for visual identification
|
||||
- **Prompt** - Comprehensive system prompt defining personality and expertise
|
||||
- **Description** - User-facing explanation of capabilities
|
||||
- **Rules** - Structured guidelines for behavior and responses
|
||||
#### **Universal Detection Engine**
|
||||
- **Pattern Recognition**: Automatically detects any `!{word}` pattern
|
||||
- **Future-Proof**: Works with personas that don't exist yet
|
||||
- **Smart Filtering**: Distinguishes between personas and special commands
|
||||
- **Error Handling**: Gracefully handles unknown commands
|
||||
|
||||
#### **Just-In-Time Loading System**
|
||||
```
|
||||
Available: 50+ personas in collection
|
||||
Requested: !writer !physicist !teacher
|
||||
↓ Smart Loading ↓
|
||||
Loaded: 3 personas + Master Controller
|
||||
Memory: ~75% reduction vs loading all personas
|
||||
Performance: Optimal regardless of collection size
|
||||
```
|
||||
|
||||
#### **Dynamic System Message Construction**
|
||||
Each multi-persona session gets a custom system message containing:
|
||||
1. **Master Controller** - OpenWebUI capabilities foundation
|
||||
2. **Requested Personas** - Only the personas actually needed
|
||||
3. **Execution Framework** - Instructions for seamless switching
|
||||
4. **Available Commands** - List of active personas for the session
|
||||
|
||||
### Persona Transition Control
|
||||
|
||||
Control how persona switches are displayed:
|
||||
|
||||
#### **Visible Transitions** (Default)
|
||||
```
|
||||
🎭 **Creative Writer**
|
||||
Once upon a time, in a world where artificial intelligence...
|
||||
|
||||
🎭 **Quantum Physicist**
|
||||
The quantum mechanics underlying this scenario involve...
|
||||
|
||||
🎭 **Educator**
|
||||
Let me explain these concepts in simpler terms...
|
||||
```
|
||||
|
||||
#### **Silent Transitions**
|
||||
```
|
||||
Once upon a time, in a world where artificial intelligence...
|
||||
|
||||
The quantum mechanics underlying this scenario involve...
|
||||
|
||||
Let me explain these concepts in simpler terms...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -142,16 +283,36 @@ Each persona includes:
|
||||
|---------|---------|-------------|
|
||||
| `keyword_prefix` | `!` | Command prefix for persona switching |
|
||||
| `case_sensitive` | `false` | Whether commands are case-sensitive |
|
||||
| `persistent_persona` | `true` | Keep persona active across messages |
|
||||
| `persistent_persona` | `true` | Keep single personas active across messages |
|
||||
| `show_persona_info` | `true` | Display status messages for switches |
|
||||
|
||||
### Advanced Settings
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `multi_persona_transitions` | `true` | **NEW:** Show transition announcements in multi-persona responses |
|
||||
| `status_message_auto_close_delay_ms` | `5000` | Auto-close delay for status messages |
|
||||
| `debug_performance` | `false` | Enable performance debugging logs |
|
||||
|
||||
### **NEW: Multi-Persona Transition Control**
|
||||
|
||||
The `multi_persona_transitions` valve controls how persona switches are displayed in multi-persona sequences:
|
||||
|
||||
**Enabled (Default):**
|
||||
- Shows `🎭 **Persona Name**` announcements
|
||||
- Clear visual indication of expert transitions
|
||||
- Helpful for understanding which expert is responding
|
||||
|
||||
**Disabled:**
|
||||
- Silent, seamless transitions
|
||||
- Clean output without transition markers
|
||||
- Personas switch invisibly behind the scenes
|
||||
|
||||
**When to disable transitions:**
|
||||
- Creative writing where transitions would break immersion
|
||||
- Professional reports requiring clean formatting
|
||||
- When persona switches should be transparent to end users
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ System Architecture
|
||||
@@ -176,6 +337,29 @@ The plugin automatically detects your installation type:
|
||||
├── backups/ # Automatic backups
|
||||
```
|
||||
|
||||
### **NEW: Universal Detection Architecture**
|
||||
|
||||
#### **Pattern Compilation System**
|
||||
```python
|
||||
# Universal pattern matches any valid persona command
|
||||
Pattern: !{word} where word = [a-zA-Z][a-zA-Z0-9_]*
|
||||
|
||||
Examples Matched:
|
||||
✅ !coder, !writer, !analyst (existing)
|
||||
✅ !quantum_engineer, !bioethicist (future)
|
||||
✅ !custom_persona_123 (user-defined)
|
||||
❌ !123invalid (invalid format)
|
||||
```
|
||||
|
||||
#### **Dynamic Loading Pipeline**
|
||||
```
|
||||
1. Parse Input → Discover Commands → [!writer, !physicist, !teacher]
|
||||
2. Load Collection → Validate Existence → [✅writer, ✅physicist, ✅teacher]
|
||||
3. Build System → Include Definitions → Master + 3 Personas
|
||||
4. Create Instructions → Structure Sequence → Step-by-step execution
|
||||
5. Execute → LLM follows sequence → Multi-expert response
|
||||
```
|
||||
|
||||
### Auto-Update System
|
||||
|
||||
- **Weekly Checks** - Automatically checks for updates to persona collection
|
||||
@@ -185,32 +369,65 @@ The plugin automatically detects your installation type:
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Advanced Use Cases
|
||||
|
||||
### Creative Collaboration
|
||||
```bash
|
||||
!writer start mystery novel !detective add investigative realism !psychologist develop character depth !editor polish prose !marketer create book blurb
|
||||
```
|
||||
|
||||
### Technical Documentation
|
||||
```bash
|
||||
!engineer explain system architecture !coder provide implementation examples !teacher create tutorials !technical_writer polish documentation
|
||||
```
|
||||
|
||||
### Business Strategy
|
||||
```bash
|
||||
!analyst present market data !economist add macro trends !consultant recommend strategies !projectmanager create timelines !presenter format executive summary
|
||||
```
|
||||
|
||||
### Educational Content
|
||||
```bash
|
||||
!teacher introduce topic !researcher provide latest findings !philosopher explore implications !artist create visual aids !writer craft engaging narrative
|
||||
```
|
||||
|
||||
### Problem Solving
|
||||
```bash
|
||||
!analyst define problem !researcher gather evidence !consultant brainstorm solutions !engineer evaluate feasibility !projectmanager plan implementation
|
||||
```
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**❌ Persona Not Loading**
|
||||
**❌ Multi-Persona Not Working**
|
||||
```
|
||||
Solution:
|
||||
1. Use !list to verify persona exists
|
||||
1. Ensure multiple !commands in single message
|
||||
2. Check that filter is enabled
|
||||
3. Restart OpenWebUI if needed
|
||||
3. Verify personas exist with !list
|
||||
4. Check transition settings in configuration
|
||||
```
|
||||
|
||||
**❌ Commands Not Recognized**
|
||||
**❌ Unknown Persona Commands**
|
||||
```
|
||||
Solution:
|
||||
- Check keyword_prefix setting (default: "!")
|
||||
- Ensure case_sensitive matches your usage
|
||||
- Verify filter is enabled and active
|
||||
Behavior: System gracefully ignores unknown commands
|
||||
Status: Shows "⚠️ Unknown: !invalid_persona"
|
||||
Solution: Use !list to see available personas
|
||||
```
|
||||
|
||||
**❌ Auto-Download Failed**
|
||||
**❌ Performance Issues with Large Sequences**
|
||||
```
|
||||
Solution:
|
||||
- Check internet connectivity
|
||||
- Plugin will create minimal config with basic personas
|
||||
- Auto-retry will happen on next restart
|
||||
Optimization: System only loads requested personas
|
||||
Memory: Scales efficiently regardless of sequence length
|
||||
Tip: No performance penalty for complex workflows
|
||||
```
|
||||
|
||||
**❌ Transitions Too Verbose/Invisible**
|
||||
```
|
||||
Solution: Adjust multi_persona_transitions valve
|
||||
- Enable: Shows clear 🎭 **Persona** markers
|
||||
- Disable: Silent, seamless transitions
|
||||
```
|
||||
|
||||
### Recovery
|
||||
@@ -220,6 +437,13 @@ Solution:
|
||||
2. Plugin will auto-download fresh persona collection
|
||||
3. Use `!list` to verify restoration
|
||||
|
||||
**Clear Persona State:**
|
||||
```bash
|
||||
!reset # Clears all active personas
|
||||
!default # Alternative reset command
|
||||
!normal # Another reset option
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Advanced Features
|
||||
@@ -230,8 +454,8 @@ Add custom personas by editing the `personas.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"custom_key": {
|
||||
"name": "🎯 Your Custom Persona",
|
||||
"custom_expert": {
|
||||
"name": "🎯 Your Custom Expert",
|
||||
"prompt": "You are a specialized assistant for...",
|
||||
"description": "Brief description of capabilities",
|
||||
"rules": [
|
||||
@@ -244,10 +468,24 @@ Add custom personas by editing the `personas.json` file:
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
- **Universal Detection** - Works with unlimited personas
|
||||
- **Smart Caching** - Only reloads when files change
|
||||
- **Just-In-Time Loading** - Only loads requested personas
|
||||
- **Pattern Pre-compilation** - Regex patterns compiled once
|
||||
- **Change Detection** - File modification time tracking
|
||||
- **Lazy Loading** - Personas loaded on-demand
|
||||
|
||||
### **NEW: Multi-Persona Performance**
|
||||
|
||||
```
|
||||
Traditional Approach: Load all 50+ personas
|
||||
Memory Usage: High
|
||||
Loading Time: Slow
|
||||
|
||||
Dynamic Approach: Load only requested personas
|
||||
Memory Usage: Minimal
|
||||
Loading Time: Instant
|
||||
Scalability: Infinite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -260,6 +498,7 @@ Include the following information:
|
||||
- **Filter Configuration** - Relevant valve settings
|
||||
- **Error Messages** - Full error text and logs
|
||||
- **Reproduction Steps** - How to recreate the issue
|
||||
- **Multi-Persona Details** - If issue involves persona sequences
|
||||
|
||||
### Persona Contributions
|
||||
|
||||
@@ -267,6 +506,43 @@ Guidelines for new personas:
|
||||
- **Clear Purpose** - Well-defined role and expertise
|
||||
- **Comprehensive Prompt** - Detailed behavioral instructions
|
||||
- **User-Friendly Description** - Clear capability explanation
|
||||
- **Multi-Persona Compatibility** - Works well with other experts
|
||||
|
||||
### Feature Requests
|
||||
|
||||
When requesting features:
|
||||
- **Use Case** - Explain the specific workflow need
|
||||
- **Multi-Persona Impact** - How it affects persona sequences
|
||||
- **Performance Considerations** - Scalability requirements
|
||||
|
||||
---
|
||||
|
||||
## 📊 Performance Metrics
|
||||
|
||||
### **Traditional vs Dynamic Architecture**
|
||||
|
||||
| Metric | Traditional | **Dynamic Multi-Persona** |
|
||||
|--------|-------------|---------------------------|
|
||||
| **Memory Usage** | All personas loaded | Only requested personas |
|
||||
| **Loading Time** | Fixed overhead | Scales with usage |
|
||||
| **Flexibility** | Single persona | Unlimited combinations |
|
||||
| **Future-Proofing** | Manual updates | Automatic discovery |
|
||||
| **Performance** | Degrades with size | Constant performance |
|
||||
|
||||
### **Scalability Examples**
|
||||
|
||||
```bash
|
||||
# 2 personas: ~95% memory savings
|
||||
!writer !teacher
|
||||
|
||||
# 5 personas: ~90% memory savings
|
||||
!coder !analyst !economist !historian !artist
|
||||
|
||||
# 10 personas: ~80% memory savings
|
||||
!writer !coder !teacher !physicist !artist !economist !historian !philosopher !consultant !researcher
|
||||
|
||||
# Performance remains optimal regardless of sequence complexity
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -278,8 +554,19 @@ This project is licensed under the MIT License.
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
- **OpenWebUI Team** - For the amazing platform
|
||||
- **OpenWebUI Team** - For the amazing platform and architecture
|
||||
- **Community Contributors** - For persona collections and feedback
|
||||
- **Early Adopters** - For testing multi-persona workflows
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Future Roadmap
|
||||
|
||||
- **Persona Chaining** - Automatic persona suggestions based on context
|
||||
- **Workflow Templates** - Pre-built multi-persona sequences for common tasks
|
||||
- **Performance Analytics** - Detailed metrics on persona usage patterns
|
||||
- **Custom Transition Styles** - User-defined transition formatting
|
||||
- **Persona Marketplace** - Community-contributed expert collections
|
||||
|
||||
---
|
||||
|
||||
@@ -287,6 +574,8 @@ This project is licensed under the MIT License.
|
||||
|
||||
**🎭 Transform your AI interactions with Agent Hotswap!**
|
||||
|
||||
*Seamless persona switching • Rich OpenWebUI integration • Automatic setup*
|
||||
*Revolutionary multi-persona sequences • Universal compatibility • Infinite scalability*
|
||||
|
||||
</div>
|
||||
### **Experience the future of AI interaction today**
|
||||
|
||||
</div>
|
||||
@@ -5,8 +5,8 @@ author_url: https://github.com/pkeffect
|
||||
project_urls: https://github.com/pkeffect/functions/tree/main/functions/filters/agent_hotswap | https://github.com/open-webui/functions/tree/main/functions/filters/agent_hotswap | https://openwebui.com/f/pkeffect/agent_hotswap
|
||||
funding_url: https://github.com/open-webui
|
||||
date: 2025-06-15
|
||||
version: 0.1.2
|
||||
description: Switch between AI personas with optimized performance. Features: external config, pre-compiled regex patterns, smart caching, validation, and modular architecture. Commands: !list, !reset, !coder, !writer, etc.
|
||||
version: 0.2.0
|
||||
description: Universal AI persona switching with dynamic multi-persona support. Features: mid-prompt persona switching, universal persona detection, smart caching, auto-download, and modular architecture. Commands: !list, !reset, !coder, !writer, plus unlimited combinations.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -57,7 +57,7 @@ class PersonaDownloadManager:
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
download_url, headers={"User-Agent": "OpenWebUI-AgentHotswap/0.1.0"}
|
||||
download_url, headers={"User-Agent": "OpenWebUI-AgentHotswap/0.2.0"}
|
||||
)
|
||||
|
||||
with urllib.request.urlopen(req, timeout=DOWNLOAD_TIMEOUT) as response:
|
||||
@@ -195,19 +195,20 @@ class PersonaDownloadManager:
|
||||
return {}
|
||||
|
||||
|
||||
class PatternCompiler:
|
||||
"""Pre-compiles and manages regex patterns for efficient persona detection."""
|
||||
class UniversalPatternCompiler:
|
||||
"""Enhanced pattern compiler with universal persona detection capabilities."""
|
||||
|
||||
def __init__(self, config_valves):
|
||||
self.valves = config_valves
|
||||
self.persona_patterns = {}
|
||||
self.reset_pattern = None
|
||||
self.list_pattern = None
|
||||
self.universal_persona_pattern = None
|
||||
self._last_compiled_config = None
|
||||
self._compile_patterns()
|
||||
|
||||
def _compile_patterns(self):
|
||||
"""Compile all regex patterns once for reuse."""
|
||||
"""Compile all regex patterns once for reuse, including universal detection."""
|
||||
try:
|
||||
current_config = {
|
||||
"prefix": self.valves.keyword_prefix,
|
||||
@@ -222,6 +223,12 @@ class PatternCompiler:
|
||||
prefix_escaped = re.escape(self.valves.keyword_prefix)
|
||||
flags = 0 if self.valves.case_sensitive else re.IGNORECASE
|
||||
|
||||
# Compile universal persona detection pattern
|
||||
# Matches: !{word} where word starts with letter, followed by letters/numbers/underscores
|
||||
self.universal_persona_pattern = re.compile(
|
||||
rf"{prefix_escaped}([a-zA-Z][a-zA-Z0-9_]*)\b", flags
|
||||
)
|
||||
|
||||
# Compile list command pattern
|
||||
list_cmd = self.valves.list_command_keyword
|
||||
if not self.valves.case_sensitive:
|
||||
@@ -252,26 +259,39 @@ class PatternCompiler:
|
||||
except Exception as e:
|
||||
print(f"[PATTERN COMPILER] Error compiling patterns: {e}")
|
||||
|
||||
def get_persona_pattern(self, persona_key: str):
|
||||
"""Get or compile a pattern for a specific persona."""
|
||||
if persona_key not in self.persona_patterns:
|
||||
try:
|
||||
prefix_escaped = re.escape(self.valves.keyword_prefix)
|
||||
keyword_check = (
|
||||
persona_key if self.valves.case_sensitive else persona_key.lower()
|
||||
)
|
||||
flags = 0 if self.valves.case_sensitive else re.IGNORECASE
|
||||
pattern_str = rf"{prefix_escaped}{re.escape(keyword_check)}\b"
|
||||
self.persona_patterns[persona_key] = re.compile(pattern_str, flags)
|
||||
except Exception:
|
||||
return None
|
||||
def discover_all_persona_commands(self, message_content: str) -> List[str]:
|
||||
"""
|
||||
Dynamically discover ALL persona commands in content.
|
||||
Works with current 50+ personas AND any future additions.
|
||||
"""
|
||||
if not message_content:
|
||||
return []
|
||||
|
||||
return self.persona_patterns[persona_key]
|
||||
self._compile_patterns()
|
||||
|
||||
def detect_keyword(
|
||||
self, message_content: str, available_personas: Dict
|
||||
) -> Optional[str]:
|
||||
"""Efficiently detect persona keywords using pre-compiled patterns."""
|
||||
if not self.universal_persona_pattern:
|
||||
return []
|
||||
|
||||
content_to_check = (
|
||||
message_content if self.valves.case_sensitive else message_content.lower()
|
||||
)
|
||||
|
||||
# Find all persona commands
|
||||
matches = self.universal_persona_pattern.findall(content_to_check)
|
||||
|
||||
# Remove duplicates while preserving order
|
||||
seen = set()
|
||||
unique_personas = []
|
||||
for persona in matches:
|
||||
persona_key = persona if self.valves.case_sensitive else persona.lower()
|
||||
if persona_key not in seen:
|
||||
seen.add(persona_key)
|
||||
unique_personas.append(persona_key)
|
||||
|
||||
return unique_personas
|
||||
|
||||
def detect_special_commands(self, message_content: str) -> Optional[str]:
|
||||
"""Detect special commands (list, reset) that take precedence."""
|
||||
if not message_content:
|
||||
return None
|
||||
|
||||
@@ -289,14 +309,91 @@ class PatternCompiler:
|
||||
if self.reset_pattern and self.reset_pattern.search(content_to_check):
|
||||
return "reset"
|
||||
|
||||
# Check persona commands
|
||||
for persona_key in available_personas.keys():
|
||||
pattern = self.get_persona_pattern(persona_key)
|
||||
if pattern and pattern.search(content_to_check):
|
||||
return persona_key
|
||||
|
||||
return None
|
||||
|
||||
def parse_multi_persona_sequence(self, content: str) -> Dict:
|
||||
"""
|
||||
Parse content with multiple persona switches into structured sequence.
|
||||
|
||||
Input: "!writer do X !teacher do Y !physicist do Z"
|
||||
|
||||
Output: {
|
||||
'is_multi_persona': True,
|
||||
'sequence': [
|
||||
{'persona': 'writer', 'task': 'do X'},
|
||||
{'persona': 'teacher', 'task': 'do Y'},
|
||||
{'persona': 'physicist', 'task': 'do Z'}
|
||||
],
|
||||
'requested_personas': ['writer', 'teacher', 'physicist']
|
||||
}
|
||||
"""
|
||||
if not content:
|
||||
return {"is_multi_persona": False}
|
||||
|
||||
self._compile_patterns()
|
||||
|
||||
if not self.universal_persona_pattern:
|
||||
return {"is_multi_persona": False}
|
||||
|
||||
# Find all persona commands and their positions
|
||||
persona_matches = []
|
||||
for match in self.universal_persona_pattern.finditer(content):
|
||||
persona_key = match.group(1)
|
||||
if not self.valves.case_sensitive:
|
||||
persona_key = persona_key.lower()
|
||||
|
||||
persona_matches.append(
|
||||
{"persona": persona_key, "start": match.start(), "end": match.end()}
|
||||
)
|
||||
|
||||
if len(persona_matches) < 1:
|
||||
return {"is_multi_persona": False}
|
||||
|
||||
# Extract content segments between persona commands
|
||||
sequence = []
|
||||
for i, match in enumerate(persona_matches):
|
||||
# Get content from end of current command to start of next command
|
||||
# (or end of string for last command)
|
||||
task_start = match["end"]
|
||||
task_end = (
|
||||
persona_matches[i + 1]["start"]
|
||||
if i + 1 < len(persona_matches)
|
||||
else len(content)
|
||||
)
|
||||
task_content = content[task_start:task_end].strip()
|
||||
|
||||
# Clean up task content by removing any leading persona commands from next segment
|
||||
if i + 1 < len(persona_matches):
|
||||
# Remove the next persona command if it bleeds into this task
|
||||
next_persona_cmd = (
|
||||
f"{self.valves.keyword_prefix}{persona_matches[i + 1]['persona']}"
|
||||
)
|
||||
if task_content.endswith(next_persona_cmd):
|
||||
task_content = task_content[: -len(next_persona_cmd)].strip()
|
||||
|
||||
sequence.append(
|
||||
{
|
||||
"persona": match["persona"],
|
||||
"task": (
|
||||
task_content
|
||||
if task_content
|
||||
else "Please introduce yourself and explain your capabilities."
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
# Get unique personas requested
|
||||
requested_personas = list(
|
||||
dict.fromkeys(match["persona"] for match in persona_matches)
|
||||
)
|
||||
|
||||
return {
|
||||
"is_multi_persona": len(sequence) > 0,
|
||||
"is_single_persona": len(sequence) == 1,
|
||||
"sequence": sequence,
|
||||
"requested_personas": requested_personas,
|
||||
}
|
||||
|
||||
|
||||
class SmartPersonaCache:
|
||||
"""Intelligent caching system for persona configurations."""
|
||||
@@ -369,11 +466,15 @@ class Filter:
|
||||
default=False,
|
||||
description="Enable performance debugging - logs timing information",
|
||||
)
|
||||
multi_persona_transitions: bool = Field(
|
||||
default=True,
|
||||
description="Show transition announcements in multi-persona responses (🎭 **Persona Name**)",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
self.toggle = True
|
||||
self.icon = """data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iY3VycmVudENvbG9yIj4KICA8cGF0aCBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0xNS43NSA1QzE1Ljc1IDMuMzQzIDE0LjQwNyAyIDEyLjc1IDJTOS43NSAzLjM0MyA5Ljc1IDV2MC41QTMuNzUgMy43NSAwIDAgMCAxMy41IDkuMjVjMi4xIDAgMy44MS0xLjc2NyAzLjc1LTMuODZWNVoiLz4KICA8cGF0aCBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik04LjI1IDV2LjVhMy43NSAzLjc1IDAgMCAwIDMuNzUgMy43NWMuNzE0IDAgMS4zODUtLjIgMS45Ni0uNTU2QTMuNzUgMy43NSAwIDAgMCAxNy4yNSA1djAuNUMxNy4yNSAzLjM0MyAxNS45MDcgMiAxNC4yNSAzczMuNzUgMS4zNDMgMy43NSAzdjAuNUEzLjc1IDMuNzUgMCAwIDAgMjEuNzUgOWMuNzE0IDAgMS4zODUtLjIgMS45Ni0uNTU2QTMuNzUgMy43NSAwIDAgMCAyMS4yNSA1djAuNSIvPgo8L3N2Zz4="""
|
||||
self.icon = """data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iY3VycmVudENvbG9yIj4KICA8cGF0aCBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0xNS43NSA1QzE1Ljc1IDMuMzQzIDE0LjQwNyAyIDEyLjc1IDJTOS43NSAzLjM0MyA5Ljc1IDV2MC41QTMuNzUgMy43NSAwIDAgMCAxMy41IDkuMjVjMi4xIDAgMy44MS0xLjc2NyAzLjc1LTMuODZWNVoiLz4KICA8cGF0aCBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik04LjI1IDV2LjVhMy43NSAzLjc1IDAgMCAwIDMuNzUgMy43NWMuNzE0IDAgMS4zODUtLjIgMS45Ni0uNTU2QTMuNzUgMy43NSAwIDAgMCAxNy4yNSA1djAuNUMxNy4yNSAzLjM0MyAxNS45MDcgMiAxNC4yNSAyczMuNzUgMS4zNDMgMy43NSAzdjAuNUEzLjc1IDMuNzUgMCAwIDAgMjEuNzUgOWMuNzE0IDAgMS4zODUtLjIgMS45Ni0uNTU2QTMuNzUgMy43NSAwIDAgMCAyMS4yNSA1djAuNSIvPgo8L3N2Zz4="""
|
||||
|
||||
# State management
|
||||
self.current_persona = None
|
||||
@@ -382,7 +483,7 @@ class Filter:
|
||||
self.event_emitter_for_close_task = None
|
||||
|
||||
# Performance optimization components
|
||||
self.pattern_compiler = PatternCompiler(self.valves)
|
||||
self.pattern_compiler = UniversalPatternCompiler(self.valves)
|
||||
self.persona_cache = SmartPersonaCache()
|
||||
|
||||
# Download system
|
||||
@@ -686,21 +787,37 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
print(f"[PERSONA CONFIG] Error loading personas: {e}")
|
||||
return self.get_master_controller_persona()
|
||||
|
||||
def _detect_persona_keyword(self, message_content: str) -> Optional[str]:
|
||||
"""Efficiently detect persona keywords using pre-compiled patterns."""
|
||||
start_time = time.time() if self.valves.debug_performance else 0
|
||||
def _load_requested_personas_only(self, requested_personas: List[str]) -> Dict:
|
||||
"""
|
||||
Load ONLY the personas actually requested in the prompt.
|
||||
Includes validation and graceful error handling.
|
||||
"""
|
||||
# Load the full personas database once
|
||||
all_available_personas = self._load_personas()
|
||||
|
||||
if not message_content:
|
||||
return None
|
||||
# Always include Master Controller
|
||||
result = {
|
||||
"_master_controller": all_available_personas.get("_master_controller", {})
|
||||
}
|
||||
|
||||
personas = self._load_personas()
|
||||
result = self.pattern_compiler.detect_keyword(message_content, personas)
|
||||
# Track what we found vs what was requested
|
||||
found_personas = []
|
||||
missing_personas = []
|
||||
|
||||
if self.valves.debug_performance:
|
||||
elapsed = (time.time() - start_time) * 1000
|
||||
self._debug_log(
|
||||
f"_detect_persona_keyword completed in {elapsed:.2f}ms (result: {result})"
|
||||
)
|
||||
for persona_key in requested_personas:
|
||||
if persona_key in all_available_personas:
|
||||
result[persona_key] = all_available_personas[persona_key]
|
||||
found_personas.append(persona_key)
|
||||
else:
|
||||
missing_personas.append(persona_key)
|
||||
|
||||
# Add metadata about the loading process
|
||||
result["_loading_info"] = {
|
||||
"requested": requested_personas,
|
||||
"found": found_personas,
|
||||
"missing": missing_personas,
|
||||
"total_loaded": len(found_personas),
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -728,7 +845,75 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
|
||||
return {"role": "system", "content": system_content}
|
||||
|
||||
def _create_dynamic_multi_persona_system(
|
||||
self, requested_personas: List[str]
|
||||
) -> Dict:
|
||||
"""
|
||||
Build dynamic system message with Master Controller + requested personas.
|
||||
Works with ANY persona combination, current or future.
|
||||
"""
|
||||
# Load only the requested personas
|
||||
loaded_personas = self._load_requested_personas_only(requested_personas)
|
||||
loading_info = loaded_personas.pop("_loading_info")
|
||||
|
||||
# Build system message with Master Controller + requested personas
|
||||
master_controller = loaded_personas.get("_master_controller", {})
|
||||
system_content = master_controller.get("prompt", "")
|
||||
|
||||
# Add each successfully loaded persona
|
||||
persona_definitions = []
|
||||
for persona_key in loading_info["found"]:
|
||||
persona_data = loaded_personas[persona_key]
|
||||
persona_name = persona_data.get("name", persona_key.title())
|
||||
persona_prompt = persona_data.get("prompt", "")
|
||||
|
||||
persona_definitions.append(
|
||||
f"""
|
||||
=== {persona_name.upper()} PERSONA ===
|
||||
Activation Command: !{persona_key}
|
||||
{persona_prompt}
|
||||
=== END {persona_name.upper()} ===
|
||||
"""
|
||||
)
|
||||
|
||||
# Create execution instructions
|
||||
transition_instruction = ""
|
||||
if self.valves.multi_persona_transitions and self.valves.show_persona_info:
|
||||
transition_instruction = '3. Announce switches: "🎭 **[Persona Name]**"'
|
||||
else:
|
||||
transition_instruction = (
|
||||
"3. Switch personas seamlessly without announcements"
|
||||
)
|
||||
|
||||
multi_persona_instructions = f"""
|
||||
|
||||
=== DYNAMIC MULTI-PERSONA MODE ===
|
||||
Active Personas: {len(loading_info['found'])} loaded on-demand
|
||||
|
||||
{(''.join(persona_definitions))}
|
||||
|
||||
EXECUTION FRAMEWORK:
|
||||
1. Parse user's persona sequence from their original message
|
||||
2. When you encounter !{{persona}}, switch to that persona immediately
|
||||
{transition_instruction}
|
||||
4. Execute the task following each !command until the next !command
|
||||
5. Maintain context flow between all switches
|
||||
6. Available commands in this session: {', '.join([f'!{p}' for p in loading_info['found']])}
|
||||
|
||||
{f"⚠️ Unrecognized commands (will be ignored): {', '.join([f'!{p}' for p in loading_info['missing']])}" if loading_info['missing'] else ""}
|
||||
|
||||
Execute the user's multi-persona sequence seamlessly.
|
||||
=== END DYNAMIC MULTI-PERSONA MODE ===
|
||||
"""
|
||||
|
||||
return {
|
||||
"role": "system",
|
||||
"content": system_content + multi_persona_instructions,
|
||||
"loading_info": loading_info, # For status messages
|
||||
}
|
||||
|
||||
def _remove_keyword_from_message(self, content: str, keyword_found: str) -> str:
|
||||
"""Remove persona command keywords from message content."""
|
||||
prefix = re.escape(self.valves.keyword_prefix)
|
||||
flags = 0 if self.valves.case_sensitive else re.IGNORECASE
|
||||
|
||||
@@ -751,12 +936,45 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
|
||||
return content.strip()
|
||||
|
||||
def _build_multi_persona_instructions(
|
||||
self, sequence: List[Dict], personas_data: Dict
|
||||
) -> str:
|
||||
"""
|
||||
Convert parsed sequence into clear LLM instructions.
|
||||
"""
|
||||
if not sequence:
|
||||
return "No valid persona sequence found."
|
||||
|
||||
instructions = ["Execute this multi-persona sequence:\n"]
|
||||
|
||||
for i, step in enumerate(sequence, 1):
|
||||
persona_key = step["persona"]
|
||||
task = step["task"]
|
||||
persona_name = personas_data.get(persona_key, {}).get(
|
||||
"name", persona_key.title()
|
||||
)
|
||||
|
||||
instructions.append(
|
||||
f"""
|
||||
**Step {i} - {persona_name}:**
|
||||
{task}
|
||||
"""
|
||||
)
|
||||
|
||||
instructions.append(
|
||||
"""
|
||||
\nExecute each step in sequence, following the persona switching framework provided."""
|
||||
)
|
||||
|
||||
return "\n".join(instructions)
|
||||
|
||||
async def _emit_and_schedule_close(
|
||||
self,
|
||||
emitter: Callable[[dict], Any],
|
||||
description: str,
|
||||
status_type: str = "in_progress",
|
||||
):
|
||||
"""Emit status message and schedule auto-close."""
|
||||
if not emitter or not self.valves.show_persona_info:
|
||||
return
|
||||
|
||||
@@ -780,6 +998,7 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
asyncio.create_task(self._try_close_message_after_delay(message_id))
|
||||
|
||||
async def _try_close_message_after_delay(self, message_id_to_close: str):
|
||||
"""Auto-close status message after configured delay."""
|
||||
await asyncio.sleep(self.valves.status_message_auto_close_delay_ms / 1000.0)
|
||||
if (
|
||||
self.event_emitter_for_close_task
|
||||
@@ -820,6 +1039,7 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
and (
|
||||
"🎭 **Active Persona**" in msg.get("content", "")
|
||||
or "=== OPENWEBUI MASTER CONTROLLER ===" in msg.get("content", "")
|
||||
or "=== DYNAMIC MULTI-PERSONA MODE ===" in msg.get("content", "")
|
||||
)
|
||||
)
|
||||
]
|
||||
@@ -861,7 +1081,7 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
]
|
||||
reset_cmds_str = ", ".join(reset_cmds_formatted)
|
||||
|
||||
# Return instructions for the LLM to present the table (like the original)
|
||||
# Return instructions for the LLM to present the table
|
||||
return (
|
||||
f"Please present the following information. First, a Markdown table of available persona commands, "
|
||||
f"titled '**Available Personas**'. The table should have columns for 'Command' and 'Name', "
|
||||
@@ -872,7 +1092,9 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
f"{table_data_str}\n\n"
|
||||
f"After the table, please add the following explanation on a new line:\n"
|
||||
f"To revert to the default assistant, use one of these commands: {reset_cmds_str}\n\n"
|
||||
f"Ensure the output is only the Markdown table with its title, followed by the reset instructions, all correctly formatted."
|
||||
f"**Multi-Persona Support:** You can now use multiple personas in a single message! "
|
||||
f"Example: `{self.valves.keyword_prefix}writer create a story {self.valves.keyword_prefix}teacher explain the literary techniques {self.valves.keyword_prefix}artist describe visuals`\n\n"
|
||||
f"Ensure the output is properly formatted Markdown."
|
||||
)
|
||||
|
||||
async def _handle_toggle_off_state(
|
||||
@@ -938,8 +1160,9 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
|
||||
for msg_dict in messages:
|
||||
msg = dict(msg_dict)
|
||||
if msg.get("role") == "system" and "🎭 **Active Persona**" in msg.get(
|
||||
"content", ""
|
||||
if msg.get("role") == "system" and (
|
||||
"🎭 **Active Persona**" in msg.get("content", "")
|
||||
or "=== DYNAMIC MULTI-PERSONA MODE ===" in msg.get("content", "")
|
||||
):
|
||||
continue
|
||||
if (
|
||||
@@ -968,29 +1191,30 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
)
|
||||
return body
|
||||
|
||||
async def _handle_persona_switch_command(
|
||||
async def _handle_single_persona_command(
|
||||
self,
|
||||
detected_keyword_key: str,
|
||||
persona_key: str,
|
||||
body: Dict,
|
||||
messages: List[Dict],
|
||||
last_message_idx: int,
|
||||
original_content: str,
|
||||
__event_emitter__: Callable[[dict], Any],
|
||||
) -> Dict:
|
||||
"""Handle persona switching commands like !coder, !writer, etc."""
|
||||
"""Handle single persona switching commands like !coder, !writer, etc."""
|
||||
personas_data = self._load_personas()
|
||||
if detected_keyword_key not in personas_data:
|
||||
if persona_key not in personas_data:
|
||||
return body
|
||||
|
||||
self.current_persona = detected_keyword_key
|
||||
persona_config = personas_data[detected_keyword_key]
|
||||
self.current_persona = persona_key
|
||||
persona_config = personas_data[persona_key]
|
||||
temp_messages = []
|
||||
user_message_modified = False
|
||||
|
||||
for msg_dict in messages:
|
||||
msg = dict(msg_dict)
|
||||
if msg.get("role") == "system" and "🎭 **Active Persona**" in msg.get(
|
||||
"content", ""
|
||||
if msg.get("role") == "system" and (
|
||||
"🎭 **Active Persona**" in msg.get("content", "")
|
||||
or "=== DYNAMIC MULTI-PERSONA MODE ===" in msg.get("content", "")
|
||||
):
|
||||
continue
|
||||
if (
|
||||
@@ -999,7 +1223,7 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
and msg.get("content", "") == original_content
|
||||
):
|
||||
cleaned_content = self._remove_keyword_from_message(
|
||||
original_content, detected_keyword_key
|
||||
original_content, persona_key
|
||||
)
|
||||
intro_request_default = (
|
||||
"Please introduce yourself and explain what you can help me with."
|
||||
@@ -1024,7 +1248,7 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
msg["content"] = intro_request_default
|
||||
else:
|
||||
persona_name_for_prompt = persona_config.get(
|
||||
"name", detected_keyword_key.title()
|
||||
"name", persona_key.title()
|
||||
)
|
||||
msg["content"] = (
|
||||
f"Please briefly introduce yourself as {persona_name_for_prompt}. After your introduction, please help with the following: {cleaned_content}"
|
||||
@@ -1032,11 +1256,11 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
user_message_modified = True
|
||||
temp_messages.append(msg)
|
||||
|
||||
persona_system_msg = self._create_persona_system_message(detected_keyword_key)
|
||||
persona_system_msg = self._create_persona_system_message(persona_key)
|
||||
temp_messages.insert(0, persona_system_msg)
|
||||
body["messages"] = temp_messages
|
||||
|
||||
persona_display_name = persona_config.get("name", detected_keyword_key.title())
|
||||
persona_display_name = persona_config.get("name", persona_key.title())
|
||||
await self._emit_and_schedule_close(
|
||||
__event_emitter__,
|
||||
f"🎭 Switched to {persona_display_name}",
|
||||
@@ -1044,6 +1268,67 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
)
|
||||
return body
|
||||
|
||||
async def _handle_multi_persona_command(
|
||||
self,
|
||||
sequence_data: Dict,
|
||||
body: Dict,
|
||||
messages: List[Dict],
|
||||
last_message_idx: int,
|
||||
original_content: str,
|
||||
__event_emitter__: Callable[[dict], Any],
|
||||
) -> Dict:
|
||||
"""
|
||||
Handle complex multi-persona sequences.
|
||||
"""
|
||||
requested_personas = sequence_data["requested_personas"]
|
||||
sequence = sequence_data["sequence"]
|
||||
|
||||
# Build dynamic system message
|
||||
dynamic_system_result = self._create_dynamic_multi_persona_system(
|
||||
requested_personas
|
||||
)
|
||||
loading_info = dynamic_system_result.pop("loading_info")
|
||||
|
||||
# Remove old persona messages
|
||||
temp_messages = self._remove_persona_system_messages(messages)
|
||||
temp_messages.insert(0, dynamic_system_result)
|
||||
|
||||
# Build instruction content from the sequence
|
||||
all_personas = self._load_personas()
|
||||
instruction_content = self._build_multi_persona_instructions(
|
||||
sequence, all_personas
|
||||
)
|
||||
|
||||
# Update user message with structured instructions
|
||||
temp_messages[last_message_idx + 1][
|
||||
"content"
|
||||
] = instruction_content # +1 because we inserted system message
|
||||
|
||||
body["messages"] = temp_messages
|
||||
|
||||
# Update current state for multi-persona
|
||||
if len(loading_info["found"]) == 1:
|
||||
self.current_persona = loading_info["found"][0]
|
||||
else:
|
||||
self.current_persona = f"multi:{':'.join(loading_info['found'])}"
|
||||
|
||||
# Status message
|
||||
if loading_info["found"]:
|
||||
persona_names = []
|
||||
for p in loading_info["found"]:
|
||||
name = all_personas.get(p, {}).get("name", p.title())
|
||||
persona_names.append(name)
|
||||
|
||||
status_msg = f"🎭 Multi-persona sequence: {' → '.join(persona_names)}"
|
||||
if loading_info["missing"]:
|
||||
status_msg += f" | ⚠️ Unknown: {', '.join([f'!{p}' for p in loading_info['missing']])}"
|
||||
|
||||
await self._emit_and_schedule_close(
|
||||
__event_emitter__, status_msg, "complete"
|
||||
)
|
||||
|
||||
return body
|
||||
|
||||
def _apply_persistent_persona(self, body: Dict, messages: List[Dict]) -> Dict:
|
||||
"""Apply current persona to messages when no command detected (ALWAYS includes master controller)."""
|
||||
if not self.valves.persistent_persona:
|
||||
@@ -1052,6 +1337,11 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
personas = self._load_personas()
|
||||
target_persona = self.current_persona if self.current_persona else None
|
||||
|
||||
# Handle multi-persona persistent state
|
||||
if target_persona and target_persona.startswith("multi:"):
|
||||
# For multi-persona, we don't persist - user needs to issue new commands
|
||||
return body
|
||||
|
||||
if not target_persona or target_persona not in personas:
|
||||
return body
|
||||
|
||||
@@ -1097,7 +1387,7 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
__event_emitter__: Callable[[dict], Any],
|
||||
__user__: Optional[dict] = None,
|
||||
) -> dict:
|
||||
"""Main entry point - orchestrates the persona switching flow."""
|
||||
"""Main entry point - orchestrates the universal persona switching flow."""
|
||||
messages = body.get("messages", [])
|
||||
if messages is None:
|
||||
messages = []
|
||||
@@ -1123,18 +1413,17 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
if last_message_idx == -1:
|
||||
return self._apply_persistent_persona(body, messages)
|
||||
|
||||
# Detect persona command
|
||||
detected_keyword_key = self._detect_persona_keyword(
|
||||
# Check for special commands first (they take precedence)
|
||||
special_command = self.pattern_compiler.detect_special_commands(
|
||||
original_content_of_last_user_msg
|
||||
)
|
||||
|
||||
# Route to appropriate command handler
|
||||
if detected_keyword_key:
|
||||
if detected_keyword_key == "list_personas":
|
||||
if special_command:
|
||||
if special_command == "list_personas":
|
||||
return await self._handle_list_personas_command(
|
||||
body, messages, last_message_idx, __event_emitter__
|
||||
)
|
||||
elif detected_keyword_key == "reset":
|
||||
elif special_command == "reset":
|
||||
return await self._handle_reset_command(
|
||||
body,
|
||||
messages,
|
||||
@@ -1142,10 +1431,28 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
original_content_of_last_user_msg,
|
||||
__event_emitter__,
|
||||
)
|
||||
|
||||
# Parse for persona sequence (universal detection)
|
||||
sequence_data = self.pattern_compiler.parse_multi_persona_sequence(
|
||||
original_content_of_last_user_msg
|
||||
)
|
||||
|
||||
if sequence_data["is_multi_persona"]:
|
||||
if sequence_data["is_single_persona"]:
|
||||
# Single persona command
|
||||
persona_key = sequence_data["sequence"][0]["persona"]
|
||||
return await self._handle_single_persona_command(
|
||||
persona_key,
|
||||
body,
|
||||
messages,
|
||||
last_message_idx,
|
||||
original_content_of_last_user_msg,
|
||||
__event_emitter__,
|
||||
)
|
||||
else:
|
||||
# Handle persona switching command
|
||||
return await self._handle_persona_switch_command(
|
||||
detected_keyword_key,
|
||||
# Multi-persona sequence
|
||||
return await self._handle_multi_persona_command(
|
||||
sequence_data,
|
||||
body,
|
||||
messages,
|
||||
last_message_idx,
|
||||
@@ -1153,7 +1460,7 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
__event_emitter__,
|
||||
)
|
||||
else:
|
||||
# No command detected, apply persistent persona if active
|
||||
# No persona commands detected, apply persistent persona if active
|
||||
return self._apply_persistent_persona(body, messages)
|
||||
|
||||
async def outlet(
|
||||
@@ -1162,6 +1469,7 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
return body
|
||||
|
||||
def get_persona_list(self) -> str:
|
||||
"""Get formatted list of available personas for API/external use."""
|
||||
personas = self._load_personas()
|
||||
|
||||
# Filter out master controller and metadata from user-facing list
|
||||
@@ -1190,7 +1498,9 @@ Leverage these capabilities appropriately - use LaTeX for math, Mermaid for diag
|
||||
command_info = (
|
||||
f"\n\n**System Commands:**\n"
|
||||
f"• {list_command_display} - Lists persona commands and names in a multi-column Markdown table.\n"
|
||||
f"• {reset_keywords_display} - Reset to default assistant behavior (LLM will confirm)."
|
||||
f"• {reset_keywords_display} - Reset to default assistant behavior (LLM will confirm).\n\n"
|
||||
f"**Multi-Persona Support:** Use multiple personas in one message!\n"
|
||||
f"Example: `{self.valves.keyword_prefix}writer story {self.valves.keyword_prefix}teacher explain {self.valves.keyword_prefix}artist visuals`"
|
||||
)
|
||||
|
||||
if not persona_list_items:
|
||||
|
||||
Reference in New Issue
Block a user