feat(fase0): synthetic reference domain (D4) + backend profile skeleton (D2)
Completes Fase 0 (skeleton & decision-lock): - reference_domain.py + data/reference_projects.json: a synthetic "anleggskostnad" portfolio (3 fictional construction-cost projects with cost line items) as the framework's bundled reference input. Plain typed loader (frozen dataclasses); the JSON-Schema data-source *contract* (B5) is deliberately deferred to Fase 2. - backends.py: Profile (azure|local) + ChatBackend Protocol seam + AzureFoundryBackend/LocalBackend stubs + get_backend() selector (fail-fast on unknown profile). Empty skeleton per D2 — create_chat_client raises NotImplementedError until live wiring in Fase 1. Return type is the MAF BaseChatClient (the common base of FoundryChatClient/OpenAIChatClient). Quality gate green: ruff format + check, mypy (src) clean, 12 pytest passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9FyyENxebxVThjrn9et8C
This commit is contained in:
parent
491a746bba
commit
b57aa83a30
5 changed files with 282 additions and 0 deletions
84
src/portfolio_optimiser/backends.py
Normal file
84
src/portfolio_optimiser/backends.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
"""Backend profiles (D2): the seam between the framework and a MAF chat client.
|
||||
|
||||
Per research §14, a MAF agent binds to a *chat client* and the model is a
|
||||
parameter on that client — so model choice is per-client (and later per-agent
|
||||
via a role->deployment model-map, B12). A "backend profile" selects how models
|
||||
are served and produces the corresponding MAF chat client.
|
||||
|
||||
This is an EMPTY skeleton for Fase 0: it defines the profile selector and the
|
||||
seam (`ChatBackend`), with stubs that raise ``NotImplementedError``. Live wiring
|
||||
lands in Fase 1, when the Group Chat maker-checker spike needs real clients.
|
||||
|
||||
Cost-discipline (D6): the ``LOCAL`` profile (an OpenAI-compatible local endpoint
|
||||
such as Ollama/LM Studio, driven by the GA ``agent-framework-openai`` client) is
|
||||
the development default; the ``AZURE`` profile (``FoundryChatClient``) is reserved
|
||||
for targeted, minimal verification against the private Foundry tenant.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
from agent_framework import BaseChatClient
|
||||
|
||||
|
||||
class Profile(str, Enum):
|
||||
"""Model-serving backend profile (D2)."""
|
||||
|
||||
AZURE = "azure" # Foundry / Azure OpenAI — full profile
|
||||
LOCAL = "local" # OpenAI-compatible local endpoint — fallback / dev default
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ChatBackend(Protocol):
|
||||
"""The seam: a backend produces a MAF chat client for a given model.
|
||||
|
||||
Implementations are Fase 0 skeletons — ``create_chat_client`` raises
|
||||
``NotImplementedError`` until wired in Fase 1. ``model`` is the
|
||||
deployment/model name (resolved from the role->deployment model-map during
|
||||
onboarding, B12).
|
||||
"""
|
||||
|
||||
profile: Profile
|
||||
|
||||
def create_chat_client(self, *, model: str) -> BaseChatClient:
|
||||
"""Create a MAF chat client bound to ``model``. Wired in Fase 1."""
|
||||
...
|
||||
|
||||
|
||||
class AzureFoundryBackend:
|
||||
"""AZURE profile: ``FoundryChatClient`` against a Foundry project (U18).
|
||||
|
||||
Skeleton — Foundry wiring (``project_endpoint``, ``credential``) lands in Fase 1.
|
||||
"""
|
||||
|
||||
profile = Profile.AZURE
|
||||
|
||||
def create_chat_client(self, *, model: str) -> BaseChatClient:
|
||||
raise NotImplementedError(
|
||||
"AzureFoundryBackend is a Fase 0 skeleton; Foundry wiring lands in Fase 1."
|
||||
)
|
||||
|
||||
|
||||
class LocalBackend:
|
||||
"""LOCAL profile: ``OpenAIChatClient`` against an OpenAI-compatible local
|
||||
endpoint (Ollama/LM Studio). Development default per cost-discipline (D6).
|
||||
|
||||
Skeleton — local-endpoint wiring (``base_url``, ``model``) lands in Fase 1.
|
||||
"""
|
||||
|
||||
profile = Profile.LOCAL
|
||||
|
||||
def create_chat_client(self, *, model: str) -> BaseChatClient:
|
||||
raise NotImplementedError(
|
||||
"LocalBackend is a Fase 0 skeleton; local-endpoint wiring lands in Fase 1."
|
||||
)
|
||||
|
||||
|
||||
def get_backend(profile: Profile | str) -> ChatBackend:
|
||||
"""Select a backend by profile. Fail-fast (``ValueError``) on unknown profile."""
|
||||
profile = Profile(profile) # validates: ValueError on unknown string
|
||||
if profile is Profile.AZURE:
|
||||
return AzureFoundryBackend()
|
||||
return LocalBackend()
|
||||
Loading…
Add table
Add a link
Reference in a new issue