Archive: System v4 - Estado al 2024-12-24
This commit is contained in:
538
v4-archive/contratos-comunes/docs/IMPLEMENTACION.md
Normal file
538
v4-archive/contratos-comunes/docs/IMPLEMENTACION.md
Normal file
@@ -0,0 +1,538 @@
|
||||
# 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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
# 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)
|
||||
|
||||
```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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```python
|
||||
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"*
|
||||
Reference in New Issue
Block a user