16 KiB
16 KiB
IMPLEMENTACION - Wrappers y Ejemplos
Version: 2.0
Dependencia: S-CONTRACT.md
Estado: Referencia
1. Introduccion
Este documento proporciona implementaciones de referencia para:
- Construccion de requests S-CONTRACT
- Validacion de responses
- Integracion con Alfred (n8n)
2. ContractBuilder (Python)
2.1 Clase Principal
import uuid
import hashlib
from datetime import datetime, timezone
from typing import Optional, Dict, Any, List
from dataclasses import dataclass, asdict
@dataclass
class ContractBuilder:
"""
Constructor de requests S-CONTRACT v2.0.
"""
contract_version: str = "2.0"
profile: str = "FULL"
# Envelope
trace_id: Optional[str] = None
parent_trace_id: Optional[str] = None
step_id: Optional[str] = None
step_index: int = 1
total_steps: Optional[int] = None
idempotency_key: Optional[str] = None
ttl_ms: int = 30000
provider_timeout_ms: int = 15000
# Routing
module: Optional[str] = None
module_version: Optional[str] = None
provider_preference: Optional[List[str]] = None
fallback_chain: Optional[List[str]] = None
max_fallback_level: int = 2
# Context
lang: str = "es"
mode: str = "strict"
pii_filter: bool = False
bandera_id: Optional[str] = None
player_id: Optional[str] = None
method_hash: Optional[str] = None
human_readable: Optional[str] = None
# Payload
payload_type: str = "text"
encoding: str = "utf-8"
content: Optional[str] = None
content_hash: Optional[str] = None
schema_expected: Optional[str] = None
# Batch
is_batch: bool = False
batch_id: Optional[str] = None
item_index: Optional[int] = None
items_total: int = 1
batch_mode: str = "SEQUENTIAL"
# Storage
input_location: str = "internal"
input_ref: Optional[str] = None
output_location: str = "internal"
output_ref: Optional[str] = None
persist_intermediate: bool = True
retention_days: int = 30
# Security
encryption_profile: str = "NONE"
data_sensitivity: str = "LOW"
key_vault_ref: Optional[str] = None
pii_detected: bool = False
gdpr_relevant: bool = False
def __post_init__(self):
# Auto-generate IDs if not provided
if not self.trace_id:
self.trace_id = str(uuid.uuid4())
if not self.step_id:
self.step_id = str(uuid.uuid4())
def set_content(self, content: str) -> 'ContractBuilder':
"""Sets content and auto-calculates hash."""
self.content = content
self.content_hash = hashlib.sha256(content.encode('utf-8')).hexdigest()
if not self.idempotency_key:
self.idempotency_key = self.content_hash
return self
def for_module(self, module: str, version: str = "1.0") -> 'ContractBuilder':
"""Sets target module."""
self.module = module
self.module_version = version
return self
def with_fallback(self, chain: List[str]) -> 'ContractBuilder':
"""Sets fallback chain."""
self.fallback_chain = chain
return self
def lite(self) -> 'ContractBuilder':
"""Switches to LITE profile."""
self.profile = "LITE"
return self
def build(self) -> Dict[str, Any]:
"""Builds the final request dictionary."""
timestamp = datetime.now(timezone.utc).isoformat()
if self.profile == "LITE":
return self._build_lite(timestamp)
return self._build_full(timestamp)
def _build_lite(self, timestamp: str) -> Dict[str, Any]:
return {
"contract_version": self.contract_version,
"profile": "LITE",
"envelope": {
"trace_id": self.trace_id,
"idempotency_key": self.idempotency_key
},
"routing": {
"module": self.module
},
"context": {
"lang": self.lang,
"mode": self.mode
},
"payload": {
"type": self.payload_type,
"encoding": self.encoding,
"content": self.content
}
}
def _build_full(self, timestamp: str) -> Dict[str, Any]:
return {
"contract_version": self.contract_version,
"profile": "FULL",
"envelope": {
"trace_id": self.trace_id,
"parent_trace_id": self.parent_trace_id,
"step_id": self.step_id,
"step_index": self.step_index,
"total_steps": self.total_steps,
"idempotency_key": self.idempotency_key,
"timestamp_init": timestamp,
"ttl_ms": self.ttl_ms,
"provider_timeout_ms": self.provider_timeout_ms
},
"routing": {
"module": self.module,
"version": self.module_version,
"provider_preference": self.provider_preference,
"fallback_chain": self.fallback_chain,
"max_fallback_level": self.max_fallback_level
},
"context": {
"lang": self.lang,
"mode": self.mode,
"pii_filter": self.pii_filter,
"bandera_id": self.bandera_id,
"player_id": self.player_id,
"method_hash": self.method_hash,
"human_readable": self.human_readable
},
"payload": {
"type": self.payload_type,
"encoding": self.encoding,
"content": self.content,
"content_hash": self.content_hash,
"schema_expected": self.schema_expected
},
"batch": {
"is_batch": self.is_batch,
"batch_id": self.batch_id,
"item_index": self.item_index,
"items_total": self.items_total,
"batch_mode": self.batch_mode
},
"storage": {
"input_location": self.input_location,
"input_ref": self.input_ref,
"output_location": self.output_location,
"output_ref": self.output_ref,
"persist_intermediate": self.persist_intermediate,
"retention_days": self.retention_days
},
"security": {
"encryption_profile": self.encryption_profile,
"data_sensitivity": self.data_sensitivity,
"key_vault_ref": self.key_vault_ref,
"pii_detected": self.pii_detected,
"gdpr_relevant": self.gdpr_relevant
}
}
2.2 Uso
# Request LITE para clasificacion
request = (
ContractBuilder()
.for_module("CLASSIFIER")
.set_content("Factura de Telefonica por 45.99 EUR")
.lite()
.build()
)
# Request FULL con fallback
request = (
ContractBuilder()
.for_module("OCR_CORE", "1.0")
.set_content(base64_image)
.with_fallback(["OCR_LOCAL", "OCR_GROQ", "OCR_OPENAI"])
.build()
)
3. ResponseValidator (Python)
from typing import Dict, Any, List, Optional
from dataclasses import dataclass
@dataclass
class ValidationResult:
valid: bool
errors: List[str]
warnings: List[str]
class ResponseValidator:
"""
Validador de responses S-CONTRACT v2.0.
"""
VALID_STATUS_CODES = {'SUCCESS', 'PARTIAL', 'ERROR', 'TIMEOUT', 'FALLBACK'}
REQUIRED_LITE = {'contract_version', 'profile', 'envelope', 'status', 'result'}
REQUIRED_FULL = REQUIRED_LITE | {'quality', 'metadata', 'storage', 'audit'}
def validate(self, response: Dict[str, Any]) -> ValidationResult:
errors = []
warnings = []
# Contract version
if response.get('contract_version') != '2.0':
warnings.append(f"Contract version mismatch: {response.get('contract_version')}")
# Profile
profile = response.get('profile', 'FULL')
required = self.REQUIRED_LITE if profile == 'LITE' else self.REQUIRED_FULL
# Required fields
for field in required:
if field not in response:
errors.append(f"Missing required field: {field}")
# Status validation
status = response.get('status', {})
if isinstance(status, dict):
code = status.get('code')
if code not in self.VALID_STATUS_CODES:
errors.append(f"Invalid status code: {code}")
else:
errors.append("Status must be an object with 'code' field")
# Envelope validation
envelope = response.get('envelope', {})
if not envelope.get('trace_id'):
errors.append("Missing trace_id in envelope")
if not envelope.get('idempotency_key'):
warnings.append("Missing idempotency_key in envelope")
# Quality validation (FULL profile)
if profile == 'FULL':
quality = response.get('quality', {})
confidence = quality.get('confidence')
if confidence is not None:
if not (0 <= confidence <= 1):
errors.append(f"Confidence out of range: {confidence}")
return ValidationResult(
valid=len(errors) == 0,
errors=errors,
warnings=warnings
)
4. Integracion n8n (Alfred)
4.1 Nodo Function: Build Request
// n8n Function node: Build S-CONTRACT Request
const crypto = require('crypto');
function buildRequest(input, module, profile = 'LITE') {
const content = typeof input === 'string' ? input : JSON.stringify(input);
const contentHash = crypto.createHash('sha256').update(content).digest('hex');
const traceId = $node.data.trace_id || crypto.randomUUID();
if (profile === 'LITE') {
return {
contract_version: '2.0',
profile: 'LITE',
envelope: {
trace_id: traceId,
idempotency_key: contentHash
},
routing: { module },
context: { lang: 'es', mode: 'strict' },
payload: {
type: 'text',
encoding: 'utf-8',
content: content
}
};
}
return {
contract_version: '2.0',
profile: 'FULL',
envelope: {
trace_id: traceId,
step_id: crypto.randomUUID(),
step_index: 1,
idempotency_key: contentHash,
timestamp_init: new Date().toISOString(),
ttl_ms: 30000
},
routing: {
module,
fallback_chain: $node.data.fallback_chain || []
},
context: {
lang: 'es',
mode: 'strict',
player_id: $node.data.player_id,
human_readable: $node.data.description
},
payload: {
type: 'text',
encoding: 'utf-8',
content: content,
content_hash: contentHash
},
batch: { is_batch: false, items_total: 1 },
storage: {
input_location: 'internal',
persist_intermediate: true,
retention_days: 30
},
security: {
encryption_profile: 'NONE',
data_sensitivity: 'LOW'
}
};
}
// Usage
const request = buildRequest(
$input.first().json.text,
'CLASSIFIER',
'LITE'
);
return [{ json: request }];
4.2 Nodo Function: Validate Response
// n8n Function node: Validate S-CONTRACT Response
function validateResponse(response) {
const errors = [];
const warnings = [];
// Status check
const status = response.status?.code;
const validStatus = ['SUCCESS', 'PARTIAL', 'ERROR', 'TIMEOUT', 'FALLBACK'];
if (!validStatus.includes(status)) {
errors.push(`Invalid status: ${status}`);
}
// Envelope check
if (!response.envelope?.trace_id) {
errors.push('Missing trace_id');
}
// Result check
if (status === 'SUCCESS' && !response.result?.data) {
warnings.push('SUCCESS status but no result data');
}
return {
valid: errors.length === 0,
errors,
warnings,
trace_id: response.envelope?.trace_id,
status: status,
result: response.result?.data
};
}
const validation = validateResponse($input.first().json);
if (!validation.valid) {
throw new Error(`Contract validation failed: ${validation.errors.join(', ')}`);
}
return [{ json: validation }];
5. Logging a SYS_LOG
import psycopg2
from psycopg2.extras import Json
from datetime import datetime
def log_to_syslog(conn, request: dict, response: dict):
"""
Inserta registro en SYS_LOG.
"""
envelope = request.get('envelope', {})
r_envelope = response.get('envelope', {})
status = response.get('status', {})
quality = response.get('quality', {})
metadata = response.get('metadata', {})
sql = """
INSERT INTO SYS_LOG (
trace_id, step_id, idempotency_key,
step_index, step_type, profile,
timestamp_created, timestamp_started, timestamp_completed,
duration_ms,
module_name, module_version,
provider_used, fallback_level, model_id,
status_code,
input_hash, input_ref, input_type,
output_hash, output_ref,
confidence, coverage,
tokens_input, tokens_output,
cost_units
) VALUES (
%(trace_id)s, %(step_id)s, %(idempotency_key)s,
%(step_index)s, %(step_type)s, %(profile)s,
%(timestamp_created)s, %(timestamp_started)s, %(timestamp_completed)s,
%(duration_ms)s,
%(module_name)s, %(module_version)s,
%(provider_used)s, %(fallback_level)s, %(model_id)s,
%(status_code)s,
%(input_hash)s, %(input_ref)s, %(input_type)s,
%(output_hash)s, %(output_ref)s,
%(confidence)s, %(coverage)s,
%(tokens_input)s, %(tokens_output)s,
%(cost_units)s
)
"""
params = {
'trace_id': envelope.get('trace_id'),
'step_id': envelope.get('step_id'),
'idempotency_key': envelope.get('idempotency_key'),
'step_index': envelope.get('step_index', 1),
'step_type': 'TRANSFORM',
'profile': request.get('profile', 'FULL'),
'timestamp_created': datetime.now(),
'timestamp_started': envelope.get('timestamp_init'),
'timestamp_completed': r_envelope.get('timestamp_end'),
'duration_ms': metadata.get('processing_ms'),
'module_name': request.get('routing', {}).get('module'),
'module_version': request.get('routing', {}).get('version'),
'provider_used': status.get('provider_used'),
'fallback_level': status.get('fallback_level_used', 0),
'model_id': metadata.get('model_id'),
'status_code': status.get('code'),
'input_hash': request.get('payload', {}).get('content_hash'),
'input_ref': request.get('storage', {}).get('input_ref'),
'input_type': request.get('payload', {}).get('type'),
'output_hash': response.get('storage', {}).get('output_hash'),
'output_ref': response.get('storage', {}).get('output_ref'),
'confidence': quality.get('confidence'),
'coverage': quality.get('coverage'),
'tokens_input': quality.get('tokens_input'),
'tokens_output': quality.get('tokens_output'),
'cost_units': metadata.get('cost_units')
}
with conn.cursor() as cur:
cur.execute(sql, params)
conn.commit()
6. Checklist de Implementacion
- Implementar ContractBuilder en Python
- Implementar ContractBuilder en JavaScript (n8n)
- Implementar ResponseValidator
- Crear funcion log_to_syslog
- Configurar nodos n8n con templates
- Probar flujo completo LITE
- Probar flujo completo FULL
- Probar fallback chain
Fin del Documento IMPLEMENTACION - Version 2.0
Sistema GRACE - "Alfred Decide, GRACE Transforma"