Skip to content

Commit 4af0d74

Browse files
author
Arena AI Agent
committed
feat: 🚀 upgrade architecture to SOTA (Pydantic models + Instructor strict JSON schema)
1 parent 616e302 commit 4af0d74

3 files changed

Lines changed: 52 additions & 68 deletions

File tree

‎epistemic_forge/llm.py‎

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
1-
"""Robust LLM Wrapper with Retry Logic and Logging."""
2-
from litellm import completion
1+
"""SOTA LLM Engine using Instructor and Pydantic for Strict Structured Outputs."""
2+
import instructor
3+
from openai import OpenAI
4+
from pydantic import BaseModel
35
from loguru import logger
46
from tenacity import retry, stop_after_attempt, wait_exponential
57

8+
try:
9+
client = instructor.from_openai(OpenAI())
10+
except Exception as e:
11+
logger.warning(f"OpenAI client init failed (missing key?): {e}")
12+
client = None
13+
614
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
7-
def call_llm(messages: list, model: str = "gpt-4o-mini", temperature: float = 0.7) -> str:
8-
"""Call an LLM with automatic retries, fallback, and tracking."""
15+
def generate_structured(
16+
messages: list,
17+
response_model: type[BaseModel],
18+
model: str = "gpt-4o-2024-08-06",
19+
temperature: float = 0.0
20+
) -> BaseModel:
21+
"""
22+
State-of-the-Art Structured Extraction.
23+
Guarantees the output strictly matches the Pydantic schema using JSON Mode / Tool Calls.
24+
"""
25+
if not client:
26+
raise ValueError("LLM Client is not initialized. Please set OPENAI_API_KEY.")
27+
928
try:
10-
logger.debug(f"Initiating neural call to {model}...")
11-
response = completion(model=model, messages=messages, temperature=temperature)
12-
return response.choices[0].message.content
29+
logger.debug(f"Initiating strict structured call to {model} for schema [{response_model.__name__}]...")
30+
response = client.chat.completions.create(
31+
model=model,
32+
messages=messages,
33+
response_model=response_model,
34+
temperature=temperature,
35+
)
36+
return response
1337
except Exception as e:
14-
logger.error(f"LLM API Critical Failure: {str(e)}")
38+
logger.error(f"SOTA LLM API Critical Failure: {str(e)}")
1539
raise

‎epistemic_forge/models.py‎

Lines changed: 19 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from dataclasses import asdict, dataclass, field
5+
from pydantic import BaseModel, Field
66
from enum import Enum
77
from typing import Any, Dict, List, Optional
88

@@ -23,47 +23,36 @@ class Confidence(str, Enum):
2323
WEAK = "weak"
2424

2525

26-
@dataclass
27-
class ProjectSpec:
26+
class ProjectSpec(BaseModel):
2827
"""User-facing project request."""
2928

3029
title: str
3130
question: str
3231
domain: Domain = Domain.HYBRID
3332
audience: str = "technical peer / client"
34-
constraints: List[str] = field(default_factory=list)
35-
keywords: List[str] = field(default_factory=list)
33+
constraints: List[str] = Field(default_factory=list)
34+
keywords: List[str] = Field(default_factory=list)
3635
budget_tokens: int = 8000
3736
max_trials: int = 3
3837
enable_opro_style: bool = True
3938
enable_skills: bool = True
4039

41-
def to_dict(self) -> Dict[str, Any]:
42-
d = asdict(self)
43-
d["domain"] = self.domain.value
44-
return d
4540

4641

47-
@dataclass
48-
class Claim:
42+
class Claim(BaseModel):
4943
"""Atomic epistemic unit."""
5044

5145
id: str
5246
text: str
53-
support: List[str] = field(default_factory=list)
54-
objections: List[str] = field(default_factory=list)
47+
support: List[str] = Field(default_factory=list)
48+
objections: List[str] = Field(default_factory=list)
5549
confidence: Confidence = Confidence.LIKELY
56-
sources: List[str] = field(default_factory=list)
57-
tags: List[str] = field(default_factory=list)
50+
sources: List[str] = Field(default_factory=list)
51+
tags: List[str] = Field(default_factory=list)
5852

59-
def to_dict(self) -> Dict[str, Any]:
60-
d = asdict(self)
61-
d["confidence"] = self.confidence.value
62-
return d
6353

6454

65-
@dataclass
66-
class RouteDecision:
55+
class RouteDecision(BaseModel):
6756
"""L0 router output."""
6857

6958
families: List[str]
@@ -72,64 +61,49 @@ class RouteDecision:
7261
l1_mode: str = "ape" # ape | opro | cascade | off
7362
l3_mode: str = "tot" # tot | lats | cascade | off
7463

75-
def to_dict(self) -> Dict[str, Any]:
76-
return asdict(self)
7764

7865

79-
@dataclass
80-
class SearchNode:
66+
class SearchNode(BaseModel):
8167
"""L3 deliberate-search node."""
8268

8369
id: str
8470
thought: str
8571
value: float
8672
parent_id: Optional[str] = None
87-
children: List[str] = field(default_factory=list)
88-
meta: Dict[str, Any] = field(default_factory=dict)
73+
children: List[str] = Field(default_factory=list)
74+
meta: Dict[str, Any] = Field(default_factory=dict)
8975

90-
def to_dict(self) -> Dict[str, Any]:
91-
return asdict(self)
9276

9377

94-
@dataclass
95-
class Reflection:
78+
class Reflection(BaseModel):
9679
"""L5 verbal memory unit (Reflexion-style)."""
9780

9881
trial: int
9982
failure_summary: str
10083
lesson: str
10184
next_action: str
10285

103-
def to_dict(self) -> Dict[str, Any]:
104-
return asdict(self)
10586

10687

107-
@dataclass
108-
class Skill:
88+
class Skill(BaseModel):
10989
"""L5 procedural memory unit (Voyager-style)."""
11090

11191
name: str
11292
description: str
11393
code: str
114-
tags: List[str] = field(default_factory=list)
94+
tags: List[str] = Field(default_factory=list)
11595

116-
def to_dict(self) -> Dict[str, Any]:
117-
return asdict(self)
11896

11997

120-
@dataclass
121-
class StageArtifact:
98+
class StageArtifact(BaseModel):
12299
name: str
123100
content: str
124101
kind: str # markdown | python | json | notebook-md
125102
path_hint: str = ""
126103

127-
def to_dict(self) -> Dict[str, Any]:
128-
return asdict(self)
129104

130105

131-
@dataclass
132-
class ForgeResult:
106+
class ForgeResult(BaseModel):
133107
"""Full pipeline output."""
134108

135109
spec: ProjectSpec
@@ -141,20 +115,6 @@ class ForgeResult:
141115
skills_used: List[str]
142116
artifacts: List[StageArtifact]
143117
peer_review: Dict[str, Any]
144-
trial_log: List[Dict[str, Any]] = field(default_factory=list)
118+
trial_log: List[Dict[str, Any]] = Field(default_factory=list)
145119
final_score: float = 0.0
146120

147-
def to_dict(self) -> Dict[str, Any]:
148-
return {
149-
"spec": self.spec.to_dict(),
150-
"route": self.route.to_dict(),
151-
"instruction": self.instruction,
152-
"claims": [c.to_dict() for c in self.claims],
153-
"search_trace": [n.to_dict() for n in self.search_trace],
154-
"reflections": [r.to_dict() for r in self.reflections],
155-
"skills_used": self.skills_used,
156-
"artifacts": [a.to_dict() for a in self.artifacts],
157-
"peer_review": self.peer_review,
158-
"trial_log": self.trial_log,
159-
"final_score": self.final_score,
160-
}

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ classifiers = [
3131
"Topic :: Scientific/Engineering :: Artificial Intelligence",
3232
"Topic :: Text Processing :: Linguistic",
3333
]
34-
dependencies = ["litellm>=1.0.0", "loguru>=0.7.0", "tenacity>=8.0.0"]
34+
dependencies = ["instructor>=1.3.0", "openai>=1.30.0", "pydantic>=2.0.0", "loguru>=0.7.0", "tenacity>=8.0.0"]
3535

3636
[project.optional-dependencies]
3737
dev = ["pytest>=7.0"]

0 commit comments

Comments
 (0)