TradeFlowAI / src /ai /mock_llm.py
muhammadghiffari's picture
Deploy TradeFlow API to HF
db4ba8d
Raw
History Blame Contribute Delete
2.34 kB
"""
Deterministic LLM stub for deterministic E2E and tests.
Returns predictable CEISAFields outputs so the LangGraph flow is repeatable.
"""
from __future__ import annotations
from typing import Any
from pydantic import BaseModel
class DeterministicStructuredLLM:
def __init__(self, model_schema: type[BaseModel]):
self._schema = model_schema
async def ainvoke(self, messages: Any):
# Return a deterministic instance matching the pydantic output schema
# Use simple fixed safe defaults; tests relying on presence of fields
# can assert these exact values for determinism.
data = {}
# Pydantic v2 uses `model_fields`; v1 uses `__fields__` with different metadata
schema_fields = getattr(self._schema, 'model_fields', None) or getattr(self._schema, '__fields__', {})
for k, meta in schema_fields.items():
# Determine annotation/type across pydantic versions
if isinstance(meta, dict):
ftype = meta.get('annotation')
elif hasattr(meta, 'annotation'):
ftype = meta.annotation
elif hasattr(meta, 'outer_type_'):
ftype = meta.outer_type_
else:
ftype = None
# Provide reasonable deterministic defaults by common types
if ftype is str or getattr(ftype, '__name__', '') == 'str':
data[k] = f"det-{k}"
elif ftype is int or getattr(ftype, '__name__', '') == 'int':
data[k] = 1
elif ftype is float or getattr(ftype, '__name__', '') == 'float':
data[k] = 1.0
else:
data[k] = None
# Create a pydantic model instance if possible
try:
return self._schema.model_validate(data) if hasattr(self._schema, 'model_validate') else self._schema(**data)
except Exception:
# Last resort: return raw dict
return data
class DeterministicLLM:
def __init__(self, *_, **__):
pass
def with_structured_output(self, schema: type[BaseModel]):
return DeterministicStructuredLLM(schema)
# synchronous convenience factory
def create_deterministic_llm(*args, **kwargs):
return DeterministicLLM()