# Visibility

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/core/service/llm/visibility>.

An `LLM` *Service* emits identity-aware, inference-specific *AccessLogs* in real time. The common part identifies the *User*, *Session*, *Device*, *Service*, *Namespace*, authorization decision and policy match. The `entry.info.llm` part records the normalized inference exchange (read more about visibility [here](https://octelium.com/docs/octelium/latest/management/core/visibility.md)).

## Body and Header Visibility

Prompt and completion bodies are disabled by default because they routinely contain secrets, personal information, source code and proprietary data. Enable only the required fields:

```yaml
spec:
  mode: LLM
  config:
    upstream:
      url: https://api.openai.com
    llm:
      visibility:
        enableRequestBody: true
        enableRequestBodyMap: true
        enableResponseBody: true
        enableResponseBodyMap: true
        includeRequestHeaders:
          - User-Agent
          - X-Request-ID
        includeResponseHeaders:
          - Content-Type
          - X-Provider-Request-ID
```

- `enableRequestBody` records the request body as bytes.
- `enableRequestBodyMap` records its serialized JSON representation.
- `enableResponseBody` records a non-streamed response body as bytes.
- `enableResponseBodyMap` records its serialized JSON representation.
- `includeRequestHeaders` and `includeResponseHeaders` select individual headers.

All headers can be enabled with explicit exclusions:

```yaml
llm:
  visibility:
    includeAllRequestHeaders: true
    includeAllResponseHeaders: true
    excludeRequestHeaders:
      - Cookie
      - X-Internal-Token
    excludeResponseHeaders:
      - Set-Cookie
```

Sensitive authentication and session headers are excluded even when all headers are enabled. Body and event capture is bounded, so a large body can be omitted. Streamed response bodies and body maps are not recorded through the response-body options.

> **Note:**
>
> Access-log visibility and semantic cache storage are independent. A *Service* that records no prompt or completion body still stores prompt-derived vectors, execution-context digests and generated responses while a semantic cache entry lives.

## Entry Types

The `type` field describes the lifecycle of a response:

- `COMPLETE` is a request with a finite response.
- `STREAM_START` is emitted when a streamed response starts.
- `STREAM_END` is the final record of that stream and carries accumulated response metadata and usage.

The `source` field identifies who produced the response:

- `UPSTREAM` means the provider produced it and inference tokens were consumed.
- `SEMANTIC_CACHE` means an earlier response was reused without invoking the provider.
- `OCTELIUM` means Octelium produced it, including rejected requests.

`isUpstreamInvoked` is intentionally separate. A response guardrail can reject provider-generated output, resulting in `source: OCTELIUM` while `isUpstreamInvoked: true` and `usage` still show the tokens consumed.

## Complete Response

The following is the LLM-specific part of an example non-streamed *AccessLog*. Common identity and policy fields are omitted for brevity:

```json
{
  "entry": {
    "info": {
      "llm": {
        "type": "COMPLETE",
        "protocol": "OPENAI",
        "operation": "GENERATE",
        "route": "RESPONSES",
        "source": "UPSTREAM",
        "isUpstreamInvoked": true,
        "model": {
          "requested": "auto",
          "effective": "gpt-5",
          "reported": "gpt-5-2026-08-01",
          "source": "SEMANTIC_ROUTER"
        },
        "maxOutputTokens": "2048",
        "inputItemCount": 1,
        "estimatedInputTokens": "96",
        "estimateQuality": "COMPLETE",
        "usage": {
          "state": "COMPLETE",
          "inputTokens": "101",
          "outputTokens": "328",
          "totalTokens": "429",
          "cacheReadInputTokens": "0",
          "reasoningOutputTokens": "112"
        },
        "responseID": "resp_01HXYZ",
        "finishReason": "TOOL_CALL",
        "rawFinishReason": "tool_calls",
        "reasoning": {
          "effort": "high"
        },
        "tools": {
          "count": 2,
          "names": ["read_document", "search_knowledge_base"],
          "removedCount": 1,
          "removedNames": ["execute_shell"],
          "calledNames": ["search_knowledge_base"],
          "callCount": 1
        },
        "guardrails": [
          {
            "result": "MODIFIED",
            "leg": "REQUEST",
            "plugin": "input-dlp"
          },
          {
            "result": "PASS",
            "leg": "RESPONSE",
            "plugin": "output-safety"
          }
        ],
        "tokenRateLimit": {
          "result": "ALLOWED"
        },
        "semanticCache": {
          "result": "MISS",
          "plugin": "user-cache"
        },
        "semanticRouter": {
          "result": "MATCH",
          "route": "complex-engineering",
          "similarity": 0.81,
          "model": "gpt-5",
          "plugin": "automatic-model-selection"
        },
        "http": {
          "request": {
            "method": "POST",
            "path": "/v1/responses",
            "bodyBytes": "742"
          },
          "response": {
            "code": 200,
            "bodyBytes": "1480",
            "contentType": "application/json"
          },
          "httpVersion": "HTTP2"
        }
      }
    }
  }
}
```

Integer values whose protobuf type is 64-bit are represented as JSON strings.

## Model and Reasoning

`model` distinguishes three names:

- `requested` is what the downstream asked for.
- `effective` is what Octelium sent to the provider.
- `reported` is the provider's response value, which can resolve an alias or unpinned name.

Its `source` is unset when the requested model was preserved, or is `CONFIG`, `PLUGIN` or `SEMANTIC_ROUTER`. `plugin` names the model plugin when the source is `PLUGIN`.

`reasoning` is present when the *Service* decided the configuration. It contains `isDisabled`, a served ordinal `effort`, or a numeric `tokenBudget`. Octelium does not interpret a reasoning value controlled entirely by the downstream.

## Provider Token Usage

`usage` contains provider-reported counts only. It is never populated with Octelium's estimate, and it is absent for a semantic cache hit. `state` is `COMPLETE` for final provider usage or `PARTIAL` when a stream ended before the final usage report.

`inputTokens`, `outputTokens` and `totalTokens` are accompanied by:

- `cacheReadInputTokens`, which is a subset of input for OpenAI and additive for Anthropic.
- `cacheWriteInputTokens`, which Anthropic reports additively.
- `reasoningOutputTokens`, which OpenAI already includes in output.

Use `estimatedInputTokens` and `estimateQuality` when no provider usage is present, but never treat the estimate as billing truth.

## Streamed Response

A streamed response emits a start record followed by an end record. The end record can include time to first token, event count, provider usage and finish reason:

```json
{
  "entry": {
    "info": {
      "llm": {
        "type": "STREAM_END",
        "protocol": "ANTHROPIC",
        "operation": "GENERATE",
        "route": "MESSAGES",
        "source": "UPSTREAM",
        "isUpstreamInvoked": true,
        "model": {
          "requested": "claude-sonnet",
          "effective": "claude-sonnet",
          "reported": "claude-sonnet-2026-08-15"
        },
        "stream": true,
        "usage": {
          "state": "COMPLETE",
          "inputTokens": "220",
          "outputTokens": "640",
          "totalTokens": "860"
        },
        "finishReason": "STOP",
        "rawFinishReason": "end_turn",
        "timeToFirstToken": {
          "milliseconds": 480
        },
        "eventCount": "37"
      }
    }
  }
}
```

An incomplete stream can have `usage.state: PARTIAL` and an unset finish reason. Its observed token counts are a floor rather than a final total.

## Translated Request

A request translated to another upstream protocol (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/llm/overview.md#protocol-translation)) records the upstream protocol and route:

```json
{
  "entry": {
    "info": {
      "llm": {
        "type": "COMPLETE",
        "protocol": "OPENAI",
        "route": "CHAT_COMPLETIONS",
        "translation": {
          "upstreamProtocol": "ANTHROPIC",
          "upstreamRoute": "MESSAGES"
        }
      }
    }
  }
}
```

`translation` is absent when the request was not translated. `protocol` and `route` still describe the downstream request. `usage`, the response ID, the raw finish reason and the reported model are the values reported by the upstream, so the token counts, especially the cache token counts, follow the accounting of `upstreamProtocol`.

## Semantic Cache Hit

An exact or semantic hit has no provider usage and does not spend a token quota:

```json
{
  "entry": {
    "info": {
      "llm": {
        "type": "COMPLETE",
        "source": "SEMANTIC_CACHE",
        "semanticCache": {
          "result": "SEMANTIC_HIT",
          "similarity": 0.96,
          "isStored": false,
          "plugin": "user-cache"
        }
      }
    }
  }
}
```

Cache results are `EXACT_HIT`, `SEMANTIC_HIT`, `MISS`, `BYPASS` or `ERROR`. `ERROR` means an embedding or vector-store failure; the request fails open to the provider. `isStored` shows whether the observed response was added to the cache.

## Rejections

A token quota rejection identifies the plugin and scope that denied the request:

```json
{
  "entry": {
    "info": {
      "llm": {
        "type": "COMPLETE",
        "source": "OCTELIUM",
        "tokenRateLimit": {
          "result": "DENIED",
          "plugin": "user-total-budget",
          "scope": "TOTAL"
        },
        "http": {
          "response": {
            "code": 429,
            "contentType": "application/json"
          }
        }
      }
    }
  }
}
```

Each applied guardrail produces an entry with `PASS`, `MODIFIED`, `DENIED` or `ERROR`. `ERROR` distinguishes an inability to reach a verdict from content that matched a deny pattern.

## Metrics and Export

*AccessLogs* are exported through OpenTelemetry and can be sent to an observability or SIEM backend. Useful dimensions include *Service*, *User*, requested and effective model, protocol, operation, route, finish reason, cache result, semantic route, guardrail result and quota result. Only sum `usage` when measuring provider consumption; it contains authoritative reported values and excludes cache hits.

Read more about the [LLM configuration](https://octelium.com/docs/octelium/latest/management/core/service/llm/overview.md) and [LLM plugins](https://octelium.com/docs/octelium/latest/management/core/service/llm/plugins.md).
