# Agent Framework OCI Evaluator — LLM-as-a-Judge **Language:** English | [Português](README.md) ## 1. Overview The **Agent Framework OCI Evaluator** is the batch component responsible for asynchronously evaluating the quality of conversations produced by agents built on the Agent Framework OCI. The service reads sessions and traces from **Langfuse v3**, deterministically samples sessions, calculates quality metrics with an **LLM-as-a-Judge**, persists state and results in **Oracle Autonomous Database**, generates the official `.TXT.GZ` artifact, publishes that artifact to **Google Cloud Storage (GCS)**, and audits the evaluator job itself in Langfuse. It is designed for **D+1 execution** as a **Kubernetes CronJob**, with no runtime HTTP/gRPC endpoint. > **Recommended deployment model:** one evaluator instance per agent/squad. The code is generic and supports multiple agents in configuration, but in production each deployment should normally enable only the agent owned by that squad. This keeps credentials, ownership, scheduling, and troubleshooting isolated. --- ## 2. Why this component exists Running generative-quality evaluation synchronously inside the agent request path would increase latency, cost, and coupling. The evaluator moves that responsibility out of the online interaction path. The architecture is: ```text Online interaction | v Agent Framework | +--------------------> Langfuse v3 | | traces / sessions v D+1 Evaluator (CronJob) | +------------------+------------------+ | | | v v v OCI GenAI ADB GCS LLM-as-a-Judge state/results official .TXT.GZ | +-----------------------> Langfuse audit / optional scores ``` This separation provides: - no evaluator latency in the online interaction; - replay and auditability; - configurable sampling per agent; - isolation of evaluation failures from the online agent; - support for dedicated OCI GenAI models; - an official artifact for the TIM analytics layer; - no code coupling to specific agent names, buckets, or credentials. --- ## 3. Core concepts ### 3.1. Trace A **trace** represents an interaction evaluated individually. The evaluator calculates these trace-level metrics: - `judgeScore` — overall response quality; - `accuracyScore` — factual/contextual accuracy; - `alucinationScore` — degree of unsupported claims; - `promptLength` — raw character count of `trace.input`. ### 3.2. Session A **session** represents the complete conversation. A session normally contains multiple traces. Session-level metrics are calculated once and replicated to every trace row in that session: - `inferredCsiScore`; - `resolution`; - `conversationPrecision`; - `loop`. ### 3.3. Session-based sampling Sampling is performed on **sessions**, never on isolated traces. The deterministic seed is: ```text agentId:reference_date ``` Example: ```text billing:2026-08-13 ``` Two executions for the same agent, date, and percentage therefore select the same sessions. Once a session is selected, **all traces in that session** are retained. ### 3.4. BRT time window `run-agents` calculates the reference date using: ```text D - daysBack ``` with explicit timezone: ```text America/Sao_Paulo ``` With `daysBack: 1`, a run on August 14 evaluates the full August 13 window, from 00:00:00 through 23:59:59.999999 BRT. ### 3.5. CONTEXT_TURN_LIMIT `CONTEXT_TURN_LIMIT` controls how many previous conversation turns are included in trace-level judge context. Default: ```env CONTEXT_TURN_LIMIT=6 ``` The variable exists because a response should not be judged outside the context of its conversation. Increasing the value provides more context, but also increases tokens, cost, and judge latency. ### 3.6. Loop The `loop` metric is not simple text repetition. It represents the maximum consecutive **`metadata.intent` streak** in a session. Example: ```text billing billing billing payment payment ``` Result: ```text loop = 3 ``` --- ## 4. End-to-end execution flow The production flow is executed with: ```bash python -m evaluator.cli run-agents ``` or through the installed entrypoint: ```bash af-evaluator run-agents ``` Steps: ```text 1. Load agents.yaml 2. Compute D-daysBack in BRT 3. Resolve Langfuse credentials for the agent 4. Read ALL native sessions in the period, paginated 5. Read ALL traces in the period, paginated 6. Read observations in bulk/paginated mode 7. Recover the real session_id from metadata when a legacy trace has no native sessionId 8. Filter agent traces/aliases 9. Deterministically sample logical sessions 10. Keep all traces belonging to sampled sessions 11. Persist items in ADB 12. Run trace-level judge 13. Run session-level judge 14. Calculate local loop metric 15. Generate .TXT.GZ 16. Upload to GCS 17. Delete local file after successful upload 18. Update judge-job-{agentId} audit trace in Langfuse ``` ### 4.1. Why collection avoids N+1 The implementation avoids the old pattern: ```text 1 trace -> GET trace detail -> GET observations ``` repeated once per trace. Collection now uses bulk pagination: ```text /api/public/sessions page 1..N /api/public/traces page 1..N /api/public/observations page 1..N ``` Therefore 10,000 traces do not produce approximately 20,000 additional requests. Request count grows with the number of API pages instead. The current `PAGE_SIZE` is 100. ### 4.2. Compatibility with traces without native `sessionId` Some historical instrumentations store the session in trace/observation metadata without promoting it to Langfuse's native `trace.sessionId`. The evaluator recovers the real session using this precedence: ```text trace.sessionId / trace.session_id -> trace.metadata.session_id / sessionId / conversation_key -> observation.metadata.session_id / sessionId / conversation_key -> business_context.session_key (last metadata fallback) ``` `trace.id` is **never** used as a `session_id`. This preserves session sampling and session-level metrics for historical traces without creating an artificial one-session-per-trace model. Collection statistics expose `native_sessions_read`, `metadata_sessions_recovered`, and `traces_without_session`. --- ## 5. Component architecture ```text agent_framework_oci_evaluator/ ├── evaluator/ │ ├── analytics/ │ │ └── vloop.py │ ├── collectors/ │ │ ├── langfuse.py │ │ ├── langfuse_credentials.py │ │ ├── agent_framework.py │ │ └── mock.py │ ├── config/ │ │ ├── agents.py │ │ └── settings.py │ ├── core/ │ │ └── models.py │ ├── identity/ │ │ └── resolver.py │ ├── judges/ │ │ └── llm_judge.py │ ├── llm/ │ │ ├── client.py │ │ └── profile_resolver.py │ ├── output/ │ │ └── legacy_exporter.py │ ├── persistence/ │ │ ├── oracle_store.py │ │ └── repository.py │ ├── prompts/ │ │ └── loader.py │ ├── publishers/ │ │ ├── gcs_export.py │ │ ├── langfuse_job_audit.py │ │ └── langfuse_scores.py │ ├── cli.py │ ├── engine.py │ └── time_utils.py ├── configs/ │ ├── identity.yaml │ ├── judge/ │ │ ├── agents.yaml │ │ ├── storage.yaml │ │ ├── trace_metrics.yaml │ │ └── session_metrics.yaml │ ├── langfuse/ │ │ └── langfuse_credentials.example.yaml │ └── llm_profiles/ │ └── llm_profiles.yaml ├── deploy/ ├── tests/ ├── .env.example ├── Dockerfile └── pyproject.toml ``` ### 5.1. Collectors Collectors transform external data sources into `ConversationRecord` objects. Available sources: ```text langfuse agent_framework mock ``` `langfuse` is the default production source. ### 5.2. EvaluationEngine `evaluator/engine.py` orchestrates: - run creation; - collection; - sampling; - persistence; - judging; - export; - upload; - audit. ### 5.3. Persistence Execution state and results are persisted in Oracle Database. The schema is created automatically by `init-db`. Main tables: ```text EVALUATION_RUN EVALUATION_PROGRESS_EVENT EVALUATION_ITEM EVALUATION_RESULT EVALUATION_METRIC EVALUATION_FINDING ``` The table prefix is configured through: ```env ADB_TABLE_PREFIX=AGENTFW ``` ### 5.4. Publishers - `gcs_export.py` — uploads the official artifact; - `langfuse_job_audit.py` — writes the `judge-job-{agentId}` audit trace; - `langfuse_scores.py` — optionally publishes trace scores back to Langfuse. > **Note:** production job audit uses the agent's dynamic credentials. Optional score publishing (`PUBLISH_LANGFUSE_SCORES=true`) still uses global `LANGFUSE_HOST/PUBLIC_KEY/SECRET_KEY`; keep it disabled unless an appropriate global project is configured. --- ## 6. Agent configuration Default file: ```text configs/judge/agents.yaml ``` Example: ```yaml agents: - agentId: billing enabled: true daysBack: 1 percentage: 1.0 langfuse_agent_aliases: - telecom_contas gcs_prefix: agnt_ai_contas ``` Fields: | Field | Description | |---|---| | `agentId` | official logical agent identity | | `enabled` | enables/disables the agent in that deployment | | `daysBack` | number of days to subtract from the current BRT date | | `percentage` | percentage of sessions evaluated, from 0.0 to 1.0 | | `langfuse_agent_aliases` | technical agent names accepted in traces; when populated, it is an explicit filter regardless of the Langfuse credential source | | `gcs_prefix` | agent prefix inside the bucket | `agent_id` and `days_back` are also accepted for compatibility, but `agentId` / `daysBack` is the recommended form. **`langfuse_agent_aliases` semantics:** when the list contains values, only traces whose agent identifier matches one of the aliases are eligible for sampling and evaluation. When the list is empty, the evaluator does not apply an agent-identity filter and treats the configured Langfuse project/endpoint as exclusive to that entry. ### 6.1. One evaluator per agent For the recommended one-instance-per-agent deployment model, keep only one agent enabled in that deployment: ```yaml agents: - agentId: billing enabled: true daysBack: 1 percentage: 1.0 langfuse_agent_aliases: - telecom_contas - billing_agent gcs_prefix: agnt_ai_contas ``` The same evaluator code can be reused by another repository/deployment simply by changing configuration files. --- ## 7. Dynamic Langfuse credentials There are no hardcoded variables such as: ```text LANGFUSE_BILLING_* LANGFUSE_SALES_* ``` The evaluator reads one declarative definition: ```env LANGFUSE_CREDENTIALS_CONFIG_PATH=configs/langfuse/langfuse_credentials.yaml ``` Create it from the example: ```bash cp configs/langfuse/langfuse_credentials.example.yaml \ configs/langfuse/langfuse_credentials.yaml ``` Example: ```yaml langfuse: agents: - agentId: billing host: https://langfuse.example.internal publicKey: pk-... secretKey: sk-... - agentId: another-agent host: https://another-langfuse.internal publicKey: pk-... secretKey: sk-... ``` The resolver iterates through all entries. Adding a new agent requires no Python change. ### 7.1. Environment/file references To avoid storing secrets in Git: ```yaml langfuse: agents: - agentId: billing hostEnv: BILLING_LF_HOST publicKeyEnv: BILLING_LF_PUBLIC secretKeyFile: /var/secrets/langfuse/billing-secret-key ``` Supported fields: ```text host / hostEnv / hostFile publicKey / publicKeyEnv / publicKeyFile secretKey / secretKeyEnv / secretKeyFile ``` Resolution order for each field: ```text direct value -> declared environment variable -> declared file ``` ### 7.2. Global credentials These variables remain available only for the generic manual `run` command: ```env LANGFUSE_HOST=http://localhost:3005 LANGFUSE_PUBLIC_KEY=... LANGFUSE_SECRET_KEY=... ``` The production `run-agents` flow resolves credentials from the per-agent declarative file. --- ## 8. LLM configuration Supported providers: ```text mock oci_openai oci_sdk openai_compatible ``` The recommended provider for dedicated OCI models is: ```env LLM_PROVIDER=oci_sdk ``` ### 8.1. OCI SDK Local example using `~/.oci/config`: ```env LLM_PROVIDER=oci_sdk OCI_AUTH_MODE=config_file OCI_CONFIG_FILE=~/.oci/config OCI_PROFILE=DEFAULT OCI_REGION=sa-saopaulo-1 OCI_COMPARTMENT_ID=ocid1.compartment... OCI_GENAI_BASE_URL=https://inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com OCI_GENAI_MODEL=meta.llama-3.3-70b-instruct ``` Authentication modes supported by the code: ```text config_file instance_principal resource_principal oke_workload_identity workload_identity ``` ### 8.2. Dedicated endpoint For a dedicated endpoint: ```env OCI_GENAI_BASE_URL=https://.inference.generativeai..oci.oraclecloud.com OCI_GENAI_ENDPOINT_ID=ocid1.generativeaiendpoint... ``` The dedicated endpoint OCID may also be provided in `OCI_GENAI_MODEL`. The provider selects `DedicatedServingMode` when an endpoint OCID is available; otherwise it uses `OnDemandServingMode`. ### 8.3. Judge profiles File: ```text configs/llm_profiles/llm_profiles.yaml ``` Example: ```yaml profiles: judge: temperature: 0 max_tokens: 900 ``` Selected through: ```env LLM_PROFILE=judge ``` --- ## 9. Prompts and metrics ### 9.1. Trace metrics File: ```text configs/judge/trace_metrics.yaml ``` The judge must return JSON such as: ```json { "judgeScore": 0.0, "accuracyScore": 0.0, "alucinationScore": 0.0, "rationale": "..." } ``` If parsing fails: ```text 1st attempt -> parse fails 2nd attempt with retry hint -> parse fails again trace-level metrics = None -> exported as "0.00" ``` ### 9.2. Session metrics File: ```text configs/judge/session_metrics.yaml ``` Expected result: ```json { "inferredCsiScore": 0.5, "resolution": 1, "conversationPrecision": 1, "rationale": "..." } ``` Session-level values are replicated to every trace row belonging to that session. --- ## 10. Identity normalization File: ```text configs/identity.yaml ``` The identity layer converts channel/domain-specific identifiers into canonical keys such as: ```text customer_key contract_key interaction_key account_key resource_key session_key ``` This prevents `cpf`, `msisdn`, `invoice_id`, `order_id`, `ura_call_id`, and similar names from being spread throughout evaluator code. Source ordering works as fallback priority. Example: ```yaml session_key: sources: - business_context.session_key - session_key - conversation_key - session_id ``` --- ## 11. GCS configuration Buckets live outside the code in: ```text configs/judge/storage.yaml ``` Example: ```yaml storage: buckets: dev: tim-bigdata-dev-ca1f-raw-standard-agnt-ai-atendimento fqa: tim-bigdata-fqa-60ce-raw-standard-agnt-ai-atendimento prd: tim-bigdata-prod-e305-raw-standard-agnt-ai-atendimento ``` Selection: ```env APP_ENV=dev STORAGE_CONFIG_PATH=configs/judge/storage.yaml ``` Operational override: ```env JUDGE_GCS_BUCKET=my-bucket ``` Enable the production flow with: ```env ENABLE_GCS_UPLOAD=true GOOGLE_APPLICATION_CREDENTIALS=/path/GCP_ACCESS_KEY.json ``` `run-agents` treats GCS as mandatory. If `ENABLE_GCS_UPLOAD=false` or upload fails, the agent run finishes as `FAILED`. --- ## 12. Output file The name is configured directly in `.env`: ```env OUTPUT_FILENAME_PATTERN=AGENTE_{agent_id}_LLM_JUDGE_{timestamp}.TXT.GZ ``` Available placeholders are `{agent_id}`, `{run_id}`, and `{timestamp}`. The `timestamp` uses the `YYYYMMDD_HHMMSS` format in the São Paulo time zone. Resulting default pattern: ```text AGENTE_{agentId}_LLM_JUDGE_{YYYYMMDD}_{HHMMSS}.TXT.GZ ``` Example: ```text AGENTE_billing_LLM_JUDGE_20260814_020015.TXT.GZ ``` Header: ```text "judgeScore"|;"accuracyScore"|;"alucinationScore"|;"promptLength"|;"loop"|;"inferredCsiScore"|;"resolution"|;"conversationPrecision"|;"uraCallId"|;"channelId"|;"sessionId"|;"messageId" ``` Final line: ```text "TOTAL"|;"" ``` After a successful GCS upload, the local file is deleted. --- ## 13. Local installation ### 13.1. Prerequisites - Python 3.11+; - access to Oracle Database/Autonomous Database; - Oracle Wallet when required; - access to Langfuse v3; - OCI Generative AI access for real judge execution; - GCP credentials for production upload. ### 13.2. Create the virtual environment Linux/WSL/macOS: ```bash python -m venv .venv source .venv/bin/activate ``` Windows PowerShell: ```powershell python -m venv .venv .\.venv\Scripts\Activate.ps1 ``` ### 13.3. Install Recommended development mode: ```bash python -m pip install --upgrade pip setuptools wheel pip install -e . ``` Validate: ```bash pip check python -c "import oci, httpx, oracledb; import google.cloud.storage; print('Dependencies OK')" ``` --- ## 14. Local configuration step by step ### Step 1 — `.env` ```bash cp .env.example .env ``` Edit the real values. Typical minimum configuration: ```env APP_ENV=dev ADB_USER=admin ADB_PASSWORD=... ADB_DSN=... ADB_WALLET_LOCATION=/path/to/wallet ADB_WALLET_PASSWORD=... ADB_TABLE_PREFIX=AGENTFW ENABLE_LANGFUSE=true LANGFUSE_CREDENTIALS_CONFIG_PATH=configs/langfuse/langfuse_credentials.yaml LLM_PROVIDER=oci_sdk LLM_PROFILE=judge OCI_AUTH_MODE=config_file OCI_CONFIG_FILE=~/.oci/config OCI_PROFILE=DEFAULT OCI_REGION=sa-saopaulo-1 OCI_COMPARTMENT_ID=... OCI_GENAI_BASE_URL=https://... OCI_GENAI_ENDPOINT_ID=ocid1.generativeaiendpoint... CONTEXT_TURN_LIMIT=6 BATCH_SIZE=50 MAX_ATTEMPTS=3 ENABLE_GCS_UPLOAD=true STORAGE_CONFIG_PATH=configs/judge/storage.yaml GOOGLE_APPLICATION_CREDENTIALS=/path/GCP_ACCESS_KEY.json ``` ### Step 2 — Langfuse ```bash cp configs/langfuse/langfuse_credentials.example.yaml \ configs/langfuse/langfuse_credentials.yaml ``` Declare the agent that will be executed. ### Step 3 — agents.yaml Adjust: ```text configs/judge/agents.yaml ``` For a dedicated instance, keep only that squad's agent enabled. ### Step 4 — validate configuration ```bash python -m evaluator.cli show-config ``` ### Step 5 — initialize the database ```bash python -m evaluator.cli init-db ``` > `reset-db` drops and recreates the evaluator schema. Use it only in a controlled environment. --- ## 15. CLI The actual help for the current version is: ```bash python -m evaluator.cli --help ``` Available commands: ```text init-db reset-db show-config run run-agents progress runs ``` ### 15.1. `run` — manual/diagnostic execution Syntax: ```bash python -m evaluator.cli run PERIOD_START PERIOD_END [OPTIONS] ``` Example: ```bash python -m evaluator.cli run \ 2026-08-12T00:00:00 \ 2026-08-12T23:59:59 \ --source langfuse \ --limit 10 ``` Options: ```text --source TEXT default: langfuse --limit INTEGER --show-progress / --no-show-progress ``` **Important:** `run` is a generic manual path. It collects, evaluates, and persists results, but does not execute the full production per-agent contract (configured sampling, GCS export, and job audit). Use `run-agents` for certification/production behavior. ### 15.2. `run-agents` — production flow ```bash python -m evaluator.cli run-agents ``` One agent: ```bash python -m evaluator.cli run-agents \ --agent-id billing \ --source langfuse ``` Limited test: ```bash python -m evaluator.cli run-agents \ --agent-id billing \ --limit 10 ``` Options: ```text --source TEXT default: langfuse --agent-id TEXT --limit INTEGER ``` `run-agents` uses the agent's `daysBack`, `percentage`, credentials, and `gcs_prefix`. ### 15.3. List runs ```bash python -m evaluator.cli runs ``` or: ```bash python -m evaluator.cli runs --limit 50 ``` ### 15.4. Inspect run progress ```bash python -m evaluator.cli progress ``` With more events: ```bash python -m evaluator.cli progress --events 100 ``` Typical production events: ```text LANGFUSE_JOB_AUDIT RUN_CREATED COLLECTING COLLECTED SAMPLED ITEMS_INSERTED BATCH_STARTED ITEM_COMPLETED SESSION_JUDGE_COMPLETED COMPLETED EXPORTED GCS_UPLOADED LOCAL_FILE_DELETED LANGFUSE_JOB_AUDIT ``` --- ## 16. Tests Install `pytest` if needed: ```bash pip install pytest ``` ### 16.1. All tests ```bash python -m pytest -v ``` ### 16.2. TIM specification tests ```bash python -m pytest tests/test_tim_spec.py -v ``` ### 16.3. Extended compliance tests ```bash python -m pytest tests/test_full_tim_compliance.py -v ``` ### 16.4. Hardcode regression tests ```bash python -m pytest tests/test_no_product_hardcodes.py -v ``` ### 16.5. Recommended release suite ```bash python -m pytest \ tests/test_tim_spec.py \ tests/test_full_tim_compliance.py \ tests/test_no_product_hardcodes.py \ -v ``` Validate syntax/imports: ```bash python -m compileall evaluator ``` --- ## 17. Recommended local functional test ### 17.1. Mock smoke test Configure: ```env LLM_PROVIDER=mock ``` Then run a small period using the mock source when desired: ```bash python -m evaluator.cli run \ 2026-08-12T00:00:00 \ 2026-08-12T23:59:59 \ --source mock ``` ### 17.2. Small Langfuse test ```bash python -m evaluator.cli run-agents \ --agent-id billing \ --limit 5 ``` Then: ```bash python -m evaluator.cli runs --limit 5 python -m evaluator.cli progress --events 100 ``` Confirm: - `COLLECTED` > 0; - `SAMPLED` lists selected sessions; - items finish without `ITEM_FAILED`; - `EXPORTED` appears; - `GCS_UPLOADED` contains a `gs://...` URI; - `LOCAL_FILE_DELETED` appears; - `completed` audit was confirmed. --- ## 18. Docker Local build: ```bash docker build -t agent-framework-evaluator:local . ``` The production runtime is batch-only. The container does not need to expose an HTTP port. In Kubernetes, the `CronJob` runs: ```text python -m evaluator.cli run-agents --source langfuse ``` --- ## 19. Kubernetes / OKE The **evaluator itself remains CronJob-only**, as required by the specification: it does not open an HTTP/gRPC port and evaluation processing continues to run exclusively in `evaluator-batch`. The web interface is provided by a **separate read-only component**, `evaluator-monitor`. It does not execute evaluations, mutate runs, or change the evaluator batch contract. It only reads run/progress/result tables from Autonomous Database. ```text Evaluator batch (evaluator-batch CronJob) │ ├── writes EVALUATION_RUN ├── writes EVALUATION_PROGRESS_EVENT └── writes EVALUATION_RESULT │ ▼ Autonomous Database ▲ │ read-only Evaluator Monitor (HTTP Deployment) │ └── /ui + monitoring APIs ``` Kustomize resources: ```text Namespace ServiceAccount ConfigMap CronJob evaluator-batch Deployment evaluator-monitor Service evaluator-monitor (ClusterIP) ``` No Ingress is created by default, so the UI is not automatically exposed outside the cluster. Current CronJob: ```yaml schedule: "0 2 * * *" timeZone: "America/Sao_Paulo" suspend: false concurrencyPolicy: Forbid ``` ### 19.1. Prepare deployment files ```bash cp deploy/env/evaluator.env.example deploy/env/evaluator.env cp deploy/langfuse/langfuse_credentials.example.yaml \ deploy/langfuse/langfuse_credentials.yaml ``` Place the Oracle wallet in: ```text deploy/wallet/ ``` GCP credential: ```text deploy/gcp/GCP_ACCESS_KEY.json ``` ### 19.2. Create secrets DEV: ```bash ./deploy/scripts/create-secrets.sh dev ``` HML: ```bash ./deploy/scripts/create-secrets.sh hml ``` PROD: ```bash ./deploy/scripts/create-secrets.sh prod ``` Created/updated secrets: ```text evaluator-secrets evaluator-wallet evaluator-langfuse evaluator-gcp (when the GCP file exists) ``` ### 19.3. Build and push ```bash export REGISTRY=tim.azurecr.io export IMAGE_TAG= ./deploy/scripts/build-push.sh prod ``` ### 19.4. Deploy ```bash export REGISTRY=tim.azurecr.io export IMAGE_TAG= ./deploy/scripts/deploy.sh prod ``` By default the script also runs the database initialization job. To disable it: ```bash export RUN_INIT_DB=false ``` ### 19.5. Status ```bash ./deploy/scripts/status.sh prod ``` or directly: ```bash kubectl -n agent-evaluator-prod get cronjob,jobs,pods ``` ### 19.6. Immediate execution outside the schedule ```bash ./deploy/scripts/run-now.sh prod ``` The script creates a Job from the CronJob. Follow logs: ```bash kubectl -n agent-evaluator-prod get jobs,pods kubectl -n agent-evaluator-prod logs -f job/ ``` ### 19.7. Web monitoring interface The UI is served by the independent `evaluator-monitor` component: ```text GET /ui GET /health GET /api/runs GET /api/runs/{run_id} GET /api/runs/{run_id}/results ``` To access a Kubernetes deployment locally without creating an Ingress: ```bash kubectl -n agent-evaluator-prod port-forward svc/evaluator-monitor 8080:8080 ``` Then open: ```text http://localhost:8080/ui ``` The page shows recent runs, `RUN_ID`, agent, status, progress, events, and results. The browser auto-refreshes every 5 seconds. To run the monitor locally: ```bash python -m uvicorn evaluator_monitor.app:app --host 0.0.0.0 --port 8080 ``` The monitor uses the same `ADB_*`/wallet settings as the evaluator. Functionally it is **read-only**: there is no endpoint to start, cancel, delete, or modify runs. > Security recommendation: keep the Service as `ClusterIP`. If external exposure is required later, enforce authentication/authorization through the corporate Ingress/API Gateway; do not expose the monitor directly to the Internet. --- ## 20. How to release a new version Recommended flow: ### 1. Update code/configuration Never commit real credentials. ### 2. Run tests ```bash python -m pytest -v python -m compileall evaluator ``` ### 3. Test locally with a small limit ```bash python -m evaluator.cli run-agents \ --agent-id \ --limit 5 ``` ### 4. Validate the run ```bash python -m evaluator.cli runs --limit 10 python -m evaluator.cli progress --events 100 ``` ### 5. Confirm GCS The event stream must include: ```text GCS_UPLOADED ``` with a URI such as: ```text gs:////AGENTE__LLM_JUDGE_.TXT.GZ ``` ### 6. Update environment secrets/configuration ```bash ./deploy/scripts/create-secrets.sh ``` ### 7. Build/push a new tag ```bash export REGISTRY= export IMAGE_TAG= ./deploy/scripts/build-push.sh ``` ### 8. Deploy ```bash ./deploy/scripts/deploy.sh ``` ### 9. Trigger a manual certification run ```bash ./deploy/scripts/run-now.sh ``` ### 10. Validate logs and artifacts ```bash kubectl -n agent-evaluator- logs -f job/ ``` Also confirm the GCS object and the `judge-job-{agentId}` audit in Langfuse. --- ## 21. Troubleshooting ### `Collected 0 traces` Check, in this order: 1. processed date (`daysBack` uses BRT); 2. Langfuse credentials for the correct `agentId`; 3. Langfuse project `host`; 4. `langfuse_agent_aliases`; 5. whether sessions/traces exist in the period; 6. whether the logical `agentId` differs from `metadata.agent_id` in traces. Example: ```yaml - agentId: billing langfuse_agent_aliases: - telecom_contas - billing_agent ``` ### `No Langfuse credentials configured for agent ...` The agent exists in `agents.yaml` but not in the file referenced by: ```env LANGFUSE_CREDENTIALS_CONFIG_PATH ``` Add an entry with the same `agentId`. ### `Langfuse credentials definition file not found` Create/mount: ```text configs/langfuse/langfuse_credentials.yaml ``` or configure another path. ### `TIM specification requires GCS upload...` `run-agents` was executed with: ```env ENABLE_GCS_UPLOAD=false ``` Set it to `true` and configure GCP credentials. ### GCS upload failure Check: ```bash echo "$GOOGLE_APPLICATION_CREDENTIALS" ls -l "$GOOGLE_APPLICATION_CREDENTIALS" ``` Also validate the bucket resolved through `APP_ENV`/`storage.yaml`. ### OCI SDK dedicated endpoint failure Check: ```env LLM_PROVIDER=oci_sdk OCI_GENAI_BASE_URL=https:// OCI_GENAI_ENDPOINT_ID=ocid1.generativeaiendpoint... OCI_COMPARTMENT_ID=... ``` Do not use `/openai/v1` in the native `oci_sdk` endpoint. ### Database schema missing ```bash python -m evaluator.cli init-db ``` ### View progress without a UI The service exposes no HTTP endpoint. Use: ```bash python -m evaluator.cli runs python -m evaluator.cli progress --events 100 ``` --- ## 22. Main environment variables | Variable | Purpose | |---|---| | `APP_ENV` | environment used to resolve storage | | `ADB_*` | Oracle connection and wallet | | `ENABLE_LANGFUSE` | enables Langfuse collection | | `LANGFUSE_CREDENTIALS_CONFIG_PATH` | declarative per-agent YAML | | `LANGFUSE_*` | global fallback only for manual `run` | | `PUBLISH_LANGFUSE_SCORES` | publishes trace scores back to Langfuse | | `LLM_PROVIDER` | `mock`, `oci_openai`, `oci_sdk`, `openai_compatible` | | `LLM_PROFILE` | judge profile | | `OCI_AUTH_MODE` | OCI SDK authentication mode | | `OCI_GENAI_BASE_URL` | OCI GenAI endpoint | | `OCI_GENAI_MODEL` | model ID/name or endpoint-OCID compatibility | | `OCI_GENAI_ENDPOINT_ID` | dedicated endpoint OCID | | `OCI_COMPARTMENT_ID` | OCI compartment | | `AGENTS_CONFIG_PATH` | agents file | | `TRACE_PROMPT_PATH` | trace-level prompt | | `SESSION_PROMPT_PATH` | session-level prompt | | `BATCH_SIZE` | internal processing batch size | | `MAX_ATTEMPTS` | configurable attempt limit | | `CONTEXT_TURN_LIMIT` | last N turns passed to trace judge | | `ENABLE_GCS_UPLOAD` | requires upload in production flow | | `STORAGE_CONFIG_PATH` | bucket mapping by environment | | `JUDGE_GCS_BUCKET` | bucket override | | `GOOGLE_APPLICATION_CREDENTIALS` | GCP credentials | | `OUTPUT_DIR` | temporary export directory | | `OUTPUT_FILENAME_PATTERN` | configurable output filename template | --- ## 23. Security and operations - do not commit the real `.env`; - do not commit the real `langfuse_credentials.yaml`; - do not commit GCP service account JSON; - do not commit Oracle Wallet files; - in OKE, prefer `oke_workload_identity` where applicable; - mount credentials as Kubernetes Secrets; - keep `readOnlyRootFilesystem` enabled in the CronJob; - keep `concurrencyPolicy: Forbid` to prevent two D+1 jobs from the same deployment running concurrently; - never run `reset-db` in production without an explicit recovery plan. --- ## 24. Certification checklist Before promoting a version: - [ ] `pip check` passes; - [ ] `python -m pytest -v` passes; - [ ] `python -m compileall evaluator` passes; - [ ] `show-config` points to the expected ADB/OCI/config files; - [ ] `agents.yaml` contains only the expected agents for that deployment; - [ ] Langfuse credentials exist for every enabled `agentId`; - [ ] `run-agents --limit 5` finds traces; - [ ] sampling operates on sessions, not individual traces; - [ ] trace/session judges complete; - [ ] output contains the 12-column header; - [ ] filename matches `AGENTE_{agentId}_LLM_JUDGE_{YYYYMMDD}_{HHMMSS}.TXT.GZ`; - [ ] `GCS_UPLOADED` event is confirmed; - [ ] local file is deleted after upload; - [ ] Langfuse `created/completed` audit is confirmed; - [ ] CronJob has `suspend: false`; - [ ] CronJob timezone is `America/Sao_Paulo`; - [ ] no real credentials are present in the repository. --- ## 25. Quick mental model for new contributors If you need to understand the project quickly, think of it as: ```text Langfuse stores conversations ↓ Evaluator selects D+1 sessions ↓ OCI GenAI judges traces and sessions ↓ ADB stores execution state and results ↓ GCS receives the official artifact ↓ Langfuse stores the batch audit trace ``` Extension points are configuration-driven: ```text Which agent? -> configs/judge/agents.yaml Which Langfuse project?-> langfuse_credentials.yaml Which bucket? -> configs/judge/storage.yaml Which prompt? -> trace_metrics.yaml / session_metrics.yaml Which model? -> .env + llm_profiles.yaml Which identity rules? -> configs/identity.yaml ``` The most important architectural rule is: **the evaluator code must remain generic; agent, environment, credential, and storage details belong in configuration.**