SPB Git

spb/wp27_uqo

Public
2.5 KB · 66 lines python
1"""The strictly validated decision schema returned by every LLM policy (and the mock)."""2from __future__ import annotations34from typing import Literal56from pydantic import BaseModel, Field, ValidationError, field_validator789class Decision(BaseModel):10    action: Literal["hold", "place", "cancel"]11    side: Literal["buy", "sell"] | None = None12    order_type: Literal["limit", "market"] | None = None13    quantity: int | None = Field(default=None, ge=0)14    limit_price: float | None = Field(default=None, gt=0)      # currency units (e.g. 199.50)15    cancel_order_ids: list[int] = Field(default_factory=list)16    confidence: float | None = Field(default=None, ge=0.0, le=1.0)17    rationale: str = Field(default="", max_length=400)1819    @field_validator("rationale")20    @classmethod21    def _trim(cls, v: str) -> str:22        return v.strip()[:400]232425DECISION_JSON_SCHEMA: dict = {26    "type": "object",27    "properties": {28        "action": {"type": "string", "enum": ["hold", "place", "cancel"]},29        "side": {"type": ["string", "null"], "enum": ["buy", "sell", None]},30        "order_type": {"type": ["string", "null"], "enum": ["limit", "market", None]},31        "quantity": {"type": ["integer", "null"]},32        "limit_price": {"type": ["number", "null"]},33        "cancel_order_ids": {"type": "array", "items": {"type": "integer"}},34        "confidence": {"type": ["number", "null"]},35        "rationale": {"type": "string"},36    },37    "required": ["action", "side", "order_type", "quantity", "limit_price", "cancel_order_ids", "confidence", "rationale"],38    "additionalProperties": False,39}404142def parse_decision(text: str) -> tuple[Decision | None, str]:43    """Parse the model's JSON text; returns (decision, error). Lenient about code fences."""44    import json45    s = text.strip()46    if s.startswith("```"):47        s = s.strip("`")48        if s.lower().startswith("json"):49            s = s[4:]50    try:51        obj = json.loads(s)52    except json.JSONDecodeError as e:53        # try to locate the first {...} block54        i, j = s.find("{"), s.rfind("}")55        if i >= 0 and j > i:56            try:57                obj = json.loads(s[i:j + 1])58            except json.JSONDecodeError:59                return None, f"json: {e.msg}"60        else:61            return None, f"json: {e.msg}"62    try:63        return Decision.model_validate(obj), ""64    except ValidationError as e:65        return None, "schema: " + "; ".join(f"{'.'.join(map(str, err['loc']))}: {err['msg']}" for err in e.errors()[:3])66
Files · main
Menu

Theme