Spaces:
Running
Running
usmansafdarktk
commited on
Commit
·
ff9e41b
1
Parent(s):
63b19ff
fix(UI): improve dropdowns and update UI
Browse files- app.py +49 -36
- utils/data_generator.py +3 -53
- utils/template_discovery.py +44 -31
app.py
CHANGED
|
@@ -4,6 +4,8 @@ from utils.data_generator import generate_examples
|
|
| 4 |
from llm_client import ask_llm
|
| 5 |
from utils.evaluation import compute_similarity, validate_answer
|
| 6 |
import asyncio
|
|
|
|
|
|
|
| 7 |
|
| 8 |
print("Scanning for all available Engchain templates...")
|
| 9 |
ALL_TEMPLATES = discover_templates()
|
|
@@ -109,29 +111,31 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 109 |
background: var(--panel-background-fill);
|
| 110 |
}
|
| 111 |
""") as demo:
|
|
|
|
| 112 |
gr.Markdown(
|
| 113 |
"""
|
| 114 |
# 📈 Engchain Template Playground
|
| 115 |
-
Select a domain,
|
| 116 |
Compare your solutions against a live LLM and see similarity + correctness checks.
|
| 117 |
"""
|
| 118 |
)
|
| 119 |
|
| 120 |
with gr.Group(elem_classes="dropdown-box"):
|
| 121 |
with gr.Row():
|
| 122 |
-
|
| 123 |
-
label="1. Select
|
| 124 |
-
choices=[(format_name(
|
| 125 |
)
|
| 126 |
-
|
| 127 |
-
|
|
|
|
| 128 |
|
| 129 |
generate_button = gr.Button("Generate 10 Examples", variant="primary")
|
| 130 |
|
| 131 |
# Pre-create a slot for template heading
|
| 132 |
template_heading = gr.Markdown("", visible=False, elem_classes="template-heading")
|
| 133 |
|
| 134 |
-
# Pre-create 10 slots
|
| 135 |
example_blocks = []
|
| 136 |
for i in range(10):
|
| 137 |
with gr.Group(visible=False, elem_classes="example-card") as grp:
|
|
@@ -145,57 +149,70 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 145 |
|
| 146 |
example_blocks.append((grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md))
|
| 147 |
|
| 148 |
-
# Bind button → LLM response + evaluation
|
| 149 |
async def reveal_llm(q, s, llm_group=llm_group, eval_md=eval_md, llm_response_md=llm_response_md):
|
| 150 |
-
|
| 151 |
-
await asyncio.sleep(0) # ensures Gradio sees async
|
| 152 |
yield gr.update(visible=True), gr.update(value=""), gr.update(value="⏳ Fetching LLM response...")
|
| 153 |
-
|
| 154 |
-
# call the LLM asynchronously
|
| 155 |
-
response = await asyncio.to_thread(call_llm, q) # run blocking LLM call in thread
|
| 156 |
-
|
| 157 |
sim = compute_similarity(s, response)
|
| 158 |
match_flag = validate_answer(s, response)
|
| 159 |
-
|
| 160 |
eval_html = (
|
| 161 |
f"<div class='badge badge-blue'><span>Textual Similarity:</span> {sim}%</div>"
|
| 162 |
f"<div class='badge {'badge-green' if match_flag else 'badge-red'}'>"
|
| 163 |
f"<span>Final Answer Validation:</span> {'✅ Yes' if match_flag else '❌ No'}</div>"
|
| 164 |
)
|
| 165 |
-
|
| 166 |
yield gr.update(visible=True), gr.update(value=eval_html), gr.update(value=response)
|
| 167 |
|
| 168 |
btn.click(reveal_llm, inputs=[q_md, s_md], outputs=[llm_group, eval_md, llm_response_md])
|
| 169 |
|
| 170 |
# Dropdown update logic
|
| 171 |
-
def
|
| 172 |
-
if not
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
return (
|
| 174 |
gr.update(choices=[], value=None, interactive=False),
|
| 175 |
gr.update(choices=[], value=None, interactive=False),
|
| 176 |
)
|
| 177 |
-
files
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
return (
|
| 180 |
gr.update(choices=formatted_files, value=None, interactive=True),
|
| 181 |
gr.update(choices=[], value=None, interactive=False),
|
| 182 |
)
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
|
|
|
| 186 |
return gr.update(choices=[], value=None, interactive=False)
|
| 187 |
-
templates = sorted(ALL_TEMPLATES.get(domain, {}).get(filename, []))
|
| 188 |
formatted_templates = [(format_name(t), t) for t in templates]
|
| 189 |
return gr.update(choices=formatted_templates, value=None, interactive=True)
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
| 196 |
template_heading_update = gr.update(value=heading_text, visible=True)
|
| 197 |
|
| 198 |
-
# Clear all example slots
|
| 199 |
updates = []
|
| 200 |
for grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md in example_blocks:
|
| 201 |
updates.extend([
|
|
@@ -209,9 +226,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 209 |
])
|
| 210 |
return [template_heading_update] + updates
|
| 211 |
|
| 212 |
-
|
| 213 |
-
examples = generate_examples(domain, filename, template_name)
|
| 214 |
-
|
| 215 |
heading_text = f"<div style='margin-top:24px; font-size: 24px;'>Generated Examples for Template: <b>{format_name(template_name)}</b></div>"
|
| 216 |
template_heading_update = gr.update(value=heading_text, visible=True)
|
| 217 |
|
|
@@ -240,12 +255,9 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 240 |
])
|
| 241 |
return [template_heading_update] + updates
|
| 242 |
|
| 243 |
-
|
| 244 |
-
domain_dropdown.change(update_file_dropdown, domain_dropdown, [file_dropdown, template_dropdown])
|
| 245 |
-
file_dropdown.change(update_template_dropdown, [domain_dropdown, file_dropdown], template_dropdown)
|
| 246 |
generate_button.click(
|
| 247 |
render_examples,
|
| 248 |
-
[domain_dropdown, file_dropdown, template_dropdown],
|
| 249 |
[template_heading] + sum(([grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md]
|
| 250 |
for (grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md) in example_blocks), [])
|
| 251 |
)
|
|
@@ -254,3 +266,4 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 254 |
if __name__ == "__main__":
|
| 255 |
print("Launching Gradio app...")
|
| 256 |
demo.launch()
|
|
|
|
|
|
| 4 |
from llm_client import ask_llm
|
| 5 |
from utils.evaluation import compute_similarity, validate_answer
|
| 6 |
import asyncio
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
|
| 10 |
print("Scanning for all available Engchain templates...")
|
| 11 |
ALL_TEMPLATES = discover_templates()
|
|
|
|
| 111 |
background: var(--panel-background-fill);
|
| 112 |
}
|
| 113 |
""") as demo:
|
| 114 |
+
|
| 115 |
gr.Markdown(
|
| 116 |
"""
|
| 117 |
# 📈 Engchain Template Playground
|
| 118 |
+
Select a branch, domain, file, and template to generate 10 unique Q&A examples.
|
| 119 |
Compare your solutions against a live LLM and see similarity + correctness checks.
|
| 120 |
"""
|
| 121 |
)
|
| 122 |
|
| 123 |
with gr.Group(elem_classes="dropdown-box"):
|
| 124 |
with gr.Row():
|
| 125 |
+
branch_dropdown = gr.Dropdown(
|
| 126 |
+
label="1. Select Branch",
|
| 127 |
+
choices=[(format_name(b), b) for b in sorted(list(ALL_TEMPLATES.keys()))],
|
| 128 |
)
|
| 129 |
+
domain_dropdown = gr.Dropdown(label="2. Select Engineering Domain", interactive=False)
|
| 130 |
+
file_dropdown = gr.Dropdown(label="3. Select Specific Area / File", interactive=False)
|
| 131 |
+
template_dropdown = gr.Dropdown(label="4. Select Specific Tool / Template", interactive=False)
|
| 132 |
|
| 133 |
generate_button = gr.Button("Generate 10 Examples", variant="primary")
|
| 134 |
|
| 135 |
# Pre-create a slot for template heading
|
| 136 |
template_heading = gr.Markdown("", visible=False, elem_classes="template-heading")
|
| 137 |
|
| 138 |
+
# Pre-create 10 example slots
|
| 139 |
example_blocks = []
|
| 140 |
for i in range(10):
|
| 141 |
with gr.Group(visible=False, elem_classes="example-card") as grp:
|
|
|
|
| 149 |
|
| 150 |
example_blocks.append((grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md))
|
| 151 |
|
|
|
|
| 152 |
async def reveal_llm(q, s, llm_group=llm_group, eval_md=eval_md, llm_response_md=llm_response_md):
|
| 153 |
+
await asyncio.sleep(0)
|
|
|
|
| 154 |
yield gr.update(visible=True), gr.update(value=""), gr.update(value="⏳ Fetching LLM response...")
|
| 155 |
+
response = await asyncio.to_thread(call_llm, q)
|
|
|
|
|
|
|
|
|
|
| 156 |
sim = compute_similarity(s, response)
|
| 157 |
match_flag = validate_answer(s, response)
|
|
|
|
| 158 |
eval_html = (
|
| 159 |
f"<div class='badge badge-blue'><span>Textual Similarity:</span> {sim}%</div>"
|
| 160 |
f"<div class='badge {'badge-green' if match_flag else 'badge-red'}'>"
|
| 161 |
f"<span>Final Answer Validation:</span> {'✅ Yes' if match_flag else '❌ No'}</div>"
|
| 162 |
)
|
|
|
|
| 163 |
yield gr.update(visible=True), gr.update(value=eval_html), gr.update(value=response)
|
| 164 |
|
| 165 |
btn.click(reveal_llm, inputs=[q_md, s_md], outputs=[llm_group, eval_md, llm_response_md])
|
| 166 |
|
| 167 |
# Dropdown update logic
|
| 168 |
+
def update_domain_dropdown(branch):
|
| 169 |
+
if not branch:
|
| 170 |
+
return gr.update(choices=[], value=None, interactive=False), \
|
| 171 |
+
gr.update(choices=[], value=None, interactive=False), \
|
| 172 |
+
gr.update(choices=[], value=None, interactive=False)
|
| 173 |
+
domains = sorted(list(ALL_TEMPLATES.get(branch, {}).keys()))
|
| 174 |
+
formatted_domains = [(format_name(d), d) for d in domains]
|
| 175 |
+
return gr.update(choices=formatted_domains, value=None, interactive=True), \
|
| 176 |
+
gr.update(choices=[], value=None, interactive=False), \
|
| 177 |
+
gr.update(choices=[], value=None, interactive=False)
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def update_file_dropdown(branch, domain):
|
| 181 |
+
if not branch or not domain:
|
| 182 |
return (
|
| 183 |
gr.update(choices=[], value=None, interactive=False),
|
| 184 |
gr.update(choices=[], value=None, interactive=False),
|
| 185 |
)
|
| 186 |
+
# get all files for this branch+domain
|
| 187 |
+
files_dict = ALL_TEMPLATES.get(branch, {}).get(domain, {})
|
| 188 |
+
files = sorted(list(files_dict.keys()))
|
| 189 |
+
|
| 190 |
+
# Use only the last part of the path as label
|
| 191 |
+
formatted_files = [(format_name(os.path.basename(f)), f) for f in files]
|
| 192 |
+
|
| 193 |
return (
|
| 194 |
gr.update(choices=formatted_files, value=None, interactive=True),
|
| 195 |
gr.update(choices=[], value=None, interactive=False),
|
| 196 |
)
|
| 197 |
|
| 198 |
+
|
| 199 |
+
def update_template_dropdown(branch, domain, filename):
|
| 200 |
+
if not branch or not domain or not filename:
|
| 201 |
return gr.update(choices=[], value=None, interactive=False)
|
| 202 |
+
templates = sorted(ALL_TEMPLATES.get(branch, {}).get(domain, {}).get(filename, []))
|
| 203 |
formatted_templates = [(format_name(t), t) for t in templates]
|
| 204 |
return gr.update(choices=formatted_templates, value=None, interactive=True)
|
| 205 |
|
| 206 |
+
branch_dropdown.change(update_domain_dropdown, branch_dropdown, [domain_dropdown, file_dropdown, template_dropdown])
|
| 207 |
+
domain_dropdown.change(update_file_dropdown, [branch_dropdown, domain_dropdown], [file_dropdown, template_dropdown])
|
| 208 |
+
file_dropdown.change(update_template_dropdown, [branch_dropdown, domain_dropdown, file_dropdown], template_dropdown)
|
| 209 |
+
|
| 210 |
+
# Populate example slots
|
| 211 |
+
def render_examples(branch, domain, filename, template_name):
|
| 212 |
+
if not branch or not domain or not filename or not template_name:
|
| 213 |
+
heading_text = "<div style='margin-top:24px; font-size: 20px; color: red;'>⚠️ Please select a branch, domain, file, and template/tool before generating examples.</div>"
|
| 214 |
template_heading_update = gr.update(value=heading_text, visible=True)
|
| 215 |
|
|
|
|
| 216 |
updates = []
|
| 217 |
for grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md in example_blocks:
|
| 218 |
updates.extend([
|
|
|
|
| 226 |
])
|
| 227 |
return [template_heading_update] + updates
|
| 228 |
|
| 229 |
+
examples = generate_examples(branch, domain, filename, template_name)
|
|
|
|
|
|
|
| 230 |
heading_text = f"<div style='margin-top:24px; font-size: 24px;'>Generated Examples for Template: <b>{format_name(template_name)}</b></div>"
|
| 231 |
template_heading_update = gr.update(value=heading_text, visible=True)
|
| 232 |
|
|
|
|
| 255 |
])
|
| 256 |
return [template_heading_update] + updates
|
| 257 |
|
|
|
|
|
|
|
|
|
|
| 258 |
generate_button.click(
|
| 259 |
render_examples,
|
| 260 |
+
[branch_dropdown, domain_dropdown, file_dropdown, template_dropdown],
|
| 261 |
[template_heading] + sum(([grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md]
|
| 262 |
for (grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md) in example_blocks), [])
|
| 263 |
)
|
|
|
|
| 266 |
if __name__ == "__main__":
|
| 267 |
print("Launching Gradio app...")
|
| 268 |
demo.launch()
|
| 269 |
+
|
utils/data_generator.py
CHANGED
|
@@ -61,21 +61,12 @@ def discover_templates(root_dir="data/templates/branches"):
|
|
| 61 |
|
| 62 |
return discovered
|
| 63 |
|
| 64 |
-
|
| 65 |
-
def generate_examples(domain, filename, template_name):
|
| 66 |
"""
|
| 67 |
Dynamically imports and runs a selected template function multiple times
|
| 68 |
to generate a list of {question, solution} objects.
|
| 69 |
-
|
| 70 |
-
Args:
|
| 71 |
-
domain (str): The name of the domain (e.g. 'chemical_engineering').
|
| 72 |
-
filename (str): Relative path under branches/ (e.g. 'chemical_engineering/reaction_kinetics/mole_balances.py').
|
| 73 |
-
template_name (str): The template function to call (e.g. 'template_mole_balance').
|
| 74 |
-
|
| 75 |
-
Returns:
|
| 76 |
-
list[dict]: [{"question": ..., "solution": ...}, ...]
|
| 77 |
"""
|
| 78 |
-
if not all([domain, filename, template_name]):
|
| 79 |
print("Missing arguments to generate_examples()")
|
| 80 |
return []
|
| 81 |
|
|
@@ -84,7 +75,6 @@ def generate_examples(domain, filename, template_name):
|
|
| 84 |
filename_no_ext = filename.replace(".py", "").replace(os.sep, ".")
|
| 85 |
module_path = f"data.templates.branches.{filename_no_ext}"
|
| 86 |
|
| 87 |
-
# Import module dynamically
|
| 88 |
module = importlib.import_module(module_path)
|
| 89 |
template_function = getattr(module, template_name)
|
| 90 |
|
|
@@ -96,48 +86,8 @@ def generate_examples(domain, filename, template_name):
|
|
| 96 |
examples.append({"question": question, "solution": solution})
|
| 97 |
else:
|
| 98 |
print(f"Warning: {template_name} did not return (question, solution) tuple.")
|
| 99 |
-
|
| 100 |
return examples
|
| 101 |
|
| 102 |
-
except ImportError as e:
|
| 103 |
-
print(f"ImportError in {domain}/{filename}: {e}")
|
| 104 |
-
return []
|
| 105 |
-
except AttributeError as e:
|
| 106 |
-
print(f"AttributeError in {domain}/{filename}: {e}")
|
| 107 |
-
return []
|
| 108 |
except Exception as e:
|
| 109 |
-
print(f"Error running template '{template_name}' from {filename}: {e}")
|
| 110 |
return []
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
if __name__ == "__main__":
|
| 114 |
-
"""
|
| 115 |
-
Run this file directly to test the discovery and generation system.
|
| 116 |
-
"""
|
| 117 |
-
print("Discovering templates...\n")
|
| 118 |
-
templates = discover_templates()
|
| 119 |
-
|
| 120 |
-
if not templates:
|
| 121 |
-
print("No templates found.")
|
| 122 |
-
else:
|
| 123 |
-
for domain, files in templates.items():
|
| 124 |
-
print(f"\nDomain: {domain}")
|
| 125 |
-
for file_path, funcs in files.items():
|
| 126 |
-
print(f" {file_path}")
|
| 127 |
-
for func in funcs:
|
| 128 |
-
print(f" {func}")
|
| 129 |
-
|
| 130 |
-
# Example test: run one of the templates
|
| 131 |
-
print("\n\n Testing template generation...")
|
| 132 |
-
# pick first available template
|
| 133 |
-
first_domain = next(iter(templates))
|
| 134 |
-
first_file, first_funcs = next(iter(templates[first_domain].items()))
|
| 135 |
-
first_func = first_funcs[0]
|
| 136 |
-
|
| 137 |
-
examples = generate_examples(first_domain, first_file, first_func)
|
| 138 |
-
print(f"\n Generated {len(examples)} examples from {first_func} in {first_file}:")
|
| 139 |
-
for ex in examples[:3]:
|
| 140 |
-
print("Q:", ex["question"])
|
| 141 |
-
print("A:", ex["solution"])
|
| 142 |
-
print("---")
|
| 143 |
-
|
|
|
|
| 61 |
|
| 62 |
return discovered
|
| 63 |
|
| 64 |
+
def generate_examples(branch, domain, filename, template_name):
|
|
|
|
| 65 |
"""
|
| 66 |
Dynamically imports and runs a selected template function multiple times
|
| 67 |
to generate a list of {question, solution} objects.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
"""
|
| 69 |
+
if not all([branch, domain, filename, template_name]):
|
| 70 |
print("Missing arguments to generate_examples()")
|
| 71 |
return []
|
| 72 |
|
|
|
|
| 75 |
filename_no_ext = filename.replace(".py", "").replace(os.sep, ".")
|
| 76 |
module_path = f"data.templates.branches.{filename_no_ext}"
|
| 77 |
|
|
|
|
| 78 |
module = importlib.import_module(module_path)
|
| 79 |
template_function = getattr(module, template_name)
|
| 80 |
|
|
|
|
| 86 |
examples.append({"question": question, "solution": solution})
|
| 87 |
else:
|
| 88 |
print(f"Warning: {template_name} did not return (question, solution) tuple.")
|
|
|
|
| 89 |
return examples
|
| 90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
except Exception as e:
|
| 92 |
+
print(f"Error running template '{template_name}' from {branch}/{domain}/{filename}: {e}")
|
| 93 |
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
utils/template_discovery.py
CHANGED
|
@@ -4,7 +4,15 @@ import ast
|
|
| 4 |
def discover_templates(root_dir="data/templates/branches"):
|
| 5 |
"""
|
| 6 |
Recursively scans directory structure to find all template functions
|
| 7 |
-
under each domain
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
"""
|
| 9 |
discovered = {}
|
| 10 |
|
|
@@ -12,35 +20,40 @@ def discover_templates(root_dir="data/templates/branches"):
|
|
| 12 |
print(f"Error: Directory '{root_dir}' not found.")
|
| 13 |
return discovered
|
| 14 |
|
| 15 |
-
#
|
| 16 |
-
for
|
| 17 |
-
|
| 18 |
-
if os.path.isdir(
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
#
|
| 22 |
-
for
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
return discovered
|
| 46 |
-
|
|
|
|
| 4 |
def discover_templates(root_dir="data/templates/branches"):
|
| 5 |
"""
|
| 6 |
Recursively scans directory structure to find all template functions
|
| 7 |
+
under each branch/domain/file.
|
| 8 |
+
Returns:
|
| 9 |
+
dict: {
|
| 10 |
+
branch: {
|
| 11 |
+
domain: {
|
| 12 |
+
file_path: [template_function_names]
|
| 13 |
+
}
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
"""
|
| 17 |
discovered = {}
|
| 18 |
|
|
|
|
| 20 |
print(f"Error: Directory '{root_dir}' not found.")
|
| 21 |
return discovered
|
| 22 |
|
| 23 |
+
# Top-level folders = branches
|
| 24 |
+
for branch in sorted(os.listdir(root_dir)):
|
| 25 |
+
branch_path = os.path.join(root_dir, branch)
|
| 26 |
+
if os.path.isdir(branch_path):
|
| 27 |
+
branch_dict = {}
|
| 28 |
+
|
| 29 |
+
# Domains under branch
|
| 30 |
+
for domain in sorted(os.listdir(branch_path)):
|
| 31 |
+
domain_path = os.path.join(branch_path, domain)
|
| 32 |
+
if os.path.isdir(domain_path):
|
| 33 |
+
domain_templates = {}
|
| 34 |
+
|
| 35 |
+
# Walk all Python files under domain
|
| 36 |
+
for dirpath, _, filenames in os.walk(domain_path):
|
| 37 |
+
for filename in sorted(filenames):
|
| 38 |
+
if filename.endswith(".py"):
|
| 39 |
+
file_path = os.path.join(dirpath, filename)
|
| 40 |
+
try:
|
| 41 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 42 |
+
tree = ast.parse(f.read())
|
| 43 |
+
template_functions = [
|
| 44 |
+
node.name for node in ast.walk(tree)
|
| 45 |
+
if isinstance(node, ast.FunctionDef) and node.name.startswith("template_")
|
| 46 |
+
]
|
| 47 |
+
if template_functions:
|
| 48 |
+
rel_path = os.path.relpath(file_path, root_dir)
|
| 49 |
+
domain_templates[rel_path] = sorted(template_functions)
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"Error parsing {file_path}: {e}")
|
| 52 |
+
|
| 53 |
+
if domain_templates:
|
| 54 |
+
branch_dict[domain] = domain_templates
|
| 55 |
+
|
| 56 |
+
if branch_dict:
|
| 57 |
+
discovered[branch] = branch_dict
|
| 58 |
|
| 59 |
return discovered
|
|
|