-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathtemplate_utils.py
More file actions
167 lines (130 loc) · 4.98 KB
/
template_utils.py
File metadata and controls
167 lines (130 loc) · 4.98 KB
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
# SPDX-FileCopyrightText: GitHub, Inc.
# SPDX-License-Identifier: MIT
"""Jinja2 template utilities for taskflow template rendering."""
import os
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
import jinja2
__all__ = ["PromptLoader", "create_jinja_environment", "env_function", "render_template"]
if TYPE_CHECKING:
from .available_tools import AvailableTools
from .available_tools import BadToolNameError
class PromptLoader(jinja2.BaseLoader):
"""Custom Jinja2 loader for reusable prompts."""
def __init__(self, available_tools: "AvailableTools") -> None:
"""Initialize the prompt loader.
Args:
available_tools: AvailableTools instance for prompt loading
"""
self.available_tools = available_tools
def get_source(
self, environment: jinja2.Environment, template: str
) -> tuple[str, str | None, Callable[[], bool]]:
"""Load prompt from available_tools by path.
Args:
environment: Jinja2 environment
template: Template path (e.g., 'examples.prompts.example_prompt')
Returns:
Tuple of (source, filename, uptodate_func)
Raises:
jinja2.TemplateNotFound: If prompt not found
"""
del environment # unused arg
try:
prompt_data = self.available_tools.get_prompt(template)
if not prompt_data:
raise jinja2.TemplateNotFound(template)
source = prompt_data.prompt or ""
# Return: (source, filename, uptodate_func)
return source, None, lambda: True
except jinja2.TemplateNotFound:
raise
except (BadToolNameError, KeyError, AttributeError, FileNotFoundError):
raise jinja2.TemplateNotFound(template)
def env_function(var_name: str, default: Optional[str] = None, *, required: bool = True) -> str:
"""Jinja2 function to access environment variables.
Args:
var_name: Name of environment variable
default: Default value if not found
required: If True, raises error when not found and no default
Returns:
Environment variable value or default
Raises:
LookupError: If required var not found
Examples:
{{ env('LOG_DIR') }}
{{ env('OPTIONAL_VAR', 'default_value') }}
{{ env('OPTIONAL_VAR', required=False) }}
"""
value = os.getenv(var_name, default)
if value is None and required:
raise LookupError(f"Required environment variable {var_name} not found!")
return value or ""
def create_jinja_environment(available_tools: "AvailableTools") -> jinja2.Environment:
"""Create configured Jinja2 environment for taskflow templates.
Args:
available_tools: AvailableTools instance for prompt loading
Returns:
Configured Jinja2 Environment
"""
env = jinja2.Environment(
loader=PromptLoader(available_tools),
# Use same delimiters as custom system
variable_start_string='{{',
variable_end_string='}}',
block_start_string='{%',
block_end_string='%}',
# Disable auto-escaping (YAML context doesn't need HTML escaping)
autoescape=False,
# Keep whitespace for prompt formatting
trim_blocks=True,
lstrip_blocks=True,
# Raise errors for undefined variables
undefined=jinja2.StrictUndefined,
)
# Register custom functions
env.globals['env'] = env_function
return env
def render_template(
template_str: str,
available_tools: "AvailableTools",
globals_dict: Optional[Dict[str, Any]] = None,
inputs_dict: Optional[Dict[str, Any]] = None,
result_value: Optional[Any] = None,
) -> str:
"""Render a template string with provided context.
Args:
template_str: Template string to render
available_tools: AvailableTools instance
globals_dict: Global variables dict
inputs_dict: Input variables dict
result_value: Result value for repeat_prompt
Returns:
Rendered template string
Raises:
jinja2.TemplateError: On template rendering errors
Examples:
# Render with globals
render_template("{{ globals.fruit }}", tools, globals_dict={'fruit': 'apple'})
# Render with result
render_template("{{ result.name }}", tools, result_value={'name': 'test'})
# Render with all context types
render_template(
"{{ globals.x }} {{ inputs.y }} {{ result.z }}",
tools,
globals_dict={'x': 1},
inputs_dict={'y': 2},
result_value={'z': 3}
)
"""
jinja_env = create_jinja_environment(available_tools)
# Build template context
context = {
'globals': globals_dict or {},
'inputs': inputs_dict or {},
}
# Add result if provided
if result_value is not None:
context['result'] = result_value
# Render template
template = jinja_env.from_string(template_str)
return template.render(**context)