mirror of
https://github.com/langgenius/dify.git
synced 2026-03-04 15:26:21 +08:00
151 lines
4.9 KiB
Python
151 lines
4.9 KiB
Python
"""Template structures for Response nodes (Answer and End).
|
|
|
|
This module provides a unified template structure for both Answer and End nodes,
|
|
similar to SegmentGroup but focused on template representation without values.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from collections.abc import Sequence
|
|
from dataclasses import dataclass
|
|
from typing import Any, Union
|
|
|
|
from dify_graph.nodes.base.variable_template_parser import VariableTemplateParser
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TemplateSegment(ABC):
|
|
"""Base class for template segments."""
|
|
|
|
@abstractmethod
|
|
def __str__(self) -> str:
|
|
"""String representation of the segment."""
|
|
pass
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TextSegment(TemplateSegment):
|
|
"""A text segment in a template."""
|
|
|
|
text: str
|
|
|
|
def __str__(self) -> str:
|
|
return self.text
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class VariableSegment(TemplateSegment):
|
|
"""A variable reference segment in a template."""
|
|
|
|
selector: Sequence[str]
|
|
variable_name: str | None = None # Optional variable name for End nodes
|
|
|
|
def __str__(self) -> str:
|
|
return "{{#" + ".".join(self.selector) + "#}}"
|
|
|
|
|
|
# Type alias for segments
|
|
TemplateSegmentUnion = Union[TextSegment, VariableSegment]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Template:
|
|
"""Unified template structure for Response nodes.
|
|
|
|
Similar to SegmentGroup, but represents the template structure
|
|
without variable values - only marking variable selectors.
|
|
"""
|
|
|
|
segments: list[TemplateSegmentUnion]
|
|
|
|
@classmethod
|
|
def from_answer_template(cls, template_str: str) -> Template:
|
|
"""Create a Template from an Answer node template string.
|
|
|
|
Example:
|
|
"Hello, {{#node1.name#}}" -> [TextSegment("Hello, "), VariableSegment(["node1", "name"])]
|
|
|
|
Args:
|
|
template_str: The answer template string
|
|
|
|
Returns:
|
|
Template instance
|
|
"""
|
|
parser = VariableTemplateParser(template_str)
|
|
segments: list[TemplateSegmentUnion] = []
|
|
|
|
# Extract variable selectors to find all variables
|
|
variable_selectors = parser.extract_variable_selectors()
|
|
var_map = {var.variable: var.value_selector for var in variable_selectors}
|
|
|
|
# Parse template to get ordered segments
|
|
# We need to split the template by variable placeholders while preserving order
|
|
import re
|
|
|
|
# Create a regex pattern that matches variable placeholders
|
|
pattern = r"\{\{(#[a-zA-Z0-9_]{1,50}(?:\.[a-zA-Z_][a-zA-Z0-9_]{0,29}){1,10}#)\}\}"
|
|
|
|
# Split template while keeping the delimiters (variable placeholders)
|
|
parts = re.split(pattern, template_str)
|
|
|
|
for i, part in enumerate(parts):
|
|
if not part:
|
|
continue
|
|
|
|
# Check if this part is a variable reference (odd indices after split)
|
|
if i % 2 == 1: # Odd indices are variable keys
|
|
# Remove the # symbols from the variable key
|
|
var_key = part
|
|
if var_key in var_map:
|
|
segments.append(VariableSegment(selector=list(var_map[var_key])))
|
|
else:
|
|
# This shouldn't happen with valid templates
|
|
segments.append(TextSegment(text="{{" + part + "}}"))
|
|
else:
|
|
# Even indices are text segments
|
|
segments.append(TextSegment(text=part))
|
|
|
|
return cls(segments=segments)
|
|
|
|
@classmethod
|
|
def from_end_outputs(cls, outputs_config: list[dict[str, Any]]) -> Template:
|
|
"""Create a Template from an End node outputs configuration.
|
|
|
|
End nodes are treated as templates of concatenated variables with newlines.
|
|
|
|
Example:
|
|
[{"variable": "text", "value_selector": ["node1", "text"]},
|
|
{"variable": "result", "value_selector": ["node2", "result"]}]
|
|
->
|
|
[VariableSegment(["node1", "text"]),
|
|
TextSegment("\n"),
|
|
VariableSegment(["node2", "result"])]
|
|
|
|
Args:
|
|
outputs_config: List of output configurations with variable and value_selector
|
|
|
|
Returns:
|
|
Template instance
|
|
"""
|
|
segments: list[TemplateSegmentUnion] = []
|
|
|
|
for i, output in enumerate(outputs_config):
|
|
if i > 0:
|
|
# Add newline separator between variables
|
|
segments.append(TextSegment(text="\n"))
|
|
|
|
value_selector = output.get("value_selector", [])
|
|
variable_name = output.get("variable", "")
|
|
if value_selector:
|
|
segments.append(VariableSegment(selector=list(value_selector), variable_name=variable_name))
|
|
|
|
if len(segments) > 0 and isinstance(segments[-1], TextSegment):
|
|
segments = segments[:-1]
|
|
|
|
return cls(segments=segments)
|
|
|
|
def __str__(self) -> str:
|
|
"""String representation of the template."""
|
|
return "".join(str(segment) for segment in self.segments)
|