* feat: add comprehensive OpenTelemetry tracing - Add tool execution spans for reflect operations - Add tool call information (names, params) to spans - Change verification scope from 'test' to 'verification' - Add hindsight.reflect_generation span for done() processing - Implement no-op tracer for improved code readability - Update documentation for OTEL configuration - Resolve merge conflicts from rebase * fix: properly serialize Pydantic models in span recording - Add _serialize_for_span() helper to handle Pydantic models - Update all providers to use the helper function - Fixes test failures with 'Object of type X is not JSON serializable' * feat: add Grafana LGTM stack for unified local observability Add Grafana LGTM (Loki, Grafana, Tempo, Mimir) as the recommended local development observability stack. This provides traces, metrics, and logs in a single Docker container instead of separate tools. Changes: - Add scripts/dev/grafana/ with docker-compose and README - Add scripts/dev/start-grafana.sh startup script - Update .env.example to reference Grafana LGTM - Update configuration docs to emphasize Grafana LGTM as primary option - Reorder OTLP backend list to show Grafana LGTM first Benefits: - Single container vs multiple separate tools (Jaeger, SigNoz, etc.) - ~515MB image with full observability stack - Compatible with existing OTLP configuration - Simpler local development setup * chore: remove SigNoz scripts and references Remove SigNoz observability stack in favor of Grafana LGTM as the sole recommended local development tracing solution. Changes: - Delete scripts/dev/signoz/ directory and all SigNoz configurations - Delete scripts/dev/start-signoz.sh startup script - Remove SigNoz references from .env.example - Remove SigNoz from OTLP backends list in configuration docs Grafana LGTM provides the same capabilities (traces, metrics, logs) in a simpler single-container setup. * feat: add consolidation span hierarchy for tracing Add parent-child span structure for consolidation operations: - hindsight.consolidation: Parent span for each memory being processed - hindsight.consolidation_recall: Child span for finding related observations - LLM call span: Automatically created by LLM provider (scope="consolidation") This enables detailed timing breakdown in Grafana Tempo: - Total consolidation time per memory - Time spent in recall - Time spent in LLM call - Time spent executing actions (create/update) All consolidation tests pass (31/31). * feat: add Prometheus metrics and GenAI dashboard to Grafana stack Add comprehensive metrics and dashboarding to the Grafana LGTM stack: Metrics Collection: - Configure Prometheus to scrape Hindsight API /metrics endpoint - Scrape interval: 10 seconds - Targets hindsight-api on host.docker.internal:8888 GenAI Dashboard: - Pre-configured dashboard with 6 panels: - LLM call rate (by provider/model) - LLM call duration (p50/p95 by scope) - Token usage - input tokens/sec by scope - Token usage - output tokens/sec by scope - Operations rate (retain/recall/reflect/consolidation) - Operation duration p95 by operation type Configuration: - Mount prometheus.yml for metrics scraping - Mount dashboards directory for auto-provisioning - Add host.docker.internal mapping for container->host access - Dashboard provisioning with auto-reload every 10s Documentation: - Updated README with metrics viewing instructions - Added PromQL query examples - Documented dashboard access and navigation This provides full observability: traces (Tempo) + metrics (Prometheus/Mimir) + dashboards (Grafana) * refactor: merge Grafana setup into existing monitoring stack Consolidate the separate scripts/dev/grafana/ setup into the existing scripts/dev/monitoring/ stack, using Grafana LGTM (Loki, Grafana, Tempo, Mimir). Changes: - Remove separate scripts/dev/grafana/ directory and start-grafana.sh - Rewrite scripts/dev/monitoring/start.sh to use Docker + Grafana LGTM (was: download native Prometheus/Grafana binaries) - Add docker-compose.yaml for Grafana LGTM container - Add prometheus.yml for scraping Hindsight API metrics - Mount existing dashboards from monitoring/grafana/dashboards/ - Add comprehensive README.md Benefits: - Single unified monitoring command: ./scripts/dev/start-monitoring.sh - Uses existing dashboard files (hindsight-operations, hindsight-llm, hindsight-api-service) - Simpler setup: Docker-based vs downloading/running native binaries - Full observability: traces + metrics + logs + dashboards in one container - Standard ports: Grafana on 3000, OTLP on 4317/4318 Architecture: - Grafana LGTM container (~515MB) provides all components - Dashboards auto-provisioned from monitoring/grafana/dashboards/ - Prometheus scrapes host.docker.internal:8888/metrics - Shared hindsight-network for future service-to-service tracing * fix: run monitoring stack in foreground for easy Ctrl+C stop Change docker-compose from detached (-d) to foreground mode. Users can now stop the stack with Ctrl+C instead of needing to run docker-compose down separately. * fix: remove invalid home dashboard path and obsolete version field - Remove GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH environment variable (was pointing to wrong path causing 'Failed to load home dashboard' error) - Remove obsolete 'version' field from docker-compose.yaml (docker-compose v2+ doesn't require version field) * fix: load Hindsight dashboards in Grafana LGTM Mount Hindsight dashboard JSON files and custom provisioning config to make dashboards visible in Grafana. Changes: - Mount hindsight-operations.json, hindsight-llm.json, hindsight-api-service.json to /otel-lgtm/ - Create grafana-dashboards.yaml with all dashboard providers (default + Hindsight) - Mount custom provisioning config to override LGTM default All 3 Hindsight dashboards now appear in Grafana UI with metrics from Prometheus scraping the Hindsight API /metrics endpoint. * fix: configure Prometheus to scrape Hindsight API metrics Update prometheus.yml to include both OTLP receiver config (from LGTM) and scrape_configs for pulling metrics from Hindsight API. Changes: - Mount prometheus.yml to /otel-lgtm/prometheus.yaml (where LGTM reads it) - Add scrape_configs section to pull from host.docker.internal:8888/metrics - Keep OTLP receiver configuration for trace metrics - Set scrape_interval to 5s Verified: Prometheus now successfully scrapes hindsight_llm_calls_total and other Hindsight metrics. Dashboards now show live data! * feat: add comprehensive tracing for recall and improve reflect/mental_model_refresh spans - Add recall operation tracing with parent-child span hierarchy - Parent: hindsight.recall with attributes (bank_id, query, fact_types, etc.) - Children: recall_embedding, recall_retrieval, recall_fusion, recall_rerank - Fixed context propagation using start_as_current_span() - Improve reflect tracing spans - Remove reflect_generation spans, use reflect instead - Change done() tool processing to hindsight.reflect_tool_call - Fix mental_model_refresh span nesting - Add _skip_span parameter to reflect_async to avoid duplicate hindsight.reflect spans - Mental model refresh now has clean span hierarchy without nested reflect parent - Add comprehensive tracing verification tests - Test span hierarchy and attributes for all operations - Verify parent-child relationships - 5 passing tests covering recall, reflect, consolidation, and mental_model_refresh * refactor: remove redundant is_tracing_enabled() checks - Remove all is_tracing_enabled() conditional checks before tracing calls - NoOpTracer/NoOpSpan handle disabled tracing automatically - Simplify code by always calling tracer methods directly - Fix NoOpTracer.start_as_current_span() to yield NoOpSpan instead of None Changes: - memory_engine.py: Remove 5 is_tracing_enabled checks in recall spans - agent.py: Remove 2 is_tracing_enabled checks in reflect tool spans - tracing.py: Fix NoOpTracer context manager to yield proper NoOpSpan This eliminates ~50 lines of redundant conditional code while maintaining identical behavior. * docs: simplify distributed tracing section in monitoring.md - Make tracing documentation more concise - Focus on span hierarchy and attributes - Remove verbose troubleshooting and performance sections - Keep configuration.md for env vars only
270 lines
9.5 KiB
Markdown
270 lines
9.5 KiB
Markdown
# Monitoring
|
|
|
|
Hindsight provides comprehensive observability through Prometheus metrics, OpenTelemetry distributed tracing, and pre-built Grafana dashboards.
|
|
|
|
## Local Development
|
|
|
|
For local observability, use the Grafana LGTM (Loki, Grafana, Tempo, Mimir) all-in-one stack:
|
|
|
|
```bash
|
|
./scripts/dev/start-monitoring.sh
|
|
```
|
|
|
|
This starts a single Docker container providing:
|
|
- **Grafana UI**: http://localhost:3000 (anonymous admin access)
|
|
- **Traces (Tempo)**: OTLP endpoint at http://localhost:4318 (HTTP) and http://localhost:4317 (gRPC)
|
|
- **Metrics (Prometheus/Mimir)**: Scrapes http://localhost:8888/metrics automatically
|
|
- **Logs (Loki)**: Available for log aggregation
|
|
- **Pre-built Dashboards**: Hindsight Operations, LLM Metrics, API Service
|
|
|
|
**Enable tracing in your API:**
|
|
```bash
|
|
export HINDSIGHT_API_OTEL_TRACES_ENABLED=true
|
|
export HINDSIGHT_API_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
|
```
|
|
|
|
:::note Production Deployment
|
|
The local monitoring stack is for development only. In production, deploy Grafana LGTM separately or use commercial platforms (Grafana Cloud, DataDog, New Relic, etc.).
|
|
:::
|
|
|
|
## Grafana Dashboards
|
|
|
|
Pre-built dashboards are available in [`monitoring/grafana/dashboards/`](https://github.com/anthropics/hindsight/tree/main/monitoring/grafana/dashboards). Import these JSON files into your Grafana instance:
|
|
|
|
| Dashboard | Description |
|
|
|-----------|-------------|
|
|
| **Hindsight Operations** | Operation rates, latency percentiles, per-bank metrics |
|
|
| **Hindsight LLM Metrics** | LLM calls, token usage, latency by scope/provider |
|
|
| **Hindsight API Service** | HTTP requests, error rates, DB pool, process metrics |
|
|
|
|
The dashboards are automatically provisioned when using the monitoring stack script.
|
|
|
|
## Metrics Endpoint
|
|
|
|
Hindsight exposes Prometheus metrics at `/metrics`:
|
|
|
|
```bash
|
|
curl http://localhost:8888/metrics
|
|
```
|
|
|
|
## Available Metrics
|
|
|
|
### Operation Metrics
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|--------|------|--------|-------------|
|
|
| `hindsight.operation.duration` | Histogram | operation, bank_id, source, budget, max_tokens, success | Duration of operations in seconds |
|
|
| `hindsight.operation.total` | Counter | operation, bank_id, source, budget, max_tokens, success | Total number of operations executed |
|
|
|
|
**Labels:**
|
|
- `operation`: Operation type (`retain`, `recall`, `reflect`)
|
|
- `bank_id`: Memory bank identifier
|
|
- `source`: Where the operation was triggered from (`api`, `reflect`, `internal`)
|
|
- `budget`: Budget level if specified (`low`, `mid`, `high`)
|
|
- `max_tokens`: Max tokens if specified
|
|
- `success`: Whether the operation succeeded (`true`, `false`)
|
|
|
|
The `source` label allows distinguishing between:
|
|
- `api`: Direct API calls from clients
|
|
- `reflect`: Internal recall calls made during reflect operations
|
|
- `internal`: Other internal operations
|
|
|
|
### LLM Metrics
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|--------|------|--------|-------------|
|
|
| `hindsight.llm.duration` | Histogram | provider, model, scope, success | Duration of LLM API calls in seconds |
|
|
| `hindsight.llm.calls.total` | Counter | provider, model, scope, success | Total number of LLM API calls |
|
|
| `hindsight.llm.tokens.input` | Counter | provider, model, scope, success, token_bucket | Input tokens for LLM calls |
|
|
| `hindsight.llm.tokens.output` | Counter | provider, model, scope, success, token_bucket | Output tokens from LLM calls |
|
|
|
|
**Labels:**
|
|
- `provider`: LLM provider (`openai`, `anthropic`, `gemini`, `groq`, `ollama`, `lmstudio`)
|
|
- `model`: Model name (e.g., `gpt-4`, `claude-3-sonnet`)
|
|
- `scope`: What the LLM call is for (`memory`, `reflect`, `consolidation`, `answer`)
|
|
- `success`: Whether the call succeeded (`true`, `false`)
|
|
- `token_bucket`: Token count bucket for cardinality control (`0-100`, `100-500`, `500-1k`, `1k-5k`, `5k-10k`, `10k-50k`, `50k+`)
|
|
|
|
### HTTP Request Metrics
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|--------|------|--------|-------------|
|
|
| `hindsight.http.duration` | Histogram | method, endpoint, status_code, status_class | Duration of HTTP requests in seconds |
|
|
| `hindsight.http.requests.total` | Counter | method, endpoint, status_code, status_class | Total number of HTTP requests |
|
|
| `hindsight.http.requests.in_progress` | UpDownCounter | method, endpoint | Number of HTTP requests currently being processed |
|
|
|
|
**Labels:**
|
|
- `method`: HTTP method (`GET`, `POST`, `PUT`, `DELETE`)
|
|
- `endpoint`: Request path (normalized to reduce cardinality - UUIDs replaced with `{id}`)
|
|
- `status_code`: HTTP status code (`200`, `400`, `500`, etc.)
|
|
- `status_class`: Status code class (`2xx`, `4xx`, `5xx`)
|
|
|
|
### Database Pool Metrics
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|--------|------|--------|-------------|
|
|
| `hindsight.db.pool.size` | Gauge | - | Current number of connections in the pool |
|
|
| `hindsight.db.pool.idle` | Gauge | - | Number of idle connections in the pool |
|
|
| `hindsight.db.pool.min` | Gauge | - | Minimum pool size |
|
|
| `hindsight.db.pool.max` | Gauge | - | Maximum pool size |
|
|
|
|
### Process Metrics
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|--------|------|--------|-------------|
|
|
| `hindsight.process.cpu.seconds` | Gauge | type | Process CPU time in seconds |
|
|
| `hindsight.process.memory.bytes` | Gauge | type | Process memory usage in bytes |
|
|
| `hindsight.process.open_fds` | Gauge | - | Number of open file descriptors |
|
|
| `hindsight.process.threads` | Gauge | - | Number of active threads |
|
|
|
|
**Labels:**
|
|
- `type` (CPU): `user` or `system`
|
|
- `type` (Memory): `rss_max` (maximum resident set size)
|
|
|
|
### Histogram Buckets
|
|
|
|
Custom bucket boundaries are configured for better percentile accuracy:
|
|
|
|
**Operation Duration Buckets (seconds):**
|
|
```
|
|
0.1, 0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 5.0, 7.5, 10.0, 15.0, 20.0, 30.0, 60.0, 120.0
|
|
```
|
|
|
|
**LLM Duration Buckets (seconds):**
|
|
```
|
|
0.1, 0.25, 0.5, 1.0, 2.0, 3.0, 5.0, 10.0, 15.0, 30.0, 60.0, 120.0
|
|
```
|
|
|
|
**HTTP Duration Buckets (seconds):**
|
|
```
|
|
0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0
|
|
```
|
|
|
|
## Prometheus Configuration
|
|
|
|
```yaml
|
|
scrape_configs:
|
|
- job_name: 'hindsight'
|
|
static_configs:
|
|
- targets: ['localhost:8888']
|
|
```
|
|
|
|
## Example Queries
|
|
|
|
### Average operation latency by type
|
|
```promql
|
|
rate(hindsight_operation_duration_sum[5m]) / rate(hindsight_operation_duration_count[5m])
|
|
```
|
|
|
|
### LLM calls per minute by provider
|
|
```promql
|
|
rate(hindsight_llm_calls_total[1m]) * 60
|
|
```
|
|
|
|
### P95 LLM latency
|
|
```promql
|
|
histogram_quantile(0.95, rate(hindsight_llm_duration_bucket[5m]))
|
|
```
|
|
|
|
### Total tokens consumed by model
|
|
```promql
|
|
sum by (model) (hindsight_llm_tokens_input_total + hindsight_llm_tokens_output_total)
|
|
```
|
|
|
|
### Internal vs API recall operations
|
|
```promql
|
|
sum by (source) (rate(hindsight_operation_total{operation="recall"}[5m]))
|
|
```
|
|
|
|
### HTTP requests per second by endpoint
|
|
```promql
|
|
sum by (endpoint) (rate(hindsight_http_requests_total[1m]))
|
|
```
|
|
|
|
### HTTP error rate (5xx)
|
|
```promql
|
|
sum(rate(hindsight_http_requests_total{status_class="5xx"}[5m])) / sum(rate(hindsight_http_requests_total[5m]))
|
|
```
|
|
|
|
### P95 HTTP latency
|
|
```promql
|
|
histogram_quantile(0.95, sum by (le) (rate(hindsight_http_duration_seconds_bucket[5m])))
|
|
```
|
|
|
|
### Database pool utilization
|
|
```promql
|
|
hindsight_db_pool_size / hindsight_db_pool_max
|
|
```
|
|
|
|
### Active database connections
|
|
```promql
|
|
hindsight_db_pool_size - hindsight_db_pool_idle
|
|
```
|
|
|
|
### CPU usage rate
|
|
```promql
|
|
rate(hindsight_process_cpu_seconds{type="user"}[1m])
|
|
```
|
|
|
|
---
|
|
|
|
## Distributed Tracing
|
|
|
|
Hindsight supports OpenTelemetry distributed tracing for memory operations and LLM calls, following GenAI semantic conventions v1.37+.
|
|
|
|
### Configuration
|
|
|
|
See [Configuration - OpenTelemetry Tracing](./configuration#opentelemetry-tracing) for environment variables.
|
|
|
|
**Quick Start:**
|
|
```bash
|
|
# Enable tracing
|
|
export HINDSIGHT_API_OTEL_TRACES_ENABLED=true
|
|
export HINDSIGHT_API_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
|
|
|
# View traces with Grafana LGTM (local dev)
|
|
./scripts/dev/start-monitoring.sh
|
|
# Open http://localhost:3000 → Explore → Tempo
|
|
```
|
|
|
|
Supports any OTLP-compatible backend (Grafana LGTM, Langfuse, OpenLIT, DataDog, New Relic, Honeycomb, etc.).
|
|
|
|
### Span Hierarchy
|
|
|
|
**Parent Spans (Operations):**
|
|
- `hindsight.retain` - Memory ingestion
|
|
- `hindsight.recall` - Memory retrieval
|
|
- `hindsight.recall_embedding` - Query embedding
|
|
- `hindsight.recall_retrieval` - Parallel search (semantic, BM25, graph, temporal)
|
|
- `hindsight.recall_fusion` - Reciprocal Rank Fusion
|
|
- `hindsight.recall_rerank` - Cross-encoder reranking
|
|
- `hindsight.reflect` - Agentic reasoning
|
|
- `hindsight.reflect_tool_call` - Tool execution (recall, lookup, etc.)
|
|
- `hindsight.consolidation` - Observation synthesis
|
|
- `hindsight.mental_model_refresh` - Mental model updates
|
|
|
|
**Child Spans (LLM Calls):**
|
|
- Named by scope (e.g., `hindsight.memory`, `hindsight.reflect`)
|
|
- Contain full prompts/completions as events
|
|
- Follow GenAI semantic conventions for attributes
|
|
|
|
### Span Attributes
|
|
|
|
**Operation Spans:**
|
|
- `hindsight.operation` - Operation type
|
|
- `hindsight.bank_id` - Memory bank ID
|
|
- `hindsight.query` - Query text (truncated to 100 chars)
|
|
- `hindsight.fact_types` - Fact types for recall
|
|
- `hindsight.thinking_budget` - Budget allocation
|
|
- `hindsight.max_tokens` - Token limit
|
|
|
|
**LLM Spans (GenAI Semantic Conventions):**
|
|
- `gen_ai.operation.name` - Always `"chat"`
|
|
- `gen_ai.provider.name` - Provider (`openai`, `anthropic`, `google`, etc.)
|
|
- `gen_ai.request.model` - Model name
|
|
- `gen_ai.usage.input_tokens` - Input tokens
|
|
- `gen_ai.usage.output_tokens` - Output tokens
|
|
- `hindsight.scope` - LLM call purpose (`memory`, `reflect`, `consolidation`, etc.)
|
|
|
|
**Events:**
|
|
- `gen_ai.client.inference.operation.details` - Full prompts and completions
|