import re
from dataclasses import dataclass
from typing import List, Optional, Dict
from collections import Counter
from cot_reasoning import CoTStep, CoTResponse, VisualizationConfig, wrap_text
@dataclass
class SCRPath:
"""Data class representing a single self-consistency reasoning path"""
path_id: int
steps: List[CoTStep]
answer: Optional[str] = None
@dataclass
class SCRResponse:
"""Data class representing a complete self-consistency response"""
question: str
paths: List[SCRPath]
final_answer: Optional[str] = None
vote_counts: Optional[Dict[str, int]] = None
def parse_scr_response(response_text: str, question: str, num_paths: int = 5) -> SCRResponse:
"""
Parse self-consistency response text to extract multiple reasoning paths and answers.
Args:
response_text: The raw response from the API containing multiple paths
question: The original question
num_paths: Expected number of reasoning paths
Returns:
SCRResponse object containing all paths and aggregated answer
"""
# Split the response into individual paths
path_pattern = r'Path\s+(\d+):(.*?)(?=Path\s+\d+:|$)'
path_matches = re.finditer(path_pattern, response_text, re.DOTALL)
paths = []
answers = []
for match in path_matches:
path_id = int(match.group(1))
path_content = match.group(2).strip()
# Extract steps for this path
step_pattern = r'\s*(.*?)\s*'
steps = []
for step_match in re.finditer(step_pattern, path_content, re.DOTALL):
number = int(step_match.group(1))
content = step_match.group(2).strip()
steps.append(CoTStep(number=number, content=content))
# Extract answer for this path
answer_pattern = r'\s*(.*?)\s*'
answer_match = re.search(answer_pattern, path_content, re.DOTALL)
answer = answer_match.group(1).strip() if answer_match else None
if answer:
answers.append(answer)
# Sort steps by number
steps.sort(key=lambda x: x.number)
paths.append(SCRPath(path_id=path_id, steps=steps, answer=answer))
# Determine final answer through voting
vote_counts = Counter(answers)
final_answer = vote_counts.most_common(1)[0][0] if vote_counts else None
return SCRResponse(
question=question,
paths=paths,
final_answer=final_answer,
vote_counts=dict(vote_counts)
)
def create_mermaid_diagram(scr_response: SCRResponse, config: VisualizationConfig) -> str:
"""
Convert self-consistency paths to Mermaid diagram.
Args:
scr_response: SCRResponse object containing multiple reasoning paths
config: VisualizationConfig for text formatting
Returns:
Mermaid diagram markup as a string
"""
diagram = ['
', 'graph TD']
# Add question node
question_content = wrap_text(scr_response.question, config)
diagram.append(f' Q["{question_content}"]')
# Process each path
for path in scr_response.paths:
path_id = f'P{path.path_id}'
# Add path label
diagram.append(f' {path_id}["Path {path.path_id}"]')
diagram.append(f' Q --> {path_id}')
# Add steps for this path
prev_node = path_id
for step in path.steps:
content = wrap_text(step.content, config)
node_id = f'P{path.path_id}S{step.number}'
diagram.append(f' {node_id}["{content}"]')
diagram.append(f' {prev_node} --> {node_id}')
prev_node = node_id
# Add path answer
if path.answer:
answer_content = wrap_text(path.answer, config)
answer_id = f'A{path.path_id}'
diagram.append(f' {answer_id}["{answer_content}"]')
diagram.append(f' {prev_node} --> {answer_id}')
# Add final answer with vote counts
if scr_response.final_answer and scr_response.vote_counts:
vote_info = [f"{ans}: {count} votes" for ans, count in scr_response.vote_counts.items()]
final_content = wrap_text(
f"Final Answer (by voting):\\n{scr_response.final_answer}\\n\\n" +
"Vote Distribution:\\n" + "\\n".join(vote_info),
config
)
diagram.append(f' F["{final_content}"]')
# Connect all path answers to final answer
for path in scr_response.paths:
if path.answer:
diagram.append(f' A{path.path_id} --> F')
# Add styles
diagram.extend([
' classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;',
' classDef question fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;',
' classDef path fill:#fff3e0,stroke:#f57c00,stroke-width:2px;',
' classDef answer fill:#d4edda,stroke:#28a745,stroke-width:2px;',
' classDef final fill:#d4edda,stroke:#28a745,stroke-width:2px;',
' class Q question;',
' class F final;'
])
# Apply path style to all path nodes
for path in scr_response.paths:
diagram.append(f' class P{path.path_id} path;')
# Apply answer style to all answer nodes
for path in scr_response.paths:
if path.answer:
diagram.append(f' class A{path.path_id} answer;')
diagram.append('
')
return '\n'.join(diagram)