Spaces:
Sleeping
Sleeping
File size: 10,444 Bytes
d38e6a5 8056e83 d38e6a5 db39ccf d38e6a5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
"""
Conversation Flow Management - Design and store conversation paths
"""
import json
import uuid
from typing import Dict, List, Optional
from datetime import datetime
import sys
import os
# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(__file__))
class ConversationNode:
"""Represents a single node in a conversation flow"""
def __init__(self, node_id: str = None, node_type: str = "question",
content: str = "", next_node: str = None, branches: List[Dict] = None):
self.id = node_id or str(uuid.uuid4())
self.type = node_type # "question", "branch", "end"
self.content = content
self.next = next_node
self.branches = branches or [] # For conditional branching
def to_dict(self) -> Dict:
"""Convert node to dictionary"""
return {
"id": self.id,
"type": self.type,
"content": self.content,
"next": self.next,
"branches": self.branches
}
@classmethod
def from_dict(cls, data: Dict) -> 'ConversationNode':
"""Create node from dictionary"""
return cls(
node_id=data.get("id"),
node_type=data.get("type", "question"),
content=data.get("content", ""),
next_node=data.get("next"),
branches=data.get("branches", [])
)
class ConversationFlow:
"""Manages a complete conversation flow"""
def __init__(self, flow_id: str = None, name: str = "Untitled Flow",
description: str = "", nodes: List[ConversationNode] = None):
self.id = flow_id or str(uuid.uuid4())
self.name = name
self.description = description
self.nodes = nodes or []
self.created_at = datetime.now().isoformat()
self.updated_at = datetime.now().isoformat()
def add_node(self, node: ConversationNode, position: int = None):
"""Add a node to the flow"""
if position is None:
self.nodes.append(node)
else:
self.nodes.insert(position, node)
self.updated_at = datetime.now().isoformat()
def remove_node(self, node_id: str):
"""Remove a node from the flow"""
self.nodes = [n for n in self.nodes if n.id != node_id]
self.updated_at = datetime.now().isoformat()
def get_node(self, node_id: str) -> Optional[ConversationNode]:
"""Get a node by ID"""
for node in self.nodes:
if node.id == node_id:
return node
return None
def get_start_node(self) -> Optional[ConversationNode]:
"""Get the first node in the flow"""
return self.nodes[0] if self.nodes else None
def reorder_node(self, node_id: str, new_position: int):
"""Move a node to a different position"""
node = self.get_node(node_id)
if node:
self.nodes.remove(node)
self.nodes.insert(new_position, node)
self.updated_at = datetime.now().isoformat()
def to_dict(self) -> Dict:
"""Convert flow to dictionary"""
return {
"id": self.id,
"name": self.name,
"description": self.description,
"nodes": [node.to_dict() for node in self.nodes],
"created_at": self.created_at,
"updated_at": self.updated_at
}
@classmethod
def from_dict(cls, data: Dict) -> 'ConversationFlow':
"""Create flow from dictionary"""
flow = cls(
flow_id=data.get("id"),
name=data.get("name", "Untitled Flow"),
description=data.get("description", "")
)
flow.nodes = [ConversationNode.from_dict(n) for n in data.get("nodes", [])]
flow.created_at = data.get("created_at", datetime.now().isoformat())
flow.updated_at = data.get("updated_at", datetime.now().isoformat())
return flow
def save_to_file(self, filepath: str):
"""Save flow to JSON file"""
with open(filepath, 'w') as f:
json.dump(self.to_dict(), f, indent=2)
@classmethod
def load_from_file(cls, filepath: str) -> 'ConversationFlow':
"""Load flow from JSON file"""
with open(filepath, 'r') as f:
data = json.load(f)
return cls.from_dict(data)
def validate(self) -> tuple[bool, str]:
"""Validate the flow structure"""
if not self.nodes:
return False, "Flow must have at least one node"
if not self.name or not self.name.strip():
return False, "Flow must have a name"
# Check for orphaned nodes (nodes that can't be reached)
reachable = set()
if self.nodes:
current = self.nodes[0]
reachable.add(current.id)
# Simple validation: check if nodes are in sequence
for node in self.nodes:
if not node.content or not node.content.strip():
return False, f"Node {node.id} has no content"
return True, "Flow is valid"
def generate_flow_with_ai(self, llm_backend, num_questions: int = 5):
"""
Generate conversation flow nodes using AI based on flow name and description.
Args:
llm_backend: LLM backend to use for generation
num_questions: Number of conversation steps to generate
"""
if not self.name or not self.description:
raise ValueError("Flow must have a name and description to generate nodes")
# Build prompt for generating conversation flow
prompt = f"""Task: Design a structured conversation flow
**Interview Topic:** {self.name}
**Interview Purpose:** {self.description}
**Your Task:** Create {num_questions} conversation steps for a structured qualitative research interview.
**Guidelines for Each Step:**
- Start with an opening that builds rapport and explains the purpose
- Progress from general to specific questions
- Each step should be clear, open-ended, and encourage detailed responses
- Include natural transition phrases
- End with a closing that thanks the respondent
- Make questions natural and conversational, not robotic
**Output Format:** Number each step (1., 2., 3., etc.) with the exact question or statement to use.
**Generate {num_questions} Interview Steps:**
1."""
messages = [
{
"role": "system",
"content": "You are an expert qualitative research interviewer designing a conversation flow. Create engaging, professional interview questions that will elicit detailed, meaningful responses."
},
{"role": "user", "content": prompt}
]
try:
response = llm_backend.generate(messages, max_tokens=1500, temperature=0.7)
self._parse_and_add_nodes(response)
return True, "Flow generated successfully!"
except Exception as e:
return False, f"Flow generation failed: {str(e)}"
def _parse_and_add_nodes(self, response: str):
"""
Parse LLM response and create conversation nodes.
Args:
response: The LLM-generated response containing numbered questions
"""
import re
# Pattern to match numbered items: "1. Question" or "1) Question"
pattern = r'\d+[\.\)]\s+(.+?)(?=\d+[\.\)]|\Z)'
matches = re.findall(pattern, response, re.DOTALL)
if not matches:
# Fallback: split by lines and look for question-like content
lines = response.split('\n')
matches = [line.strip() for line in lines if line.strip() and len(line.strip()) > 20]
for i, match in enumerate(matches):
# Clean up the match
content = match.split('\n')[0].strip()
if not content or len(content) < 10:
continue
# Determine node type
node_type = "question"
if i == 0:
node_type = "opening"
elif i == len(matches) - 1:
node_type = "end"
# Create and add node
node = ConversationNode(content=content, node_type=node_type)
if self.nodes:
# Link to previous node
self.nodes[-1].next = node.id
self.add_node(node)
def create_example_flow() -> ConversationFlow:
"""Create an example conversation flow"""
flow = ConversationFlow(
name="Customer Feedback Interview",
description="Structured interview to gather customer feedback on product experience"
)
# Add nodes
node1 = ConversationNode(
content="Hello! Thank you for taking the time to speak with me today. I'd like to understand your experience with our product. First, can you tell me what initially attracted you to our product?",
node_type="question"
)
node2 = ConversationNode(
content="That's interesting. How would you describe your overall experience using the product so far?",
node_type="question"
)
node3 = ConversationNode(
content="What specific features do you find most valuable, and why?",
node_type="question"
)
node4 = ConversationNode(
content="Have you encountered any challenges or frustrations while using the product? If so, can you describe them?",
node_type="question"
)
node5 = ConversationNode(
content="Based on your experience, what improvements or new features would you most like to see?",
node_type="question"
)
node6 = ConversationNode(
content="Thank you so much for sharing your thoughts! Your feedback is incredibly valuable and will help us improve the product. Is there anything else you'd like to add?",
node_type="end"
)
# Link nodes
node1.next = node2.id
node2.next = node3.id
node3.next = node4.id
node4.next = node5.id
node5.next = node6.id
flow.add_node(node1)
flow.add_node(node2)
flow.add_node(node3)
flow.add_node(node4)
flow.add_node(node5)
flow.add_node(node6)
return flow
|