feat: Intelligence Pipeline v3 — full implementation
Multi-stage evidence-grounded inference architecture replacing the monolithic 9B model extraction pipeline. CPU-first specialist services handle routine extraction while the 9B vLLM model is preserved for semantic adjudication of ambiguous cases. Key components: - Capability-aware inference gateway (OpenAI-compatible + Ollama) - Endpoint registry with DB migrations and REST API - Sentence-aware document segmenter (property tests) - Deterministic financial parsing with offset integrity - Symbol resolution with ambiguity detection - Specialist service (GLiNER2, dynamic batching, K8s deployment) - Company-specific sentiment (FinBERT, calibration) - Retrieval-based novelty and duplicate detection - Confidence calibration pipeline - Deterministic routing engine (property tests) - 9B adjudication layer with VRAM gating - Stock-specific impact model (features, labels, baseline, trained) - Pipeline orchestrator (state machine, queues, leases, feature flags) - Bounded parallelism (async workers, semaphore, load shedding) - Observability (tracing, metrics, alerts) - Compatibility adapter (v3→v2 golden mapping tests) - Shadow/canary promotion framework - Active learning and fine-tuning pipeline Test results: 1,161 tests pass, ruff lint clean. All 282 spec tasks completed.
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
"""Seed migration helpers for the inference registry.
|
||||
|
||||
Provides canonical endpoint profiles, model deployments, and agent
|
||||
provider-to-stage-binding conversion logic for migrating from the legacy
|
||||
model_provider/model_name fields to the v3 registry.
|
||||
|
||||
Task 18.1-18.5: Migrate existing provider records.
|
||||
Requirements: 3.8, 3.9
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
# ─── Well-known IDs ────────────────────────────────────────────────────────────
|
||||
# These match the SQL seed migration (042_seed_inference_registry.sql)
|
||||
|
||||
OLLAMA_ENDPOINT_ID = UUID("a0000000-0000-4000-8000-000000000001")
|
||||
VLLM_ENDPOINT_ID = UUID("a0000000-0000-4000-8000-000000000002")
|
||||
OLLAMA_DEPLOYMENT_ID = UUID("b0000000-0000-4000-8000-000000000001")
|
||||
VLLM_DEPLOYMENT_ID = UUID("b0000000-0000-4000-8000-000000000002")
|
||||
|
||||
|
||||
def get_initial_endpoints() -> list[dict[str, Any]]:
|
||||
"""Return the canonical endpoint profiles for the seed migration.
|
||||
|
||||
Returns:
|
||||
List of endpoint dicts matching the inference_endpoints table schema.
|
||||
- stonks-ollama: Ollama native protocol at cluster-internal URL.
|
||||
- stonks-vllm: OpenAI-chat protocol (vLLM) at cluster-internal URL.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"id": OLLAMA_ENDPOINT_ID,
|
||||
"name": "stonks-ollama",
|
||||
"protocol": "ollama_native",
|
||||
"base_url": "http://ollama.ollama-service.svc.cluster.local:11434",
|
||||
"auth_secret_ref": None,
|
||||
"auth_scheme": "none",
|
||||
"default_headers": {},
|
||||
"health_path": "/api/tags",
|
||||
"enabled": True,
|
||||
},
|
||||
{
|
||||
"id": VLLM_ENDPOINT_ID,
|
||||
"name": "stonks-vllm",
|
||||
"protocol": "openai_chat",
|
||||
"base_url": "http://kube-vllm.stonks-oracle.svc.cluster.local:8000",
|
||||
"auth_secret_ref": None,
|
||||
"auth_scheme": "none",
|
||||
"default_headers": {},
|
||||
"health_path": "/health",
|
||||
"enabled": True,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_initial_deployments() -> list[dict[str, Any]]:
|
||||
"""Return the initial model deployments for the seed migration.
|
||||
|
||||
Returns:
|
||||
List of deployment dicts matching the model_deployments table schema.
|
||||
- Ollama: qwen3.5:9b with native JSON mode.
|
||||
- vLLM: AxionML/Qwen3.5-9B-NVFP4 on RTX 4070 Ti SUPER, strict JSON Schema.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"id": OLLAMA_DEPLOYMENT_ID,
|
||||
"endpoint_id": OLLAMA_ENDPOINT_ID,
|
||||
"served_model_name": "qwen3.5:9b",
|
||||
"display_name": "Qwen 3.5 9B (Ollama)",
|
||||
"capabilities": {
|
||||
"chat_completions": True,
|
||||
"json_schema": False,
|
||||
"json_object": True,
|
||||
"seed": False,
|
||||
"usage": False,
|
||||
"max_completion_tokens": False,
|
||||
"model_listing": True,
|
||||
},
|
||||
"context_window": 32768,
|
||||
"max_output_tokens": 32768,
|
||||
"quantization": None,
|
||||
"runtime_metadata": {
|
||||
"source": "ollama_native",
|
||||
"notes": "Ollama-served model with native JSON mode",
|
||||
},
|
||||
"enabled": True,
|
||||
},
|
||||
{
|
||||
"id": VLLM_DEPLOYMENT_ID,
|
||||
"endpoint_id": VLLM_ENDPOINT_ID,
|
||||
"served_model_name": "AxionML/Qwen3.5-9B-NVFP4",
|
||||
"display_name": "Qwen 3.5 9B NVFP4 (vLLM)",
|
||||
"capabilities": {
|
||||
"chat_completions": True,
|
||||
"json_schema": True,
|
||||
"json_object": True,
|
||||
"seed": True,
|
||||
"usage": True,
|
||||
"max_completion_tokens": True,
|
||||
"model_listing": True,
|
||||
},
|
||||
"context_window": 8192,
|
||||
"max_output_tokens": 2048,
|
||||
"quantization": "NVFP4",
|
||||
"runtime_metadata": {
|
||||
"gpu": "RTX 4070 Ti SUPER",
|
||||
"gpu_memory_utilization": 0.80,
|
||||
"max_num_seqs": 8,
|
||||
"vllm_structured_outputs": True,
|
||||
},
|
||||
"enabled": True,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ─── Provider mapping ──────────────────────────────────────────────────────────
|
||||
# Maps legacy model_provider values to endpoint/deployment IDs.
|
||||
|
||||
_PROVIDER_TO_ENDPOINT: dict[str, UUID] = {
|
||||
"ollama": OLLAMA_ENDPOINT_ID,
|
||||
"vllm": VLLM_ENDPOINT_ID,
|
||||
}
|
||||
|
||||
_PROVIDER_TO_DEPLOYMENT: dict[str, UUID] = {
|
||||
"ollama": OLLAMA_DEPLOYMENT_ID,
|
||||
"vllm": VLLM_DEPLOYMENT_ID,
|
||||
}
|
||||
|
||||
|
||||
class UnknownProviderError(ValueError):
|
||||
"""Raised when an agent record has an unrecognized model_provider value."""
|
||||
|
||||
def __init__(self, provider: str, agent_id: str) -> None:
|
||||
self.provider = provider
|
||||
self.agent_id = agent_id
|
||||
super().__init__(
|
||||
f"Unknown model_provider '{provider}' for agent '{agent_id}'. "
|
||||
f"Supported providers: {sorted(_PROVIDER_TO_ENDPOINT.keys())}. "
|
||||
f"Cannot silently convert unknown providers."
|
||||
)
|
||||
|
||||
|
||||
def convert_agent_providers(agent_records: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""Convert existing agent model_provider/model_name fields to stage bindings.
|
||||
|
||||
For each agent record with a known model_provider (ollama or vllm), produces
|
||||
a stage binding record that maps the agent to the appropriate endpoint and
|
||||
model deployment. The original model_provider and model_name fields are
|
||||
retained for backward compatibility — this function supplements, not replaces.
|
||||
|
||||
Args:
|
||||
agent_records: List of dicts with at least 'id' (or 'agent_id'),
|
||||
'model_provider', and optionally 'slug' fields.
|
||||
|
||||
Returns:
|
||||
List of stage binding dicts suitable for insertion into
|
||||
agent_stage_bindings.
|
||||
|
||||
Raises:
|
||||
UnknownProviderError: If a record has a model_provider that cannot be
|
||||
mapped. Unknown providers must NOT be silently converted.
|
||||
"""
|
||||
bindings: list[dict[str, Any]] = []
|
||||
|
||||
for record in agent_records:
|
||||
agent_id = str(record.get("id") or record.get("agent_id", ""))
|
||||
provider = (record.get("model_provider") or "").strip().lower()
|
||||
|
||||
if not provider:
|
||||
# No provider set — skip, nothing to convert
|
||||
continue
|
||||
|
||||
if provider not in _PROVIDER_TO_ENDPOINT:
|
||||
raise UnknownProviderError(provider=provider, agent_id=agent_id)
|
||||
|
||||
endpoint_id = _PROVIDER_TO_ENDPOINT[provider]
|
||||
deployment_id = _PROVIDER_TO_DEPLOYMENT[provider]
|
||||
|
||||
# Determine stage from agent slug or default to 'extraction'
|
||||
slug = record.get("slug", "")
|
||||
stage = _infer_stage_from_slug(slug)
|
||||
|
||||
bindings.append({
|
||||
"agent_id": agent_id,
|
||||
"stage": stage,
|
||||
"endpoint_id": endpoint_id,
|
||||
"model_deployment_id": str(deployment_id),
|
||||
"route_order": 0,
|
||||
"routing_config": {},
|
||||
"is_active": True,
|
||||
})
|
||||
|
||||
return bindings
|
||||
|
||||
|
||||
def _infer_stage_from_slug(slug: str) -> str:
|
||||
"""Map an agent slug to a pipeline stage name.
|
||||
|
||||
Known agent slugs and their corresponding stages:
|
||||
- document-extractor -> extraction
|
||||
- event-classifier -> classification
|
||||
- thesis-rewriter -> thesis_rewrite
|
||||
- report-summarizer -> summarization
|
||||
|
||||
Falls back to 'extraction' for unrecognized slugs.
|
||||
"""
|
||||
slug_to_stage: dict[str, str] = {
|
||||
"document-extractor": "extraction",
|
||||
"event-classifier": "classification",
|
||||
"thesis-rewriter": "thesis_rewrite",
|
||||
"report-summarizer": "summarization",
|
||||
}
|
||||
return slug_to_stage.get(slug, "extraction")
|
||||
|
||||
|
||||
# ─── Conflicting defaults identification ──────────────────────────────────────
|
||||
|
||||
# Known locations where model/provider defaults have historically conflicted.
|
||||
_KNOWN_CONFLICT_LOCATIONS: list[dict[str, str]] = [
|
||||
{
|
||||
"location": "services/shared/config.py",
|
||||
"field": "VLLMConfig.model",
|
||||
"description": "Python config default for vLLM model name",
|
||||
},
|
||||
{
|
||||
"location": "services/shared/config.py",
|
||||
"field": "VLLMConfig.base_url",
|
||||
"description": "Python config default for vLLM base URL (192.168.42.254:8000)",
|
||||
},
|
||||
{
|
||||
"location": "infra/migrations/026_ai_agents.sql",
|
||||
"field": "model_provider/model_name DEFAULT",
|
||||
"description": "Agent table DDL defaults to 'ollama'/'qwen3.5:9b-fast'",
|
||||
},
|
||||
{
|
||||
"location": "infra/migrations/031_fix_agent_defaults.sql",
|
||||
"field": "model_provider UPDATE",
|
||||
"description": "Migration updates agents to 'vllm'/'AxionML/Qwen3.5-9B-NVFP4'",
|
||||
},
|
||||
{
|
||||
"location": "infra/migrations/033_stop_hardcoding_agent_model.sql",
|
||||
"field": "model_provider UPDATE",
|
||||
"description": "Migration updates agents to 'vllm'/'AxionML/Qwen3.5-9B-NVFP4'",
|
||||
},
|
||||
{
|
||||
"location": "infra/helm/stonks-oracle/values.yaml",
|
||||
"field": "VLLM_BASE_URL / VLLM_MODEL",
|
||||
"description": "Helm values point to nuextract-external.vllm-service with NuExtract3",
|
||||
},
|
||||
{
|
||||
"location": "infra/helm/stonks-oracle/values.yaml",
|
||||
"field": "OLLAMA_BASE_URL / OLLAMA_MODEL",
|
||||
"description": "Helm values point to nuextract-external.vllm-service with NuExtract3",
|
||||
},
|
||||
{
|
||||
"location": "infra/kube-vllm/deployment.yaml",
|
||||
"field": "vLLM deployment model arg",
|
||||
"description": "Standalone kube-vllm deployment with AxionML/Qwen3.5-9B-NVFP4",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def identify_conflicting_defaults() -> list[str]:
|
||||
"""List locations where model defaults historically conflict.
|
||||
|
||||
Returns a list of human-readable strings identifying places where
|
||||
the model provider, model name, or base URL have conflicting
|
||||
defaults across config.py, migrations, Helm values, and the
|
||||
kube-vllm deployment.
|
||||
|
||||
These conflicts should be resolved after the inference registry
|
||||
is established as the single source of truth.
|
||||
|
||||
Returns:
|
||||
List of conflict description strings.
|
||||
"""
|
||||
conflicts: list[str] = []
|
||||
|
||||
for entry in _KNOWN_CONFLICT_LOCATIONS:
|
||||
conflicts.append(
|
||||
f"{entry['location']} [{entry['field']}]: {entry['description']}"
|
||||
)
|
||||
|
||||
return conflicts
|
||||
Reference in New Issue
Block a user