CLARISSA Tutorial 09: Full Pipeline Demoยถ
Learning Objectives:
- See all CLARISSA components working together
- Walk through a complete conversation-to-simulation flow
- Understand the data flow between layers
- Execute a real simulation with OPM Flow
Prerequisites: Notebooks 01-08
Estimated Time: 60 minutes
Pipeline Overviewยถ
User Input โ NL Parser โ Intent/Entities โ Knowledge Layer
โ โ
Conversation โ LLM Layer โโโโโโโโโโโโโโโ Constraint Engine
โ โ
Deck Generator โ Validation โ OPM Flow โ Results
โ โ
โโโโโโโโโโ RL Feedback โโโโโโโโโโโโโโโโโโโโโ
This notebook demonstrates the complete flow.
Inย [ย ]:
Copied!
# Imports from previous tutorials (simplified versions)
import os
import json
import subprocess
import tempfile
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Any
from enum import Enum, auto
from pathlib import Path
import re
print("CLARISSA Full Pipeline Demo")
print("=" * 50)
# Imports from previous tutorials (simplified versions)
import os
import json
import subprocess
import tempfile
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Any
from enum import Enum, auto
from pathlib import Path
import re
print("CLARISSA Full Pipeline Demo")
print("=" * 50)
Section 1: Pipeline Componentsยถ
Bring together all the components from previous tutorials.
Inย [ย ]:
Copied!
# ========== From Tutorial 04: Conversation Layer ==========
class UserIntent(Enum):
CREATE_MODEL = auto()
MODIFY_MODEL = auto()
ADD_WELL = auto()
RUN_SIMULATION = auto()
SHOW_RESULTS = auto()
EXPLAIN = auto()
UNKNOWN = auto()
@dataclass
class ExtractedEntities:
well_names: List[str] = field(default_factory=list)
coordinates: List[Tuple[int, int]] = field(default_factory=list)
rates: List[float] = field(default_factory=list)
pressures: List[float] = field(default_factory=list)
depths: List[float] = field(default_factory=list)
time_values: List[Tuple[float, str]] = field(default_factory=list)
porosity: Optional[float] = None
permeability: Optional[float] = None
grid_size: Optional[Tuple[int, int, int]] = None
class NLParser:
"""Parse natural language to intent and entities."""
def parse(self, text: str) -> Tuple[UserIntent, ExtractedEntities]:
text_lower = text.lower()
entities = ExtractedEntities()
# Intent classification
if any(w in text_lower for w in ['create', 'build', 'new model']):
intent = UserIntent.CREATE_MODEL
elif any(w in text_lower for w in ['add well', 'new well', 'producer', 'injector']):
intent = UserIntent.ADD_WELL
elif any(w in text_lower for w in ['run', 'simulate', 'execute']):
intent = UserIntent.RUN_SIMULATION
else:
intent = UserIntent.UNKNOWN
# Entity extraction
# Grid size
grid_match = re.search(r'(\d+)\s*x\s*(\d+)\s*x?\s*(\d+)?', text_lower)
if grid_match:
nx, ny = int(grid_match.group(1)), int(grid_match.group(2))
nz = int(grid_match.group(3)) if grid_match.group(3) else 1
entities.grid_size = (nx, ny, nz)
# Porosity
poro_match = re.search(r'porosity[:\s]+([\d.]+)', text_lower)
if poro_match:
entities.porosity = float(poro_match.group(1))
# Permeability
perm_match = re.search(r'permeability[:\s]+([\d.]+)', text_lower)
if perm_match:
entities.permeability = float(perm_match.group(1))
# Depth
depth_match = re.search(r'(\d+)\s*(?:ft|feet)', text_lower)
if depth_match:
entities.depths.append(float(depth_match.group(1)))
# Pressure
pressure_match = re.search(r'(\d+)\s*psi', text_lower)
if pressure_match:
entities.pressures.append(float(pressure_match.group(1)))
return intent, entities
print("NL Parser ready")
# ========== From Tutorial 04: Conversation Layer ==========
class UserIntent(Enum):
CREATE_MODEL = auto()
MODIFY_MODEL = auto()
ADD_WELL = auto()
RUN_SIMULATION = auto()
SHOW_RESULTS = auto()
EXPLAIN = auto()
UNKNOWN = auto()
@dataclass
class ExtractedEntities:
well_names: List[str] = field(default_factory=list)
coordinates: List[Tuple[int, int]] = field(default_factory=list)
rates: List[float] = field(default_factory=list)
pressures: List[float] = field(default_factory=list)
depths: List[float] = field(default_factory=list)
time_values: List[Tuple[float, str]] = field(default_factory=list)
porosity: Optional[float] = None
permeability: Optional[float] = None
grid_size: Optional[Tuple[int, int, int]] = None
class NLParser:
"""Parse natural language to intent and entities."""
def parse(self, text: str) -> Tuple[UserIntent, ExtractedEntities]:
text_lower = text.lower()
entities = ExtractedEntities()
# Intent classification
if any(w in text_lower for w in ['create', 'build', 'new model']):
intent = UserIntent.CREATE_MODEL
elif any(w in text_lower for w in ['add well', 'new well', 'producer', 'injector']):
intent = UserIntent.ADD_WELL
elif any(w in text_lower for w in ['run', 'simulate', 'execute']):
intent = UserIntent.RUN_SIMULATION
else:
intent = UserIntent.UNKNOWN
# Entity extraction
# Grid size
grid_match = re.search(r'(\d+)\s*x\s*(\d+)\s*x?\s*(\d+)?', text_lower)
if grid_match:
nx, ny = int(grid_match.group(1)), int(grid_match.group(2))
nz = int(grid_match.group(3)) if grid_match.group(3) else 1
entities.grid_size = (nx, ny, nz)
# Porosity
poro_match = re.search(r'porosity[:\s]+([\d.]+)', text_lower)
if poro_match:
entities.porosity = float(poro_match.group(1))
# Permeability
perm_match = re.search(r'permeability[:\s]+([\d.]+)', text_lower)
if perm_match:
entities.permeability = float(perm_match.group(1))
# Depth
depth_match = re.search(r'(\d+)\s*(?:ft|feet)', text_lower)
if depth_match:
entities.depths.append(float(depth_match.group(1)))
# Pressure
pressure_match = re.search(r'(\d+)\s*psi', text_lower)
if pressure_match:
entities.pressures.append(float(pressure_match.group(1)))
return intent, entities
print("NL Parser ready")
Inย [ย ]:
Copied!
# ========== From Tutorial 05: Constraint Engine ==========
class ConstraintChecker:
"""Validate physics constraints."""
def check_all(self, params: Dict) -> Tuple[bool, List[str]]:
"""Check all constraints, return (valid, issues)."""
issues = []
# Porosity range
if 'porosity' in params:
if not (0 < params['porosity'] < 0.5):
issues.append(f"Porosity {params['porosity']} out of range (0, 0.5)")
# Permeability positive
if 'permeability' in params:
if params['permeability'] <= 0:
issues.append("Permeability must be positive")
# Pressure gradient
if 'depth' in params and 'pressure' in params:
gradient = params['pressure'] / params['depth']
if not (0.35 <= gradient <= 0.55):
issues.append(f"Pressure gradient {gradient:.3f} psi/ft unusual")
return len(issues) == 0, issues
print("Constraint Checker ready")
# ========== From Tutorial 05: Constraint Engine ==========
class ConstraintChecker:
"""Validate physics constraints."""
def check_all(self, params: Dict) -> Tuple[bool, List[str]]:
"""Check all constraints, return (valid, issues)."""
issues = []
# Porosity range
if 'porosity' in params:
if not (0 < params['porosity'] < 0.5):
issues.append(f"Porosity {params['porosity']} out of range (0, 0.5)")
# Permeability positive
if 'permeability' in params:
if params['permeability'] <= 0:
issues.append("Permeability must be positive")
# Pressure gradient
if 'depth' in params and 'pressure' in params:
gradient = params['pressure'] / params['depth']
if not (0.35 <= gradient <= 0.55):
issues.append(f"Pressure gradient {gradient:.3f} psi/ft unusual")
return len(issues) == 0, issues
print("Constraint Checker ready")
Inย [ย ]:
Copied!
# ========== From Tutorial 06: Deck Generator ==========
class DeckGenerator:
"""Generate ECLIPSE decks."""
def generate(self, params: Dict) -> str:
"""Generate complete deck from parameters."""
nx = params.get('nx', 10)
ny = params.get('ny', 10)
nz = params.get('nz', 5)
total = nx * ny * nz
top_cells = nx * ny
poro = params.get('porosity', 0.2)
perm = params.get('permeability', 100)
depth = params.get('depth', 8000)
pressure = params.get('pressure', 3500)
deck = f'''RUNSPEC
TITLE
CLARISSA Generated Model
OIL
WATER
FIELD
DIMENS
{nx} {ny} {nz} /
TABDIMS
1 1 20 20 /
WELLDIMS
10 50 5 10 /
START
1 JAN 2024 /
GRID
DX
{total}*100 /
DY
{total}*100 /
DZ
{total}*20 /
TOPS
{top_cells}*{depth} /
PORO
{total}*{poro} /
PERMX
{total}*{perm} /
PERMY
{total}*{perm} /
PERMZ
{total}*{perm*0.1:.1f} /
PROPS
SWOF
0.20 0.0000 1.0000 0.0
0.30 0.0200 0.6000 0.0
0.50 0.1000 0.2000 0.0
0.70 0.3500 0.0200 0.0
0.80 0.5000 0.0000 0.0 /
PVTW
{pressure} 1.01 3.0E-6 0.5 0 /
PVDO
1000 1.20 1.5
2000 1.15 1.2
3000 1.10 1.0
4000 1.05 0.8
5000 1.02 0.7 /
ROCK
{pressure} 3.0E-6 /
DENSITY
45.0 64.0 0.06 /
SOLUTION
EQUIL
{depth} {pressure} {depth + 1000} 0 0 0 1 /
SCHEDULE
WELSPECS
PROD1 G1 {nx//2} {ny//2} 1* OIL /
/
COMPDAT
PROD1 {nx//2} {ny//2} 1 {nz} OPEN 1* 0.5 /
/
WCONPROD
PROD1 OPEN ORAT 500 4* 1000 /
/
TSTEP
30*30 /
END
'''
return deck
print("Deck Generator ready")
# ========== From Tutorial 06: Deck Generator ==========
class DeckGenerator:
"""Generate ECLIPSE decks."""
def generate(self, params: Dict) -> str:
"""Generate complete deck from parameters."""
nx = params.get('nx', 10)
ny = params.get('ny', 10)
nz = params.get('nz', 5)
total = nx * ny * nz
top_cells = nx * ny
poro = params.get('porosity', 0.2)
perm = params.get('permeability', 100)
depth = params.get('depth', 8000)
pressure = params.get('pressure', 3500)
deck = f'''RUNSPEC
TITLE
CLARISSA Generated Model
OIL
WATER
FIELD
DIMENS
{nx} {ny} {nz} /
TABDIMS
1 1 20 20 /
WELLDIMS
10 50 5 10 /
START
1 JAN 2024 /
GRID
DX
{total}*100 /
DY
{total}*100 /
DZ
{total}*20 /
TOPS
{top_cells}*{depth} /
PORO
{total}*{poro} /
PERMX
{total}*{perm} /
PERMY
{total}*{perm} /
PERMZ
{total}*{perm*0.1:.1f} /
PROPS
SWOF
0.20 0.0000 1.0000 0.0
0.30 0.0200 0.6000 0.0
0.50 0.1000 0.2000 0.0
0.70 0.3500 0.0200 0.0
0.80 0.5000 0.0000 0.0 /
PVTW
{pressure} 1.01 3.0E-6 0.5 0 /
PVDO
1000 1.20 1.5
2000 1.15 1.2
3000 1.10 1.0
4000 1.05 0.8
5000 1.02 0.7 /
ROCK
{pressure} 3.0E-6 /
DENSITY
45.0 64.0 0.06 /
SOLUTION
EQUIL
{depth} {pressure} {depth + 1000} 0 0 0 1 /
SCHEDULE
WELSPECS
PROD1 G1 {nx//2} {ny//2} 1* OIL /
/
COMPDAT
PROD1 {nx//2} {ny//2} 1 {nz} OPEN 1* 0.5 /
/
WCONPROD
PROD1 OPEN ORAT 500 4* 1000 /
/
TSTEP
30*30 /
END
'''
return deck
print("Deck Generator ready")
Section 2: The CLARISSA Pipelineยถ
Orchestrate all components.
Inย [ย ]:
Copied!
class CLARISSAPipeline:
"""Main CLARISSA processing pipeline."""
def __init__(self):
self.parser = NLParser()
self.constraints = ConstraintChecker()
self.generator = DeckGenerator()
self.conversation_history: List[Dict] = []
self.current_params: Dict = {}
self.current_deck: Optional[str] = None
self.assumptions: List[str] = []
def process_message(self, user_message: str) -> str:
"""Process a user message and return response."""
self.conversation_history.append({'role': 'user', 'content': user_message})
# 1. Parse intent and entities
intent, entities = self.parser.parse(user_message)
# 2. Update parameters from entities
self._update_params(entities)
# 3. Route based on intent
if intent == UserIntent.CREATE_MODEL:
response = self._handle_create_model(entities)
elif intent == UserIntent.ADD_WELL:
response = self._handle_add_well(entities)
elif intent == UserIntent.RUN_SIMULATION:
response = self._handle_run_simulation()
else:
response = self._handle_unknown(user_message)
self.conversation_history.append({'role': 'assistant', 'content': response})
return response
def _update_params(self, entities: ExtractedEntities):
"""Update current parameters from extracted entities."""
if entities.grid_size:
self.current_params['nx'] = entities.grid_size[0]
self.current_params['ny'] = entities.grid_size[1]
self.current_params['nz'] = entities.grid_size[2]
if entities.porosity:
self.current_params['porosity'] = entities.porosity
if entities.permeability:
self.current_params['permeability'] = entities.permeability
if entities.depths:
self.current_params['depth'] = entities.depths[0]
if entities.pressures:
self.current_params['pressure'] = entities.pressures[0]
def _handle_create_model(self, entities: ExtractedEntities) -> str:
"""Handle model creation request."""
self.assumptions = []
# Apply defaults for missing parameters
defaults = {
'nx': 10, 'ny': 10, 'nz': 5,
'porosity': 0.2, 'permeability': 100,
'depth': 8000, 'pressure': 3500
}
for key, default in defaults.items():
if key not in self.current_params:
self.current_params[key] = default
self.assumptions.append(f"Using default {key}={default}")
# Validate constraints
valid, issues = self.constraints.check_all(self.current_params)
if not valid:
return f"I found some issues: {', '.join(issues)}. Please clarify."
# Generate deck
self.current_deck = self.generator.generate(self.current_params)
# Build response
nx, ny, nz = self.current_params['nx'], self.current_params['ny'], self.current_params['nz']
response = f"I've created a {nx}x{ny}x{nz} simulation model.\n\n"
if self.assumptions:
response += "Assumptions made:\n"
for a in self.assumptions:
response += f" - {a}\n"
response += "\nThe deck is ready. Would you like to run the simulation?"
return response
def _handle_add_well(self, entities: ExtractedEntities) -> str:
"""Handle add well request."""
if not self.current_deck:
return "Please create a model first before adding wells."
return "Well functionality coming in next version."
def _handle_run_simulation(self) -> str:
"""Handle simulation run request."""
if not self.current_deck:
return "No deck to simulate. Please create a model first."
# Try to run OPM Flow
result = self._execute_opm_flow()
return result
def _handle_unknown(self, message: str) -> str:
"""Handle unknown intent."""
return ("I can help you create reservoir simulation models. Try:\n"
"- 'Create a 20x20x5 model with porosity 0.22'\n"
"- 'Run the simulation'\n"
"- 'Add a producer well at coordinates 10,10'")
def _execute_opm_flow(self) -> str:
"""Execute OPM Flow simulation."""
# Check if OPM Flow is available
opm_path = os.environ.get('OPM_FLOW_PATH', '/usr/bin/flow')
if not os.path.exists(opm_path):
return ("OPM Flow not found. The deck has been generated and is ready.\n"
f"You can run it manually with: flow YOUR_DECK.DATA\n\n"
f"Deck preview (first 500 chars):\n{self.current_deck[:500]}...")
# Write deck to temp file
with tempfile.TemporaryDirectory() as tmpdir:
deck_path = Path(tmpdir) / "CLARISSA_MODEL.DATA"
deck_path.write_text(self.current_deck)
try:
result = subprocess.run(
[opm_path, str(deck_path)],
capture_output=True,
text=True,
timeout=300,
cwd=tmpdir
)
if result.returncode == 0:
return "Simulation completed successfully! Results are available."
else:
return f"Simulation failed: {result.stderr[:500]}"
except subprocess.TimeoutExpired:
return "Simulation timed out after 5 minutes."
except Exception as e:
return f"Simulation error: {str(e)}"
print("CLARISSA Pipeline ready")
class CLARISSAPipeline:
"""Main CLARISSA processing pipeline."""
def __init__(self):
self.parser = NLParser()
self.constraints = ConstraintChecker()
self.generator = DeckGenerator()
self.conversation_history: List[Dict] = []
self.current_params: Dict = {}
self.current_deck: Optional[str] = None
self.assumptions: List[str] = []
def process_message(self, user_message: str) -> str:
"""Process a user message and return response."""
self.conversation_history.append({'role': 'user', 'content': user_message})
# 1. Parse intent and entities
intent, entities = self.parser.parse(user_message)
# 2. Update parameters from entities
self._update_params(entities)
# 3. Route based on intent
if intent == UserIntent.CREATE_MODEL:
response = self._handle_create_model(entities)
elif intent == UserIntent.ADD_WELL:
response = self._handle_add_well(entities)
elif intent == UserIntent.RUN_SIMULATION:
response = self._handle_run_simulation()
else:
response = self._handle_unknown(user_message)
self.conversation_history.append({'role': 'assistant', 'content': response})
return response
def _update_params(self, entities: ExtractedEntities):
"""Update current parameters from extracted entities."""
if entities.grid_size:
self.current_params['nx'] = entities.grid_size[0]
self.current_params['ny'] = entities.grid_size[1]
self.current_params['nz'] = entities.grid_size[2]
if entities.porosity:
self.current_params['porosity'] = entities.porosity
if entities.permeability:
self.current_params['permeability'] = entities.permeability
if entities.depths:
self.current_params['depth'] = entities.depths[0]
if entities.pressures:
self.current_params['pressure'] = entities.pressures[0]
def _handle_create_model(self, entities: ExtractedEntities) -> str:
"""Handle model creation request."""
self.assumptions = []
# Apply defaults for missing parameters
defaults = {
'nx': 10, 'ny': 10, 'nz': 5,
'porosity': 0.2, 'permeability': 100,
'depth': 8000, 'pressure': 3500
}
for key, default in defaults.items():
if key not in self.current_params:
self.current_params[key] = default
self.assumptions.append(f"Using default {key}={default}")
# Validate constraints
valid, issues = self.constraints.check_all(self.current_params)
if not valid:
return f"I found some issues: {', '.join(issues)}. Please clarify."
# Generate deck
self.current_deck = self.generator.generate(self.current_params)
# Build response
nx, ny, nz = self.current_params['nx'], self.current_params['ny'], self.current_params['nz']
response = f"I've created a {nx}x{ny}x{nz} simulation model.\n\n"
if self.assumptions:
response += "Assumptions made:\n"
for a in self.assumptions:
response += f" - {a}\n"
response += "\nThe deck is ready. Would you like to run the simulation?"
return response
def _handle_add_well(self, entities: ExtractedEntities) -> str:
"""Handle add well request."""
if not self.current_deck:
return "Please create a model first before adding wells."
return "Well functionality coming in next version."
def _handle_run_simulation(self) -> str:
"""Handle simulation run request."""
if not self.current_deck:
return "No deck to simulate. Please create a model first."
# Try to run OPM Flow
result = self._execute_opm_flow()
return result
def _handle_unknown(self, message: str) -> str:
"""Handle unknown intent."""
return ("I can help you create reservoir simulation models. Try:\n"
"- 'Create a 20x20x5 model with porosity 0.22'\n"
"- 'Run the simulation'\n"
"- 'Add a producer well at coordinates 10,10'")
def _execute_opm_flow(self) -> str:
"""Execute OPM Flow simulation."""
# Check if OPM Flow is available
opm_path = os.environ.get('OPM_FLOW_PATH', '/usr/bin/flow')
if not os.path.exists(opm_path):
return ("OPM Flow not found. The deck has been generated and is ready.\n"
f"You can run it manually with: flow YOUR_DECK.DATA\n\n"
f"Deck preview (first 500 chars):\n{self.current_deck[:500]}...")
# Write deck to temp file
with tempfile.TemporaryDirectory() as tmpdir:
deck_path = Path(tmpdir) / "CLARISSA_MODEL.DATA"
deck_path.write_text(self.current_deck)
try:
result = subprocess.run(
[opm_path, str(deck_path)],
capture_output=True,
text=True,
timeout=300,
cwd=tmpdir
)
if result.returncode == 0:
return "Simulation completed successfully! Results are available."
else:
return f"Simulation failed: {result.stderr[:500]}"
except subprocess.TimeoutExpired:
return "Simulation timed out after 5 minutes."
except Exception as e:
return f"Simulation error: {str(e)}"
print("CLARISSA Pipeline ready")
Section 3: Demo Conversationยถ
Walk through a complete interaction.
Inย [ย ]:
Copied!
# Initialize pipeline
clarissa = CLARISSAPipeline()
print("CLARISSA Conversation Demo")
print("=" * 60)
# Conversation turns
conversation = [
"Create a waterflood model with 20x20x5 grid, porosity 0.22, permeability 150 md",
"The reservoir is at 8500 ft depth with 3800 psi initial pressure",
"Run the simulation"
]
for i, user_msg in enumerate(conversation, 1):
print(f"\n[Turn {i}]")
print(f"User: {user_msg}")
response = clarissa.process_message(user_msg)
print(f"\nCLARISSA: {response}")
print("-" * 60)
# Initialize pipeline
clarissa = CLARISSAPipeline()
print("CLARISSA Conversation Demo")
print("=" * 60)
# Conversation turns
conversation = [
"Create a waterflood model with 20x20x5 grid, porosity 0.22, permeability 150 md",
"The reservoir is at 8500 ft depth with 3800 psi initial pressure",
"Run the simulation"
]
for i, user_msg in enumerate(conversation, 1):
print(f"\n[Turn {i}]")
print(f"User: {user_msg}")
response = clarissa.process_message(user_msg)
print(f"\nCLARISSA: {response}")
print("-" * 60)
Inย [ย ]:
Copied!
# Show generated deck
print("Generated ECLIPSE Deck:")
print("=" * 60)
if clarissa.current_deck:
print(clarissa.current_deck)
else:
print("No deck generated yet")
# Show generated deck
print("Generated ECLIPSE Deck:")
print("=" * 60)
if clarissa.current_deck:
print(clarissa.current_deck)
else:
print("No deck generated yet")
Section 4: Conversation State Trackingยถ
Examine what the pipeline tracked.
Inย [ย ]:
Copied!
print("Pipeline State")
print("=" * 60)
print("\nCurrent Parameters:")
for key, value in clarissa.current_params.items():
print(f" {key}: {value}")
print("\nAssumptions Made:")
for assumption in clarissa.assumptions:
print(f" - {assumption}")
print(f"\nConversation History: {len(clarissa.conversation_history)} messages")
for msg in clarissa.conversation_history:
role = msg['role'].upper()
content = msg['content'][:80] + '...' if len(msg['content']) > 80 else msg['content']
print(f" [{role}] {content}")
print("Pipeline State")
print("=" * 60)
print("\nCurrent Parameters:")
for key, value in clarissa.current_params.items():
print(f" {key}: {value}")
print("\nAssumptions Made:")
for assumption in clarissa.assumptions:
print(f" - {assumption}")
print(f"\nConversation History: {len(clarissa.conversation_history)} messages")
for msg in clarissa.conversation_history:
role = msg['role'].upper()
content = msg['content'][:80] + '...' if len(msg['content']) > 80 else msg['content']
print(f" [{role}] {content}")
Section 5: Saving and Loading Sessionsยถ
Persist conversation state.
Inย [ย ]:
Copied!
import json
from datetime import datetime
def save_session(pipeline: CLARISSAPipeline, filepath: str):
"""Save session state to JSON."""
session = {
'timestamp': datetime.now().isoformat(),
'parameters': pipeline.current_params,
'assumptions': pipeline.assumptions,
'conversation': pipeline.conversation_history,
'deck': pipeline.current_deck
}
with open(filepath, 'w') as f:
json.dump(session, f, indent=2)
print(f"Session saved to {filepath}")
def load_session(filepath: str) -> CLARISSAPipeline:
"""Load session state from JSON."""
with open(filepath) as f:
session = json.load(f)
pipeline = CLARISSAPipeline()
pipeline.current_params = session['parameters']
pipeline.assumptions = session['assumptions']
pipeline.conversation_history = session['conversation']
pipeline.current_deck = session['deck']
print(f"Session loaded from {filepath}")
print(f" Parameters: {len(pipeline.current_params)}")
print(f" Messages: {len(pipeline.conversation_history)}")
return pipeline
# Demo save/load
save_session(clarissa, '/tmp/clarissa_session.json')
loaded = load_session('/tmp/clarissa_session.json')
import json
from datetime import datetime
def save_session(pipeline: CLARISSAPipeline, filepath: str):
"""Save session state to JSON."""
session = {
'timestamp': datetime.now().isoformat(),
'parameters': pipeline.current_params,
'assumptions': pipeline.assumptions,
'conversation': pipeline.conversation_history,
'deck': pipeline.current_deck
}
with open(filepath, 'w') as f:
json.dump(session, f, indent=2)
print(f"Session saved to {filepath}")
def load_session(filepath: str) -> CLARISSAPipeline:
"""Load session state from JSON."""
with open(filepath) as f:
session = json.load(f)
pipeline = CLARISSAPipeline()
pipeline.current_params = session['parameters']
pipeline.assumptions = session['assumptions']
pipeline.conversation_history = session['conversation']
pipeline.current_deck = session['deck']
print(f"Session loaded from {filepath}")
print(f" Parameters: {len(pipeline.current_params)}")
print(f" Messages: {len(pipeline.conversation_history)}")
return pipeline
# Demo save/load
save_session(clarissa, '/tmp/clarissa_session.json')
loaded = load_session('/tmp/clarissa_session.json')
Summaryยถ
In this tutorial, we saw:
- All Components Together: NL Parser, Constraints, Generator, Execution
- Conversation Flow: Multi-turn dialogue with state tracking
- Deck Generation: Complete ECLIPSE deck from natural language
- Assumptions: Explicit documentation of defaults used
- Session Management: Save and load conversation state
Key Insight: The pipeline orchestrates specialized components, each handling a specific aspect of the task.
Next Tutorial: 10_API_Reference.ipynb - REST API documentation