first commit

This commit is contained in:
2026-06-13 08:23:21 -03:00
commit 89c23fb0ed
439 changed files with 32801 additions and 0 deletions

370
src/core/config.py Normal file
View File

@@ -0,0 +1,370 @@
"""
Configuration management module using Pydantic Settings.
This module provides configuration loading from environment variables and YAML files,
with validation of required fields.
"""
from typing import Optional, List
from pathlib import Path
from pydantic import BaseModel, Field, field_validator, AliasChoices
from pydantic_settings import BaseSettings, SettingsConfigDict
import yaml
import os
from dotenv import load_dotenv
_PROJECT_ROOT = Path(__file__).resolve().parents[2]
load_dotenv(_PROJECT_ROOT / ".env", override=False)
class LLMConfig(BaseModel):
"""LLM configuration model."""
provider: str = Field(..., description="LLM provider (openai, anthropic, etc.)")
model: str = Field(..., description="Model name")
temperature: float = Field(0.7, ge=0.0, le=2.0, description="Temperature for generation")
max_tokens: Optional[int] = Field(None, gt=0, description="Maximum tokens to generate")
@field_validator('provider')
@classmethod
def validate_provider(cls, v: str) -> str:
"""Validate that provider is supported."""
supported = ['openai', 'anthropic', 'oci_openai', 'oci', 'mock']
if v not in supported:
raise ValueError(f"Provider must be one of {supported}, got: {v}")
return v
class AgentConfig(BaseModel):
"""Agent-specific configuration model."""
name: str = Field(..., description="Agent name")
description: str = Field(..., description="Agent description")
system_prompt: str = Field(..., description="System prompt for the agent")
tools: List[str] = Field(default_factory=list, description="List of enabled tools")
max_iterations: int = Field(10, gt=0, description="Maximum graph iterations")
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=str(_PROJECT_ROOT / ".env"), extra="ignore", case_sensitive=False)
"""
Main settings class with support for environment variables and YAML files.
Configuration can be loaded from:
1. Environment variables (highest priority)
2. .env file
3. YAML file (via from_yaml method)
4. Default values (lowest priority)
"""
# App settings
APP_NAME: str = Field(default="Agent Microservice", description="Application name")
VERSION: str = Field(default="1.0.0", description="Application version")
DEBUG: bool = Field(default=False, description="Debug mode")
# LLM settings
LLM_PROVIDER: str = Field(default="oci_openai", description="LLM provider")
LLM_MODEL: str = Field(default="gpt-4", description="LLM model name")
LLM_TEMPERATURE: float = Field(default=0.7, ge=0.0, le=2.0, description="LLM temperature")
LLM_MAX_TOKENS: Optional[int] = Field(default=None, gt=0, description="Max tokens")
# Classification LLM settings
CLASSIFICATION_LLM_MODEL: str = Field(default="bo_gptoss20b_dev", description="Model name for classification node")
CLASSIFICATION_LLM_TEMPERATURE: float = Field(default=0.3, ge=0.0, le=2.0, description="Temperature for classification node")
CLASSIFICATION_LLM_MAX_TOKENS: int = Field(default=1024, gt=0, description="Max tokens for classification node")
CLASSIFICATION_LLM_TOP_P: float = Field(default=0.8, description="Top P for classification node")
CLASSIFICATION_LLM_TOP_K: float = Field(default=250, description="Top K for classification node")
# Large Classification LLM settings (for complex reasoning/canceling)
CLASSIFICATION_LARGE_LLM_MODEL: str = Field(default="bo_gptoss120b_dev", description="Large model name for critical classification steps")
CLASSIFICATION_LARGE_LLM_TEMPERATURE: float = Field(default=0.3, ge=0.0, le=2.0, description="Temperature for large classification node")
CLASSIFICATION_LARGE_LLM_MAX_TOKENS: int = Field(default=1024, gt=0, description="Max tokens for large classification node")
CLASSIFICATION_LARGE_LLM_TOP_P: float = Field(default=0.8, description="Top P for large classification node")
CLASSIFICATION_LARGE_LLM_TOP_K: float = Field(default=250, description="Top K for large classification node")
# API Keys
OPENAI_API_KEY: Optional[str] = Field(default=None, description="OpenAI API key")
ANTHROPIC_API_KEY: Optional[str] = Field(default=None, description="Anthropic API key")
# Server settings
HOST: str = Field(default="0.0.0.0", description="Server host")
PORT: int = Field(default=8000, gt=0, lt=65536, description="Server port")
# Logging
LOG_LEVEL: str = Field(default="INFO", description="Logging level")
LOG_FORMAT: str = Field(default="text", description="Log format (json or text)")
LOG_NOISY_LEVEL: str = Field(default="WARNING", description="Level applied to chatty third-party loggers (pymongo, urllib3, httpx, ...)")
# Checkpointing
CHECKPOINT_BACKEND: str = Field(default="memory", description="Checkpoint backend (memory or mongodb)")
MONGODB_CONNECTION_STRING: Optional[str] = Field(default=None, description="MongoDB connection string")
MONGODB_DATABASE: str = Field(default="agent_db", description="MongoDB database name")
MONGODB_COLLECTION: str = Field(default="checkpoints", description="MongoDB collection name")
# Autonomous Database / MongoDB Settings (memory cache + business data)
AUTONOMOUS_NOSQL_HOST: Optional[str] = Field(default=None, description="MongoDB host (OCI Autonomous)")
AUTONOMOUS_NOSQL_USER: Optional[str] = Field(
default=None,
description="MongoDB username (shared with TAIS via MONGODB_DB_USER alias)",
validation_alias=AliasChoices("AUTONOMOUS_NOSQL_USER", "MONGODB_DB_USER"),
)
AUTONOMOUS_NOSQL_PASSWORD: Optional[str] = Field(
default=None,
description="MongoDB password (shared with TAIS via MONGODB_DB_PASSWORD alias)",
validation_alias=AliasChoices("AUTONOMOUS_NOSQL_PASSWORD", "MONGODB_DB_PASSWORD", "MONGO_DB_PASSWORD"),
)
AUTONOMOUS_NOSQL_DB_NAME: Optional[str] = Field(default=None, description="MongoDB database name")
AUTONOMOUS_NOSQL_URL: Optional[str] = Field(default=None, description="MongoDB full connection URL (alternative to host+user+password)")
AUTONOMOUS_NOSQL_COLLECTION: str = Field(default="cases", description="Default collection for business data")
AUTONOMOUS_NOSQL_MEMORY_COLLECTION: str = Field(default="memory", description="Collection for cache/memory data")
USE_AUTONOMUS_NOSQL_LOCAL: bool = Field(default=False, description="Whether to use a local AutonomousDB instance instead of OCI Autonomous Database (for development/testing)")
# OCI Streaming settings
ENABLE_OCI_STREAMING: bool = Field(default=True, description="Whether to enable OCI Streaming consumer")
OCI_STREAM_ENDPOINT: Optional[str] = Field(default=None, description="OCI Streaming Custom Endpoint (e.g. https://10.152.97.46)")
OCI_REGION: str = Field(default="sa-saopaulo-1", description="OCI Region")
OCI_REQUEST_STREAM_OCID: Optional[str] = Field(default=None, description="OCID of the request stream")
OCI_RESPONSE_STREAM_OCID: Optional[str] = Field(default=None, description="OCID of the response stream")
OCI_CONSUMER_GROUP_NAME: str = Field(default="cg-case-process-bo-agent", description="Group name for OCI streaming consumer")
OCI_MESSAGES_LIMIT: int = Field(default=10, gt=0, description="Max messages to fetch per poll")
OCI_CONFIG_PROFILE: str = Field(default="DEFAULT", description="OCI config profile to use")
OCI_RESOLVE_TO_IP: Optional[str] = Field(default=None, description="IP to resolve the custom endpoint to")
OCI_STREAMING_SEMAPHORE_LIMIT: int = Field(default=8, gt=0, description="Max concurrent message processing (semaphore limit)")
# Response Emulator settings
EMULATOR_TEMPLATES_TOP_K: int = Field(default=5, gt=0, description="Top-K retrieved from the templates RAG in the response emulator")
EMULATOR_HISTORY_HIGH_SCORE_TOP_K: int = Field(default=5, gt=0, description="Top-K retrieved from the history RAG for the high-score bucket — positive references the model should learn from")
EMULATOR_HISTORY_LOW_SCORE_TOP_K: int = Field(default=5, gt=0, description="Top-K retrieved from the history RAG for the low-score bucket — negative examples the model must avoid imitating")
# Agent settings (optional)
AGENT_NAME: Optional[str] = Field(default=None, description="Agent name")
AGENT_DESCRIPTION: Optional[str] = Field(default=None, description="Agent description")
AGENT_SYSTEM_PROMPT: Optional[str] = Field(default=None, description="Agent system prompt")
AGENT_MAX_ITERATIONS: int = Field(default=10, gt=0, description="Max agent iterations")
AGENT_TYPE: str = Field(default="backoffice", description="Used as service.agent_type in application logs")
# IMDB API settings
PMID_API_HOST: str = Field(default="0.0.0.0", description="IMDB access host")
PMID_API_BASIC_TOKEN: str = Field(default="Basic base64_encoded_token", description="IMDB access basic token")
PMID_API_TIMEOUT: Optional[float] = Field(default=10.0, description="IMDB API timeout in seconds")
PMID_API_CLIENT_ID: str = Field(default="client_id", description="IMDB API client id")
# Siebel API settings
SIEBEL_API_HOST: Optional[str] = Field(default="https://siebel-api-url", description="Siebel API host ")
SIEBEL_API_ROUTE: str = Field(default="/customers/v1/backOfficeSRopening", description="Siebel API route (path)")
SIEBEL_API_TIMEOUT: int = Field(default=10, description="Siebel API timeout in seconds")
SIEBEL_API_USERNAME: Optional[str] = Field(default=None, description="Siebel API username")
SIEBEL_API_PASSWORD: Optional[str] = Field(default=None, description="Siebel API password")
SIEBEL_API_CLIENT_ID: Optional[str] = Field(default=None, description="Siebel API client id")
# Siebel Prospect API settings (non-TIM / canceled customers)
SIEBEL_PROSPECT_API_HOST: Optional[str] = Field(default=None, description="Siebel Prospect API host (base only)")
SIEBEL_PROSPECT_API_ROUTE: str = Field(default="", description="Siebel Prospect API route (path)")
SIEBEL_PROSPECT_API_AUTHORIZATION: Optional[str] = Field(default=None, description="Siebel Prospect API Authorization header (Basic)")
SIEBEL_PROSPECT_API_CLIENT_ID: Optional[str] = Field(default=None, description="Siebel Prospect API client id")
# Siebel SR status update (closing) settings
SIEBEL_STATUS_API_HOST: Optional[str] = Field(default=None, description="Siebel Pós-pago: host do endpoint de fechamento da SR")
SIEBEL_STATUS_API_ROUTE: str = Field(default="/interactions/v1/statusServiceRequest", description="Siebel Pós-pago: rota para fechamento da SR")
SIEBEL_PREPAGO_STATUS_API_HOST: Optional[str] = Field(default=None, description="Siebel Pré-pago: host do endpoint de fechamento")
SIEBEL_PREPAGO_STATUS_API_ROUTE: str = Field(default="/customers/v1/serviceRequest", description="Siebel Pré-pago: rota do endpoint de fechamento")
# ABRT API settings
ABRT_API_BASE_URL: str = Field(default="0.0.0.0", description="ABRT API host")
ABRT_API_AUTHORIZATION: str = Field(default="Basic <base64-credentials>", description="ABRT API Authorization header (Basic)")
ABRT_API_TOKEN: str = Field(default="token", description="ABRT API token header")
ABRT_API_TIMEOUT: Optional[float] = Field(default=10.0, description="ABRT API timeout in seconds")
ABRT_API_CLIENT_ID: str = Field(default="clientId", description="ABRT API client id")
# Portability API settings
PORTABILITY_API_URL: str = Field(default="0.0.0.0", description="Portability API URL")
PORTABILITY_API_AUTHORIZATION: str = Field(default="username", description="API basic authorization")
PORTABILITY_API_TIMEOUT: Optional[float] = Field(default=10.0, description="Portability API timeout in seconds")
PORTABILITY_API_CLIENT_ID: str = Field(default="clientId", description="Portability API client id")
# SSL
VERIFY_SSL: bool = Field(default=True, description="Verify SSL")
# Langfuse settings
USE_LANGFUSE_PROMPTS: bool = Field(default=True, description="Whether to use prompts from Langfuse")
USE_LANGFUSE_TRACING: bool = Field(default=True, description="Whether to send traces/generations to Langfuse")
LANGFUSE_PROMPTS_TTL: int = Field(default=600, description="TTL for Langfuse prompts cache in seconds")
LANGFUSE_SECRET_KEY: str | None = Field(default=None, description="Langfuse secret key", validation_alias=AliasChoices("LANGFUSE_SECRET_KEY", "langfuse_secret_key"))
LANGFUSE_PUBLIC_KEY: str | None = Field(default=None, description="Langfuse public key", validation_alias=AliasChoices("LANGFUSE_PUBLIC_KEY", "langfuse_public_key"))
LANGFUSE_BASE_URL: str | None = Field(default=None, description="Langfuse base URL", validation_alias=AliasChoices("LANGFUSE_BASE_URL", "langfuse_base_url", "LANGFUSE_HOST", "langfuse_host"))
LANGFUSE_ENVIRONMENT: str = Field(default="development", description="Langfuse tracing environment (e.g. development, staging, production)")
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE: Optional[str] = Field(
default=None,
description="Path to the PEM/CRT file with the CA bundle used to verify the Langfuse OTLP endpoint. Required in TIM environments where Langfuse is served behind an internal CA. Leave empty for local dev over plain HTTP.",
)
# Anatel Dictionary settings
USE_FULL_ANATEL_DICT: bool = Field(default=False, description="Whether to use the full Anatel motives dictionary instead of filtering by service")
# Speech Analytics API settings
SPEECH_PREDICTION_BASE_URL: Optional[str] = Field(default=None, description="Base URL of the Speech Prediction API gateway (OAuth2 + prediction endpoint)")
SPEECH_PREDICTION_CLIENT_ID: Optional[str] = Field(default=None, description="Client ID for the Speech Prediction OAuth2 client_credentials flow")
SPEECH_PREDICTION_CLIENT_SECRET: Optional[str] = Field(default=None, description="Client Secret for the Speech Prediction OAuth2 client_credentials flow")
SPEECH_HISTORY_BASE_URL: Optional[str] = Field(default=None, description="Base URL of the Speech History API (audio-toxico)")
SPEECH_HISTORY_AUDIENCE: Optional[str] = Field(default=None, description="Google ID Token target audience for the Speech History API")
SPEECH_HISTORY_HOST: Optional[str] = Field(default=None, description="Host header value for the Speech History API when calling via internal IP")
SPEECH_TIMEOUT: float = Field(default=10.0, description="Timeout in seconds shared by both Speech API calls (prediction and history)")
SPEECH_SIMILARITY_THRESHOLD: int = Field(default=70, ge=0, le=100, description="Minimum LLM-judged similarity percent (0-100) for a historical complaint to appear in historico_relacionado")
# TAIS Knowledge Base settings
MONGODB_DB_USER: Optional[str] = Field(
default=None,
description="Oracle ADB user for TAIS knowledge base (shares Mongo credentials)",
validation_alias=AliasChoices("MONGODB_DB_USER", "TAIS_DB_USER"),
)
MONGODB_DB_PASSWORD: Optional[str] = Field(
default=None,
description="Oracle ADB password for TAIS knowledge base (shares Mongo credentials)",
validation_alias=AliasChoices("MONGODB_DB_PASSWORD", "TAIS_DB_PASSWORD", "MONGO_DB_PASSWORD"),
)
TAIS_DB_DSN: Optional[str] = Field(default=None, description="Oracle ADB DSN for TAIS knowledge base")
TAIS_DB_TIMEOUT: float = Field(default=30.0, description="Timeout in seconds for TAIS DB connection and OCI GenAI embedding calls")
TAIS_GENAI_ENDPOINT: Optional[str] = Field(default=None, description="OCI GenAI endpoint used for embeddings")
TAIS_GENAI_COMPARTMENT_ID: Optional[str] = Field(default=None, description="OCI compartment OCID used for embeddings")
TAIS_GENAI_EMBED_MODEL_ID: str = Field(default="cohere.embed-multilingual-v3.0", description="OCI GenAI embedding model id")
TAIS_TABLE_CHUNKS: str = Field(default="CHUNKS_CHAR_COHERE_3", description="Oracle table containing TAIS chunks + embeddings")
TAIS_TABLE_FILES: str = Field(default="files_oci", description="Oracle table containing TAIS raw documents")
TAIS_TOP_K: int = Field(default=3, gt=0, description="Number of unique documents returned by TAIS KB search")
TAIS_KB_LLM_MODEL: str = Field(default="bo_gptoss20b_dev", description="Modelo LLM para pós-processamento da KB (bo_gptoss20b_dev ou bo_gptoss120b_dev)")
TAIS_KB_LLM_TEMPERATURE: float = Field(default=0.3, ge=0.0, le=2.0)
TAIS_KB_LLM_MAX_TOKENS: int = Field(default=4096, gt=0, description="Tokens de saída — manter alto para respostas completas")
TAIS_KB_LLM_TOP_P: float = Field(default=0.9)
TAIS_KB_LLM_TOP_K: float = Field(default=250)
TAIS_KB_USE_SPEECH_SUMMARY: bool = Field(default=False, description="Se True, usa speech.reclamacao_resumo como query; se False, usa complaint.description")
TAIS_KB_PREPROCESS: bool = Field(default=False, description="Pré-processa a query com LLM antes do embedding")
TAIS_KB_POSTPROCESS: bool = Field(default=True, description="Pós-processa resultados com LLM para síntese")
# Emulator RAG (Oracle ADB + OCI Cohere embeddings). DB credentials reuse the TAIS settings.
EMULATOR_RAG_OCI_GENAI_ENDPOINT: Optional[str] = Field(default=None, description="OCI GenAI endpoint for emulator RAG embeddings")
EMULATOR_RAG_COMPARTMENT_ID: Optional[str] = Field(default=None, description="OCI compartment OCID for emulator RAG embeddings")
EMULATOR_RAG_EMBED_MODEL_ID: str = Field(default="cohere.embed-multilingual-v3.0", description="OCI GenAI embedding model for emulator RAG")
EMULATOR_RAG_TEMPLATES_CHUNKS: str = Field(default="TEMPLATES_COHERE_3", description="Oracle table with templates chunks + embeddings")
EMULATOR_RAG_ANATEL_NOTAS_RESPOSTA_CHUNKS: str = Field(default="ANATEL_NOTAS_CHUNKS_RESPOSTA_COHERE_3", description="Oracle table with approved Anatel response chunks + embeddings")
EMULATOR_RAG_TOP_K: int = Field(default=5, gt=0, description="Default top-k used by the QA /emulator-rag/search route")
EMULATOR_RAG_HISTORY_HIGH_SCORE_THRESHOLD: int = Field(default=4, ge=0, le=5, description="High-score filter for the history RAG (anatel_resposta): chunks with `nota >= threshold` are retrieved as positive references")
EMULATOR_RAG_HISTORY_LOW_SCORE_THRESHOLD: int = Field(default=2, ge=0, le=5, description="Low-score filter for the history RAG (anatel_resposta): chunks with `nota <= threshold` are retrieved as negative examples the model must avoid imitating")
EMULATOR_RAG_ALLOW_MOCK_FALLBACK: bool = Field(default=False, description="LOCAL DEV ONLY: when true, RAG wrappers fall back to JSON mocks if the real backend is unset or fails. Must remain false in any shared environment (dev kube, fqa, prod) to avoid silent mock responses.")
model_config = {
"env_file": ".env",
"case_sensitive": False,
"extra": "ignore"
}
@field_validator('LLM_PROVIDER')
@classmethod
def validate_llm_provider(cls, v: str) -> str:
"""Validate that LLM provider is supported."""
supported = ['openai', 'anthropic', 'oci_openai', 'oci', 'mock']
if v not in supported:
raise ValueError(f"LLM_PROVIDER must be one of {supported}, got: {v}")
return v
@field_validator('LOG_LEVEL')
@classmethod
def validate_log_level(cls, v: str) -> str:
"""Validate log level."""
valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
v_upper = v.upper()
if v_upper not in valid_levels:
raise ValueError(f"LOG_LEVEL must be one of {valid_levels}, got: {v}")
return v_upper
@field_validator('LOG_FORMAT')
@classmethod
def validate_log_format(cls, v: str) -> str:
"""Validate log format."""
valid_formats = ['json', 'text']
v_lower = v.lower()
if v_lower not in valid_formats:
raise ValueError(f"LOG_FORMAT must be one of {valid_formats}, got: {v}")
return v_lower
@classmethod
def from_yaml(cls, path: str) -> "Settings":
"""
Load settings from a YAML file.
Args:
path: Path to the YAML configuration file
Returns:
Settings instance with values loaded from YAML
Raises:
FileNotFoundError: If the YAML file doesn't exist
yaml.YAMLError: If the YAML file is malformed
ValueError: If required fields are missing or invalid
"""
with open(path, 'r') as f:
config_dict = yaml.safe_load(f)
if config_dict is None:
config_dict = {}
return cls(**config_dict)
def to_llm_config(self) -> LLMConfig:
"""
Convert settings to LLMConfig model.
Returns:
LLMConfig instance with LLM-specific settings
"""
return LLMConfig(
provider=self.LLM_PROVIDER,
model=self.LLM_MODEL,
temperature=self.LLM_TEMPERATURE,
max_tokens=self.LLM_MAX_TOKENS
)
def to_agent_config(self) -> Optional[AgentConfig]:
"""
Convert settings to AgentConfig model if agent settings are present.
Returns:
AgentConfig instance if all required fields are present, None otherwise
"""
if not all([self.AGENT_NAME, self.AGENT_DESCRIPTION, self.AGENT_SYSTEM_PROMPT]):
return None
return AgentConfig(
name=self.AGENT_NAME,
description=self.AGENT_DESCRIPTION,
system_prompt=self.AGENT_SYSTEM_PROMPT,
tools=[], # Tools would be configured separately
max_iterations=self.AGENT_MAX_ITERATIONS
)
def setup_langfuse(self):
"""
Export Langfuse settings to environment variables.
Many libraries (like the Langfuse client and agent-framework) expect these
to be present in os.environ for authentication.
"""
if self.LANGFUSE_PUBLIC_KEY:
os.environ["LANGFUSE_PUBLIC_KEY"] = self.LANGFUSE_PUBLIC_KEY
if self.LANGFUSE_SECRET_KEY:
os.environ["LANGFUSE_SECRET_KEY"] = self.LANGFUSE_SECRET_KEY
if self.LANGFUSE_BASE_URL:
# Ensure protocol is present
url = self.LANGFUSE_BASE_URL
if url and not url.startswith(("http://", "https://")):
url = f"https://{url}" # Default to https if missing
os.environ["LANGFUSE_BASE_URL"] = url
os.environ["LANGFUSE_HOST"] = url
os.environ["LANGFUSE_TRACING_ENVIRONMENT"] = self.LANGFUSE_ENVIRONMENT
# Global settings instance
settings = Settings()
settings.setup_langfuse()