Skip to content

Commit 6975b42

Browse files
authored
Merge pull request #2 from langstruct-ai/rework_optimizer
Rework optimizers
2 parents 83342f5 + 3755204 commit 6975b42

20 files changed

Lines changed: 279 additions & 181 deletions

‎README.md‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,7 @@ Once you've got the basics working, there's more:
205205
```python
206206
extractor.optimize(
207207
texts=your_examples,
208-
expected_results=expected_outputs,
209-
num_trials=50
208+
expected_results=expected_outputs
210209
)
211210
```
212211

‎docs/src/content/docs/examples/legal-contracts.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ Create an extractor for legal document analysis:
6666
extractor = LangStruct(
6767
schema=LegalContractSchema,
6868
model="gemini/gemini-2.5-flash-lite", # Fast and reliable for legal analysis
69-
optimize=True,
7069
use_sources=True, # Critical for legal document traceability
7170
temperature=0.1, # Lower temperature for consistency
7271
max_retries=3 # Ensure reliability
7372
)
73+
# Later: extractor.optimize(training_texts, expected_results)
7474

7575
# Example contract text
7676
contract_text = """

‎docs/src/content/docs/examples/scientific-papers.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ Create an extractor for research paper analysis:
7676
extractor = LangStruct(
7777
schema=ScientificPaperSchema,
7878
model="gemini/gemini-2.5-flash-lite", # Fast and reliable for academic content
79-
optimize=True,
8079
use_sources=True, # Track where information was found
8180
temperature=0.2, # Slightly higher for nuanced interpretation
8281
max_retries=3
8382
)
83+
# Later: extractor.optimize(training_texts, expected_results)
8484

8585
# Example research paper text (excerpt)
8686
paper_text = """

‎docs/src/content/docs/optimization.mdx‎

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,28 @@ Make your extraction more accurate with automatic optimization. LangStruct learn
99

1010
## The Easy Way
1111

12-
**Enable optimization (configure optimizer) and then optimize with your data:**
12+
**Create an extractor (optionally choose the optimizer) and call `optimize()` when you're ready:**
1313

1414
```python
1515
from langstruct import LangStruct
1616

17-
# Create extractor with optimization enabled
1817
extractor = LangStruct(
1918
example={
2019
"name": "Dr. Sarah Johnson",
2120
"age": 34,
2221
"occupation": "data scientist"
2322
},
24-
optimize=True # sets up optimizer; run .optimize(...) to train
23+
optimizer="miprov2", # default optimizer
2524
)
25+
26+
# Later, once you have training data:
27+
# extractor.optimize(texts=training_texts, expected_results=good_results)
2628
```
2729

28-
**Default behavior (faster startup, good baseline accuracy):**
30+
**Quick experiments (skip optimization entirely):**
2931

3032
```python
31-
# No optimization - good for quick experiments
3233
extractor = LangStruct(example={"name": "John", "age": 30})
33-
# optimize=False by default - enables faster startup
3434
```
3535

3636
## When You Have Training Data
@@ -74,8 +74,19 @@ Optimization can significantly improve accuracy on real-world tasks:
7474

7575
## Persisting Results
7676

77-
Saving/loading an optimized extractor is not yet implemented.
78-
For now, re-run `optimize()` when you start up, or persist your training data and configuration.
77+
Save and load optimized extractors to reuse them without re-running optimization:
78+
79+
```python
80+
# Save after optimization
81+
extractor.save("./my_extractor")
82+
83+
# Load later
84+
from langstruct import LangStruct
85+
loaded = LangStruct.load("./my_extractor")
86+
87+
# Use immediately - optimization is preserved
88+
result = loaded.extract("new text")
89+
```
7990

8091
## Advanced (If You Need It)
8192

@@ -86,7 +97,6 @@ Most users don't need this, but if you want more control:
8697
extractor.optimize(
8798
texts=training_texts,
8899
expected_results=good_results,
89-
num_trials=50, # More trials = better results (takes longer)
90100
validation_split=0.3 # Use 30% for testing improvements
91101
)
92102
```
@@ -110,26 +120,26 @@ extractor.optimize(
110120

111121
## Common Questions
112122

113-
**Q: Do I always need training data?**
114-
A: No! Optimization can work without training data, but providing examples improves results significantly.
123+
**Q: Do I always need training data?**
124+
A: You need example texts, but not necessarily expected outputs. If you don't provide `expected_results`, LangStruct uses the LLM's confidence ratings to optimize. Providing expected outputs significantly improves accuracy.
115125

116-
**Q: How long does optimization take?**
126+
**Q: How long does optimization take?**
117127
A: Usually 1-5 minutes for typical datasets (10-100 examples).
118128

119-
**Q: Can I optimize an already optimized extractor?**
120-
A: Yes! You can keep optimizing with new data as you get it.
129+
**Q: Can I optimize an already optimized extractor?**
130+
A: Yes, you can continue optimizing with new data as you collect it.
121131

122-
**Q: Will this make my extractions slower?**
123-
A: No - optimization happens once during training. Production extraction speed is the same.
132+
**Q: Will this make my extractions slower?**
133+
A: No - optimization happens once during training. Production extraction speed is unchanged.
124134

125-
**Q: What happens when I switch models?**
126-
A: Just change the model and re-optimize! Same training data, same accuracy - zero prompt rewriting needed.
135+
**Q: What happens when I switch models?**
136+
A: Change the model and re-optimize with the same training data. No prompt rewriting needed.
127137

128138
## Next Steps
129139

130140
<CardGrid>
131141
<Card title="Try It Now" icon="laptop">
132-
Create a LangStruct extractor and enable optimization when you need accuracy!
142+
Create a LangStruct extractor and enable optimization when you need accuracy.
133143
</Card>
134144
<Card title="Source Grounding" icon="document">
135145
[Track where information comes from](/source-grounding/)

‎docs/src/content/docs/persistence.mdx‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,9 @@ print(result.entities)
4646
```python
4747
from langstruct import LangStruct
4848

49-
# Create extractor with optimization
49+
# Create extractor
5050
extractor = LangStruct(
5151
example={"name": "John", "age": 30, "role": "engineer"},
52-
optimize=True
5352
)
5453

5554
# Train the extractor
@@ -58,8 +57,7 @@ expected_results = [{"name": "Expected outputs..."}]
5857

5958
extractor.optimize(
6059
texts=training_texts,
61-
expected_results=expected_results,
62-
num_trials=50
60+
expected_results=expected_results
6361
)
6462

6563
# Save optimized state
@@ -215,7 +213,7 @@ Common error scenarios:
215213

216214
```python
217215
# Development: Train and save
218-
extractor = LangStruct(schema=MySchema, optimize=True)
216+
extractor = LangStruct(schema=MySchema)
219217
extractor.optimize(training_data, expected_results)
220218
extractor.save("./production_extractor")
221219

‎docs/src/content/docs/query-parsing.mdx‎

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,22 @@ This single query contains **three distinct types of information**:
4444
- Quarter: Q3 2024 (exact match)
4545
- Revenue: > $100B (numeric comparison)
4646
- Sector: Technology (category match)
47-
47+
4848
These need **database-style filtering**, not semantic search
4949
</Card>
5050
<Card title="Semantic Content" icon="magnifier">
5151
**Conceptual topics for similarity search:**
5252
- "financial reports" (could be 10-K, earnings, statements)
5353
- "AI investments" (could be ML, artificial intelligence, neural networks)
54-
54+
5555
These need **embedding-based semantic search**
5656
</Card>
5757
<Card title="Implicit Context" icon="information">
5858
**Assumed context from natural language:**
5959
- "Show me" implies retrieval intent
6060
- "companies" implies corporate entities
6161
- Plural suggests multiple results expected
62-
62+
6363
These provide **query understanding context**
6464
</Card>
6565
</CardGrid>
@@ -86,30 +86,30 @@ results = vector_db.similarity_search(query_embedding)
8686
<Tabs>
8787
<TabItem label="Semantic Terms">
8888
**What they are:** Conceptual topics that benefit from semantic understanding
89-
89+
9090
**Examples:**
9191
- "artificial intelligence" ≈ "AI" ≈ "machine learning"
9292
- "financial performance" ≈ "earnings" ≈ "fiscal results"
9393
- "customer satisfaction" ≈ "user happiness" ≈ "client feedback"
94-
94+
9595
**How they work:** Converted to embeddings for similarity matching
96-
96+
9797
**Best for:**
9898
- Finding conceptually related content
9999
- Handling synonyms and variations
100100
- Discovering relevant but not exact matches
101101
</TabItem>
102102
<TabItem label="Structured Filters">
103103
**What they are:** Exact constraints that must be precisely matched
104-
104+
105105
**Examples:**
106106
- Date/Time: "Q3 2024", "after 2023", "last 30 days"
107107
- Numbers: "revenue > $100M", "5-10 employees", "top 3"
108108
- Categories: "tech sector", "approved status", "high priority"
109109
- Entities: "Apple Inc.", "California", "John Smith"
110-
110+
111111
**How they work:** Converted to database-style filter operations
112-
112+
113113
**Best for:**
114114
- Enforcing hard constraints
115115
- Filtering by exact values
@@ -129,7 +129,7 @@ Let's see how different queries naturally decompose:
129129
- **Structured filters:** `{"quarter": "Q3 2024", "sector": "Technology", "profitable": true}`
130130
- **Why it matters:** You want companies that ARE profitable (filter), not just ones that DISCUSS profitability
131131

132-
#### Healthcare Query
132+
#### Healthcare Query
133133
> "Patient records over 65 years old with diabetes showing improvement"
134134
135135
- **Semantic terms:** `["showing improvement", "better outcomes"]`
@@ -216,7 +216,7 @@ print("📖 Explanation:", result.explanation)
216216
'revenue': {'$gte': 100.0}
217217
}
218218
💯 Confidence: 91.5%
219-
📖 Explanation:
219+
📖 Explanation:
220220
Searching for: tech companies
221221
With filters:
222222
• quarter = Q3 2024
@@ -270,30 +270,30 @@ class EnhancedRAGSystem:
270270
# Same schema for both extraction and parsing!
271271
self.langstruct = LangStruct(example=schema_example)
272272
self.vectorstore = Chroma(embedding_function=OpenAIEmbeddings())
273-
273+
274274
def index_document(self, text: str):
275275
"""Extract metadata and index document"""
276276
# Extract structured metadata
277277
extraction = self.langstruct.extract(text)
278-
278+
279279
# Index with both text and metadata
280280
self.vectorstore.add_texts(
281281
texts=[text],
282282
metadatas=[extraction.entities]
283283
)
284-
284+
285285
def natural_query(self, query: str, k: int = 5):
286286
"""Query using natural language"""
287287
# Parse query into components
288288
parsed = self.langstruct.query(query)
289-
289+
290290
# Perform hybrid search
291291
results = self.vectorstore.similarity_search(
292292
query=' '.join(parsed.semantic_terms),
293293
k=k,
294294
filter=parsed.structured_filters
295295
)
296-
296+
297297
return results, parsed.explanation
298298

299299
# Usage
@@ -407,13 +407,13 @@ ls = LangStruct(example=your_schema)
407407
# Query with natural language
408408
def smart_search(query: str):
409409
parsed = ls.query(query)
410-
410+
411411
results = collection.query(
412412
query_texts=parsed.semantic_terms,
413413
where=parsed.structured_filters,
414414
n_results=10
415415
)
416-
416+
417417
return results
418418
```
419419

@@ -431,19 +431,19 @@ ls = LangStruct(example=your_schema)
431431
# Natural language query
432432
def pinecone_search(query: str):
433433
parsed = ls.query(query)
434-
434+
435435
# Convert to Pinecone filter format
436436
pinecone_filter = {
437-
f"metadata.{k}": v
437+
f"metadata.{k}": v
438438
for k, v in parsed.structured_filters.items()
439439
}
440-
440+
441441
results = index.query(
442442
vector=embed(parsed.semantic_terms),
443443
filter=pinecone_filter,
444444
top_k=10
445445
)
446-
446+
447447
return results
448448
```
449449

@@ -497,9 +497,8 @@ domain_ls = LangStruct(
497497
# Include synonyms in descriptions
498498
"earnings": 10.5, # Also covers "profits", "income"
499499
},
500-
# Can optimize for better accuracy
501-
optimize=True
502500
)
501+
# Call domain_ls.optimize(...) with training examples when ready
503502
```
504503

505504
## Performance Considerations
@@ -512,7 +511,7 @@ from functools import lru_cache
512511
class CachedLangStruct:
513512
def __init__(self, schema):
514513
self.ls = LangStruct(example=schema)
515-
514+
516515
@lru_cache(maxsize=1000)
517516
def query_cached(self, query: str):
518517
"""Cache frequently used queries"""

‎docs/src/content/docs/quickstart.mdx‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,8 @@ extractor = LangStruct(example=schema)
8787
# See optimization in action
8888
extractor.optimize(
8989
texts=["training texts..."],
90-
expected=[{"expected outputs..."}],
91-
num_trials=50 # More trials = better accuracy
90+
expected_results=[{"expected outputs..."}] # Optional - uses confidence if omitted
9291
)
93-
print(f"Optimized accuracy: {extractor.score:.1%}")
9492
```
9593

9694
## Process Multiple Documents (with quotas)

‎docs/src/content/docs/why-dspy.mdx‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ extractor = LangStruct(example={
119119

120120
# 2. Let MIPROv2 optimize prompts and examples automatically
121121
extractor.optimize(
122-
training_texts=["Apple reported $125B in Q3...", "Meta earned $40B..."],
122+
texts=["Apple reported $125B in Q3...", "Meta earned $40B..."],
123123
expected_results=[
124124
{"company": "Apple", "revenue": 125.0, "quarter": "Q3"},
125125
{"company": "Meta", "revenue": 40.0, "quarter": "Q3"}
@@ -147,17 +147,16 @@ result = extractor.extract("Microsoft announced $65B revenue for Q4")
147147
extractor = LangStruct(
148148
example={"company": "Apple", "revenue": 100.0},
149149
model="gpt-5-mini",
150-
optimize=True
151150
)
152-
extractor.optimize(training_texts, expected_results)
151+
extractor.optimize(texts=training_texts, expected_results=expected_results)
153152

154153
# 6 months later, switch to Claude - just two lines!
155154
extractor.model = "claude-3-7-sonnet-latest"
156-
extractor.optimize(training_texts, expected_results) # Auto-reoptimizes prompts
155+
extractor.optimize(texts=training_texts, expected_results=expected_results) # Auto-reoptimizes prompts
157156

158157
# Or use local models for privacy
159158
extractor.model = "ollama/llama3.2"
160-
extractor.optimize(training_texts, expected_results) # Works the same way
159+
extractor.optimize(texts=training_texts, expected_results=expected_results) # Works the same way
161160

162161
# Same accuracy, zero prompt rewriting, zero vendor lock-in
163162
```

0 commit comments

Comments
 (0)