Spaces:
Running
Running
| from dataclasses import dataclass | |
| from typing import List, Optional | |
| import re | |
| import textwrap | |
| from cot_reasoning import VisualizationConfig, AnthropicAPI | |
| class ToTNode: | |
| """Data class representing a node in the Tree of Thoughts""" | |
| id: str | |
| content: str | |
| parent_id: Optional[str] = None | |
| children: List['ToTNode'] = None | |
| is_answer: bool = False | |
| def __post_init__(self): | |
| if self.children is None: | |
| self.children = [] | |
| class ToTResponse: | |
| """Data class representing a complete ToT response""" | |
| question: str | |
| root: ToTNode | |
| answer: Optional[str] = None | |
| def parse_tot_response(response_text: str, question: str) -> ToTResponse: | |
| """Parse ToT response text to extract nodes and build the tree""" | |
| # Parse nodes | |
| node_pattern = r'<node id="([^"]+)"(?:\s+parent="([^"]+)")?\s*>\s*(.*?)\s*</node>' | |
| nodes_dict = {} | |
| # First pass: create all nodes | |
| for match in re.finditer(node_pattern, response_text, re.DOTALL): | |
| node_id = match.group(1) | |
| parent_id = match.group(2) | |
| content = match.group(3).strip() | |
| node = ToTNode(id=node_id, content=content, parent_id=parent_id) | |
| nodes_dict[node_id] = node | |
| # Second pass: build tree relationships | |
| root = None | |
| for node in nodes_dict.values(): | |
| if node.parent_id is None: | |
| root = node | |
| else: | |
| parent = nodes_dict.get(node.parent_id) | |
| if parent: | |
| parent.children.append(node) | |
| # Parse answer if present | |
| answer_pattern = r'<answer>\s*(.*?)\s*</answer>' | |
| answer_match = re.search(answer_pattern, response_text, re.DOTALL) | |
| answer = answer_match.group(1).strip() if answer_match else None | |
| if answer: | |
| # Mark the node leading to the answer | |
| for node in nodes_dict.values(): | |
| if node.content.strip() in answer.strip(): | |
| node.is_answer = True | |
| return ToTResponse(question=question, root=root, answer=answer) | |
| def create_mermaid_diagram(tot_response: ToTResponse, config: VisualizationConfig) -> str: | |
| """Convert ToT response to Mermaid diagram""" | |
| diagram = ['<div class="mermaid">', 'graph TD'] | |
| # Add question node | |
| question_content = wrap_text(tot_response.question, config) | |
| diagram.append(f' Q["{question_content}"]') | |
| # Track leaf nodes for connecting to answer | |
| leaf_nodes = [] | |
| def add_node_and_children(node: ToTNode, parent_id: Optional[str] = None): | |
| content = wrap_text(node.content, config) | |
| node_style = 'answer' if node.is_answer else 'default' | |
| # Add node | |
| diagram.append(f' {node.id}["{content}"]') | |
| # Add connection from parent | |
| if parent_id: | |
| diagram.append(f' {parent_id} --> {node.id}') | |
| # Process children | |
| if node.children: | |
| for child in node.children: | |
| add_node_and_children(child, node.id) | |
| else: | |
| # This is a leaf node | |
| leaf_nodes.append(node.id) | |
| # Build tree structure | |
| if tot_response.root: | |
| diagram.append(f' Q --> {tot_response.root.id}') | |
| add_node_and_children(tot_response.root) | |
| # Add final answer node if answer exists | |
| if tot_response.answer: | |
| answer_content = wrap_text(tot_response.answer, config) | |
| diagram.append(f' Answer["{answer_content}"]') | |
| # Connect all leaf nodes to the answer | |
| for leaf_id in leaf_nodes: | |
| diagram.append(f' {leaf_id} --> Answer') | |
| diagram.append(' class Answer final_answer;') | |
| # Add styles | |
| diagram.extend([ | |
| ' classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;', | |
| ' classDef question fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;', | |
| ' classDef answer fill:#d4edda,stroke:#28a745,stroke-width:2px;', | |
| ' classDef final_answer fill:#d4edda,stroke:#28a745,stroke-width:2px;', | |
| ' class Q question;', | |
| ' linkStyle default stroke:#666,stroke-width:2px;' | |
| ]) | |
| diagram.append('</div>') | |
| return '\n'.join(diagram) | |
| def wrap_text(text: str, config: VisualizationConfig) -> str: | |
| """Wrap text to fit within box constraints""" | |
| text = text.replace('\n', ' ').replace('"', "'") | |
| wrapped_lines = textwrap.wrap(text, width=config.max_chars_per_line) | |
| if len(wrapped_lines) > config.max_lines: | |
| # Option 1: Simply truncate and add ellipsis to the last line | |
| wrapped_lines = wrapped_lines[:config.max_lines] | |
| wrapped_lines[-1] = wrapped_lines[-1][:config.max_chars_per_line-3] + "..." | |
| # Option 2 (alternative): Include part of the next line to show continuity | |
| # original_next_line = wrapped_lines[config.max_lines] if len(wrapped_lines) > config.max_lines else "" | |
| # wrapped_lines = wrapped_lines[:config.max_lines-1] | |
| # wrapped_lines.append(original_next_line[:config.max_chars_per_line-3] + "...") | |
| return "<br>".join(wrapped_lines) |