refactor: Archive experimental LLM features for MVP stability (Phase 1.1)
Moved experimental LLM integration code to optimization_engine/future/: - llm_optimization_runner.py - Runtime LLM API runner - llm_workflow_analyzer.py - Workflow analysis - inline_code_generator.py - Auto-generate calculations - hook_generator.py - Auto-generate hooks - report_generator.py - LLM report generation - extractor_orchestrator.py - Extractor orchestration Added comprehensive optimization_engine/future/README.md explaining: - MVP LLM strategy (Claude Code skills, not runtime LLM) - Why files were archived - When to revisit post-MVP - Production architecture reference Production runner confirmed: optimization_engine/runner.py is sole active runner. This establishes clear separation between: - Production code (stable, no runtime LLM dependencies) - Experimental code (archived for post-MVP exploration) Part of Phase 1: Core Stabilization & Organization for MVP Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
423
optimization_engine/future/llm_workflow_analyzer.py
Normal file
423
optimization_engine/future/llm_workflow_analyzer.py
Normal file
@@ -0,0 +1,423 @@
|
||||
"""
|
||||
LLM-Powered Workflow Analyzer - Phase 2.7
|
||||
|
||||
Uses Claude (LLM) to intelligently analyze user requests instead of dumb regex patterns.
|
||||
This is what we should have built from the start!
|
||||
|
||||
Integration modes:
|
||||
1. Claude Code Skill (preferred for development) - uses Claude Code's built-in AI
|
||||
2. Anthropic API (fallback for standalone) - requires API key
|
||||
|
||||
Author: Atomizer Development Team
|
||||
Version: 0.2.0 (Phase 2.7)
|
||||
Last Updated: 2025-01-16
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from typing import List, Dict, Any, Optional
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from anthropic import Anthropic
|
||||
HAS_ANTHROPIC = True
|
||||
except ImportError:
|
||||
HAS_ANTHROPIC = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowStep:
|
||||
"""A single step in an optimization workflow."""
|
||||
action: str
|
||||
domain: str
|
||||
params: Dict[str, Any]
|
||||
step_type: str # 'engineering_feature', 'inline_calculation', 'post_processing_hook'
|
||||
priority: int = 0
|
||||
|
||||
|
||||
class LLMWorkflowAnalyzer:
|
||||
"""
|
||||
Uses Claude LLM to intelligently analyze optimization requests.
|
||||
NO MORE DUMB REGEX PATTERNS!
|
||||
|
||||
Integration modes:
|
||||
1. Claude Code integration (use_claude_code=True) - preferred for development
|
||||
2. Direct API (api_key provided) - for standalone execution
|
||||
3. Fallback heuristics (neither provided) - basic pattern matching
|
||||
"""
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None, use_claude_code: bool = True):
|
||||
"""
|
||||
Initialize LLM analyzer.
|
||||
|
||||
Args:
|
||||
api_key: Anthropic API key (optional, for standalone mode)
|
||||
use_claude_code: Use Claude Code skill for analysis (default: True)
|
||||
"""
|
||||
self.use_claude_code = use_claude_code
|
||||
self.client = None
|
||||
|
||||
if api_key and HAS_ANTHROPIC:
|
||||
self.client = Anthropic(api_key=api_key)
|
||||
self.use_claude_code = False # Prefer direct API if key provided
|
||||
|
||||
def analyze_request(self, user_request: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Use Claude to analyze the request and extract workflow steps intelligently.
|
||||
|
||||
Returns:
|
||||
{
|
||||
'engineering_features': [...],
|
||||
'inline_calculations': [...],
|
||||
'post_processing_hooks': [...],
|
||||
'optimization': {...}
|
||||
}
|
||||
"""
|
||||
|
||||
prompt = f"""You are analyzing a structural optimization request for the Atomizer system.
|
||||
|
||||
USER REQUEST:
|
||||
{user_request}
|
||||
|
||||
Your task: Break this down into atomic workflow steps and classify each step.
|
||||
|
||||
STEP TYPES:
|
||||
1. ENGINEERING FEATURES - Complex FEA/CAE operations needing specialized knowledge:
|
||||
- Extract results from OP2 files (displacement, stress, strain, element forces, etc.)
|
||||
- Modify FEA properties (CBUSH/CBAR stiffness, PCOMP layup, material properties)
|
||||
- Run simulations (SOL101, SOL103, etc.)
|
||||
- Create/modify geometry in NX
|
||||
|
||||
2. INLINE CALCULATIONS - Simple math operations (auto-generate Python):
|
||||
- Calculate average, min, max, sum
|
||||
- Compare values, compute ratios
|
||||
- Statistical operations
|
||||
|
||||
3. POST-PROCESSING HOOKS - Custom calculations between FEA steps:
|
||||
- Custom objective functions combining multiple results
|
||||
- Data transformations
|
||||
- Filtering/aggregation logic
|
||||
|
||||
4. OPTIMIZATION - Algorithm and configuration:
|
||||
- Optuna, genetic algorithm, etc.
|
||||
- Design variables and their ranges
|
||||
- Multi-objective vs single objective
|
||||
|
||||
IMPORTANT DISTINCTIONS:
|
||||
- "extract forces from 1D elements" → ENGINEERING FEATURE (needs pyNastran/OP2 knowledge)
|
||||
- "find average of forces" → INLINE CALCULATION (simple Python: sum/len)
|
||||
- "compare max to average and create metric" → POST-PROCESSING HOOK (custom logic)
|
||||
- Element forces vs Reaction forces are DIFFERENT (element internal forces vs nodal reactions)
|
||||
- CBUSH vs CBAR are different element types with different properties
|
||||
|
||||
Return a JSON object with this EXACT structure:
|
||||
{{
|
||||
"engineering_features": [
|
||||
{{
|
||||
"action": "extract_1d_element_forces",
|
||||
"domain": "result_extraction",
|
||||
"description": "Extract element forces from 1D elements (CBAR/CBUSH) in Z direction",
|
||||
"params": {{
|
||||
"element_types": ["CBAR", "CBUSH"],
|
||||
"result_type": "element_force",
|
||||
"direction": "Z"
|
||||
}}
|
||||
}}
|
||||
],
|
||||
"inline_calculations": [
|
||||
{{
|
||||
"action": "calculate_average",
|
||||
"description": "Calculate average of extracted forces",
|
||||
"params": {{
|
||||
"input": "forces_z",
|
||||
"operation": "mean"
|
||||
}}
|
||||
}},
|
||||
{{
|
||||
"action": "find_minimum",
|
||||
"description": "Find minimum force value",
|
||||
"params": {{
|
||||
"input": "forces_z",
|
||||
"operation": "min"
|
||||
}}
|
||||
}}
|
||||
],
|
||||
"post_processing_hooks": [
|
||||
{{
|
||||
"action": "custom_objective_metric",
|
||||
"description": "Compare minimum to average and create objective metric",
|
||||
"params": {{
|
||||
"inputs": ["min_force", "avg_force"],
|
||||
"formula": "min_force / avg_force",
|
||||
"objective": "minimize"
|
||||
}}
|
||||
}}
|
||||
],
|
||||
"optimization": {{
|
||||
"algorithm": "genetic_algorithm",
|
||||
"design_variables": [
|
||||
{{
|
||||
"parameter": "cbar_stiffness_x",
|
||||
"type": "FEA_property",
|
||||
"element_type": "CBAR"
|
||||
}}
|
||||
],
|
||||
"objectives": [
|
||||
{{
|
||||
"type": "minimize",
|
||||
"target": "custom_objective_metric"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
}}
|
||||
|
||||
Analyze the request and return ONLY the JSON, no other text."""
|
||||
|
||||
if self.client:
|
||||
# Use Claude API
|
||||
response = self.client.messages.create(
|
||||
model="claude-sonnet-4-20250514",
|
||||
max_tokens=4000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": prompt
|
||||
}]
|
||||
)
|
||||
|
||||
# Extract JSON from response
|
||||
content = response.content[0].text
|
||||
|
||||
# Find JSON in response
|
||||
start = content.find('{')
|
||||
end = content.rfind('}') + 1
|
||||
json_str = content[start:end]
|
||||
|
||||
return json.loads(json_str)
|
||||
else:
|
||||
# Fallback: return a template showing expected format
|
||||
return {
|
||||
"engineering_features": [],
|
||||
"inline_calculations": [],
|
||||
"post_processing_hooks": [],
|
||||
"optimization": {},
|
||||
"error": "No API key provided - cannot analyze request"
|
||||
}
|
||||
|
||||
def to_workflow_steps(self, analysis: Dict[str, Any]) -> List[WorkflowStep]:
|
||||
"""Convert LLM analysis to WorkflowStep objects."""
|
||||
steps = []
|
||||
priority = 0
|
||||
|
||||
# Add engineering features
|
||||
for feature in analysis.get('engineering_features', []):
|
||||
steps.append(WorkflowStep(
|
||||
action=feature['action'],
|
||||
domain=feature['domain'],
|
||||
params=feature.get('params', {}),
|
||||
step_type='engineering_feature',
|
||||
priority=priority
|
||||
))
|
||||
priority += 1
|
||||
|
||||
# Add inline calculations
|
||||
for calc in analysis.get('inline_calculations', []):
|
||||
steps.append(WorkflowStep(
|
||||
action=calc['action'],
|
||||
domain='calculation',
|
||||
params=calc.get('params', {}),
|
||||
step_type='inline_calculation',
|
||||
priority=priority
|
||||
))
|
||||
priority += 1
|
||||
|
||||
# Add post-processing hooks
|
||||
for hook in analysis.get('post_processing_hooks', []):
|
||||
steps.append(WorkflowStep(
|
||||
action=hook['action'],
|
||||
domain='post_processing',
|
||||
params=hook.get('params', {}),
|
||||
step_type='post_processing_hook',
|
||||
priority=priority
|
||||
))
|
||||
priority += 1
|
||||
|
||||
# Add optimization
|
||||
opt = analysis.get('optimization', {})
|
||||
if opt:
|
||||
steps.append(WorkflowStep(
|
||||
action='optimize',
|
||||
domain='optimization',
|
||||
params=opt,
|
||||
step_type='engineering_feature',
|
||||
priority=priority
|
||||
))
|
||||
|
||||
return steps
|
||||
|
||||
def get_summary(self, analysis: Dict[str, Any]) -> str:
|
||||
"""Generate human-readable summary of the analysis."""
|
||||
lines = []
|
||||
lines.append("LLM Workflow Analysis")
|
||||
lines.append("=" * 80)
|
||||
lines.append("")
|
||||
|
||||
# Engineering features
|
||||
eng_features = analysis.get('engineering_features', [])
|
||||
lines.append(f"Engineering Features (Need Research): {len(eng_features)}")
|
||||
for feature in eng_features:
|
||||
lines.append(f" - {feature['action']}")
|
||||
lines.append(f" Description: {feature.get('description', 'N/A')}")
|
||||
lines.append(f" Domain: {feature['domain']}")
|
||||
lines.append("")
|
||||
|
||||
# Inline calculations
|
||||
inline_calcs = analysis.get('inline_calculations', [])
|
||||
lines.append(f"Inline Calculations (Auto-Generate): {len(inline_calcs)}")
|
||||
for calc in inline_calcs:
|
||||
lines.append(f" - {calc['action']}")
|
||||
lines.append(f" Description: {calc.get('description', 'N/A')}")
|
||||
lines.append("")
|
||||
|
||||
# Post-processing hooks
|
||||
hooks = analysis.get('post_processing_hooks', [])
|
||||
lines.append(f"Post-Processing Hooks (Generate Middleware): {len(hooks)}")
|
||||
for hook in hooks:
|
||||
lines.append(f" - {hook['action']}")
|
||||
lines.append(f" Description: {hook.get('description', 'N/A')}")
|
||||
if 'formula' in hook.get('params', {}):
|
||||
lines.append(f" Formula: {hook['params']['formula']}")
|
||||
lines.append("")
|
||||
|
||||
# Optimization
|
||||
opt = analysis.get('optimization', {})
|
||||
if opt:
|
||||
lines.append("Optimization Configuration:")
|
||||
lines.append(f" Algorithm: {opt.get('algorithm', 'N/A')}")
|
||||
if 'design_variables' in opt:
|
||||
lines.append(f" Design Variables: {len(opt['design_variables'])}")
|
||||
for var in opt['design_variables']:
|
||||
lines.append(f" - {var.get('parameter', 'N/A')} ({var.get('type', 'N/A')})")
|
||||
if 'objectives' in opt:
|
||||
lines.append(f" Objectives:")
|
||||
for obj in opt['objectives']:
|
||||
lines.append(f" - {obj.get('type', 'N/A')} {obj.get('target', 'N/A')}")
|
||||
lines.append("")
|
||||
|
||||
# Summary
|
||||
total_steps = len(eng_features) + len(inline_calcs) + len(hooks) + (1 if opt else 0)
|
||||
lines.append(f"Total Steps: {total_steps}")
|
||||
lines.append(f" Engineering: {len(eng_features)} (need research/documentation)")
|
||||
lines.append(f" Simple Math: {len(inline_calcs)} (auto-generate Python)")
|
||||
lines.append(f" Hooks: {len(hooks)} (generate middleware)")
|
||||
lines.append(f" Optimization: {1 if opt else 0}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
"""Test the LLM workflow analyzer."""
|
||||
import os
|
||||
|
||||
print("=" * 80)
|
||||
print("LLM-Powered Workflow Analyzer Test")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# Test request
|
||||
request = """I want to extract forces in direction Z of all the 1D elements and find the average of it,
|
||||
then find the minimum value and compare it to the average, then assign it to a objective metric that needs to be minimized.
|
||||
|
||||
I want to iterate on the FEA properties of the Cbar element stiffness in X to make the objective function minimized.
|
||||
|
||||
I want to use genetic algorithm to iterate and optimize this"""
|
||||
|
||||
print("User Request:")
|
||||
print(request)
|
||||
print()
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# Get API key from environment
|
||||
api_key = os.environ.get('ANTHROPIC_API_KEY')
|
||||
|
||||
if not api_key:
|
||||
print("WARNING: No ANTHROPIC_API_KEY found in environment")
|
||||
print("Set it with: export ANTHROPIC_API_KEY=your_key_here")
|
||||
print()
|
||||
print("Showing expected output format instead...")
|
||||
print()
|
||||
|
||||
# Show what the output should look like
|
||||
expected = {
|
||||
"engineering_features": [
|
||||
{
|
||||
"action": "extract_1d_element_forces",
|
||||
"domain": "result_extraction",
|
||||
"description": "Extract element forces from 1D elements in Z direction",
|
||||
"params": {
|
||||
"element_types": ["CBAR"],
|
||||
"result_type": "element_force",
|
||||
"direction": "Z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"inline_calculations": [
|
||||
{
|
||||
"action": "calculate_average",
|
||||
"description": "Calculate average of extracted forces",
|
||||
"params": {"input": "forces_z", "operation": "mean"}
|
||||
},
|
||||
{
|
||||
"action": "find_minimum",
|
||||
"description": "Find minimum force value",
|
||||
"params": {"input": "forces_z", "operation": "min"}
|
||||
}
|
||||
],
|
||||
"post_processing_hooks": [
|
||||
{
|
||||
"action": "custom_objective_metric",
|
||||
"description": "Compare minimum to average",
|
||||
"params": {
|
||||
"inputs": ["min_force", "avg_force"],
|
||||
"formula": "min_force / avg_force",
|
||||
"objective": "minimize"
|
||||
}
|
||||
}
|
||||
],
|
||||
"optimization": {
|
||||
"algorithm": "genetic_algorithm",
|
||||
"design_variables": [
|
||||
{"parameter": "cbar_stiffness_x", "type": "FEA_property"}
|
||||
],
|
||||
"objectives": [{"type": "minimize", "target": "custom_objective_metric"}]
|
||||
}
|
||||
}
|
||||
|
||||
analyzer = LLMWorkflowAnalyzer()
|
||||
print(analyzer.get_summary(expected))
|
||||
return
|
||||
|
||||
# Use LLM to analyze
|
||||
analyzer = LLMWorkflowAnalyzer(api_key=api_key)
|
||||
|
||||
print("Calling Claude to analyze request...")
|
||||
print()
|
||||
|
||||
analysis = analyzer.analyze_request(request)
|
||||
|
||||
print("LLM Analysis Complete!")
|
||||
print()
|
||||
print(analyzer.get_summary(analysis))
|
||||
|
||||
print()
|
||||
print("=" * 80)
|
||||
print("Raw JSON Analysis:")
|
||||
print("=" * 80)
|
||||
print(json.dumps(analysis, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user