shreesha345/Rudra-One
GitHub: shreesha345/Rudra-One
RudraOne 是一个基于 SigNoz 深度可观测性的 AI 语音紧急调度系统,旨在解决实时紧急呼叫处理中的毫秒级延迟定位与端到端链路追踪问题。
Stars: 0 | Forks: 0
# RudraOne — 具备深度 SigNoz 可观测性的 AI 语音紧急调度系统
RudraOne 是一个实时、低延迟的语音紧急调度 AI agent。它旨在处理关键的 911 呼叫、解析事件报告、对紧急情况进行地理定位,并调度附近的机构。为了保证在生死攸关的场景中所需的毫秒级可靠性,整个应用深度集成了 **OpenTelemetry (OTel)**,将指标、分布式 trace 和相关日志导出到 **SigNoz**。
## 📺 项目演示

*(此处将附带一段视频演示,展示实时语音通话、实时调度地图以及 SigNoz 内部的关联 trace 诊断)*
## 🚨 问题陈述
紧急响应语音 agent 对延迟的要求极其严格。处于危机中的人无法等待反应迟钝的 AI。
RudraOne 运行着一个复杂的异步 pipeline:
1. **实时音频流**:浏览器 WebSockets 将音频流传输到后端。
2. **实时语音转写**:后端将分块音频流传输到 Deepgram (STT)。
3. **LLM 推理**:FastAPI 后端向兼容 OpenAI 的 LLM(例如 OpenAI、DeepSeek 或 Vultr)发送 prompt,以对呼叫进行分类、提取位置上下文并生成响应。
4. **语音合成**:通过 ElevenLabs (TTS) 将 LLM 响应转换为音频。
5. **数据库事务**:呼叫转写、位置和调度详情异步保存到 PostgreSQL。
如果通话卡顿或出现延迟,标准的 APM 设置无法隔离瓶颈。我们必须立即知道:是网络 socket 的问题?Deepgram 转写延迟?LLM 响应生成?ElevenLabs 语音合成?还是向 PostgreSQL 写入 SQL 过慢?
## 💡 解决方案:为什么选择 SigNoz?
为了解决这些延迟和可靠性挑战,我们选择了 **SigNoz** 作为统一的可观测性平台。
### 为什么 SigNoz 是最佳选择:
1. **开源且易于部署**:与需要复杂入驻流程、闭源 agent 和许可协议的专有 APM 供应商(Datadog、New Relic)不同,SigNoz 是完全开源的。使用 **SigNoz Foundry (`foundryctl`)**,我们可以使用单个声明式配置文件 (`casting.yaml`) 在几秒钟内在本地建立完整的 SigNoz 技术栈(ClickHouse 数据库、OpenTelemetry Collector、Query-Service 和前端)。
2. **AI 原生可观测性**:自定义遥测标签支持对现代 AI 原生应用进行监控。通过 trace LLM prompt、completion 使用情况、token 指标和活动模型属性,SigNoz 为我们 AI pipeline 的内部工作原理提供了完全的透明度。
3. **深度 APM 与自定义 AI Span**:SigNoz 对自定义 OpenTelemetry span 的原生支持,使我们能够在单个端到端的分布式 trace 中 trace LLM completion、TTS 音频生成以及浏览器用户点击。
4. **日志到 Trace 的关联**:通过将活动的 trace ID 直接嵌入到我们的后端 logger 中,SigNoz 将日志映射到其起源的 span。当呼叫失败时,我们可以点击 SigNoz Logs Explorer 中的错误日志,并立即跳转到确切的 span 火焰图,以检查数据库查询或 API payload。
5. **无供应商锁定**:完全基于 OpenTelemetry 标准构建。如果我们扩大规模,可以重新指向我们的遥测 exporter endpoint,而无需更改任何一行应用代码。
## 🛠️ 使用的可观测性功能
### 1. 分布式追踪与服务地图
* **React Frontend**:自动检测浏览器 HTTP 请求和用户点击,将 W3C 上下文头部(`traceparent`)传播到后端。
* **FastAPI 后端**:读取上下文头部以将浏览器操作与服务端路由关联,自动生成统一的 trace 火焰图并构建 **SigNoz Service Map**。
* **PostgreSQL 数据库**:自动检测每一个 ORM 引擎事件和原始驱动操作(`asyncpg`),在 trace 时间线中直接暴露慢查询。
### 2. 自定义 APM 与 AI Token 使用看板
我们构建了一个自定义的 V5 看板来监控整个调度 pipeline:
* **KPI 指标**:总请求数、错误数和平均后端执行延迟(毫秒)。
* **Token 跟踪**:随时间跟踪 **Prompt**、**Completion** 和 **消耗的 LLM 总 token 数**的数值图和折线图。
* **响应延迟**:实时跟踪每分钟请求持续时间的图表。
* **Top Endpoint 表**:显示最活跃的 FastAPI 路由、它们的平均延迟和错误率。
### 3. 日志浏览器与日志查询构建器
后端日志记录格式化为注入 OTel 上下文标签:`[otelTraceID=... otelSpanID=...]`。使用日志查询构建器,我们可以按 `serviceName = 'rudraone-backend'` 进行过滤,按日志级别进行查询,并检查相关的请求历史记录。
### 4. 异常分析
任何未处理的异常、数据库事务冲突或 API 配额(例如 ElevenLabs 配额限制或 Twilio API 超时)都会作为 span 事件被捕获,并在 SigNoz 的 **Exceptions** 选项卡中报告完整的 Python 堆栈跟踪。
## 🏗️ 技术深入剖析:架构流程
以下是紧急语音调度 pipeline 的端到端执行序列。它突出了用户和操作员交互如何通过网络和数据库层进行传播,以及 **SigNoz** 如何在每一步异步收集分布式 trace、自定义属性和相关日志:
```
sequenceDiagram
autonumber
actor Caller
participant Twilio
participant Frontend as Frontend Dispatcher
participant Backend as FastAPI Backend
participant DB as PostgreSQL Database
participant Deepgram as Deepgram (STT)
participant TTS as ElevenLabs/Sarvam (TTS)
participant Services as Emergency Services
participant SigNoz as SigNoz Observability
Caller->>Twilio: Dials emergency number
Twilio->>Backend: POST /twiml (Incoming Call Webhook)
Note over Backend: Starts OTel Span: "/twiml"
Backend-->>SigNoz: Async export HTTP ingress traces
Backend->>Twilio: TwiML Response ( )
Twilio->>Backend: Opens WebSocket Stream (Sends raw µ-law audio)
Note over Backend: Starts OTel Span: WebSocket connection
loop Real-time Audio Processing
Backend->>Deepgram: Stream PCM16 audio
Deepgram->>Backend: Transcript (JSON)
Backend->>Frontend: Broadcast transcript over WebSocket
Backend->>DB: Asynchronously save partial transcripts
Note over DB: Tracks SQL execution duration
DB-->>SigNoz: Export DB operation spans
end
Backend->>Backend: Detect language mismatch
alt Translation Required
Backend->>TTS: Request translated dispatcher audio
Note over Backend: Traces translation API latency
TTS->>Backend: Returns translated audio
Backend->>Twilio: Play translated audio to Caller
Twilio->>Caller: Audio played
else No Translation
Backend->>Twilio: Play dispatcher audio to Caller
Twilio->>Caller: Audio played
end
Note over Backend,SigNoz: During the call, custom spans are exported with model and token usage attributes
Backend-->>SigNoz: Export LLM usage metrics (prompt, completion, total tokens)
Frontend->>Backend: Clicks "Send Location SMS to Caller"
Note over Frontend: Traces browser click event
Frontend-->>SigNoz: Export frontend trace context
Backend->>Twilio: Send SMS with live location link
Twilio->>Caller: SMS with link
Caller->>Backend: Opens link & shares live location (POST /location)
Backend->>Frontend: Updates dashboard with live location
Frontend->>Backend: Clicks "Send Emergency Alert"
Backend->>Twilio: POST /sms/emergency
Twilio->>Services: SMS with incident details & caller location
Frontend->>Backend: Clicks "Emergency Call"
Backend->>Twilio: POST /call/emergency (Outbound call)
Twilio->>Services: Outbound automated emergency call
Note over Backend,SigNoz: Unhandled exceptions are captured as span events
Backend-->>SigNoz: Export exception details & trace stack logs
```
## 🚀 快速开始与设置
### 前置条件
* **Docker & Docker Compose**
* **foundryctl** (SigNoz Foundry CLI)
### 1. 环境设置
根据模板在根目录下创建一个 `.env` 文件:
```
cp .env.example .env
```
打开 `.env` 并填写您的凭证:
* **LLM 配置**:设置 `LLM_API_KEY`、`LLM_BASE_URL`(例如 OpenAI 或 Vultr)和 `LLM_MODEL`。
* **Twilio**:`TWILIO_ACCOUNT_SID`、`TWILIO_AUTH_TOKEN`、`TWILIO_PHONE_NUMBER`,用于语音呼叫/短信路由。
* **语音 API**:`DEEPGRAM_API_KEY` (STT) 和 `ELEVENLABS_API_KEY` (TTS)。
* **Mapbox**:`VITE_MAPBOX_TOKEN`,用于在 React 前端渲染地图图形。
### 2. 快速开始:启动技术栈
您可以同时启动 SigNoz 和 RudraOne:
* **对于 Windows (PowerShell)**:
foundryctl cast -f casting.yaml; docker compose up -d --build
* **对于 Linux / macOS (Bash)**:
foundryctl cast -f casting.yaml && docker compose up -d --build
### 3. 自动化数据填充(零配置部署)
为了使我们的部署对评委和开发者具有 100% 的可重复性,我们的后端应用被编程为带有 **自动数据库填充脚本** (`backend/signoz_seeder.py`)。
运行 `docker compose up` 后,后端生命周期启动钩子将自动连接到 SigNoz Postgres 元数据存储并:
1. 创建一个名为 `agent` 的服务账号。
2. 将 `agent` 服务账号绑定到 `signoz-admin` 角色。
3. 填充本地 Service Account API key(由 SigNoz MCP Server 使用)。
4. 自动导入 **"RudraOne AI Agent & APM Monitor"** 看板。
一旦容器健康运行,您就可以立即访问预配置的看板:
👉 **[APM 看板 URL](http://localhost:8080/dashboard/019f5a94-8617-7b7d-bc8c-e19f1ee892d0)**
## 🔗 Endpoint 参考
| 服务 | URL | 描述 |
| :--- | :--- | :--- |
| 🖥️ **RudraOne Frontend** | `http://localhost:8082` | React 调度看板和跟踪地图。 |
| ⚙️ **RudraOne Backend API** | `http://localhost:8000` | FastAPI 根 endpoint。 |
| 📖 **API 交互式文档** | `http://localhost:8000/docs` | Swagger API 测试 UI。 |
| 📊 **SigNoz 可观测性 UI** | `http://localhost:8080` | 统一的可观测性看板。 |
## 🗃️ 自定义看板 JSON 模板
如果您需要手动将看板配置导入到不同的 SigNoz 环境中:
1. 在 SigNoz UI 中导航到 **Dashboards**。
2. 点击 **+ New Dashboard** -> **Import JSON**。
3. 复制并粘贴以下 JSON schema:
```
{
"title": "RudraOne AI Agent & APM Monitor",
"name": "RudraOne AI Agent & APM Monitor",
"description": "Comprehensive APM monitoring for the RudraOne emergency dispatch AI backend — request rates, latency percentiles, endpoint breakdown, DB queries, error tracking, and LLM token usage.",
"tags": ["rudraone", "apm", "ai-agent"],
"uploadedGrafana": false,
"version": "v5",
"variables": {},
"panelMap": {},
"layout": [
{"i": "panel-request-rate", "x": 0, "y": 0, "w": 3, "h": 2},
{"i": "panel-error-count", "x": 3, "y": 0, "w": 3, "h": 2},
{"i": "panel-avg-latency", "x": 6, "y": 0, "w": 3, "h": 2},
{"i": "panel-total-tokens-value", "x": 9, "y": 0, "w": 3, "h": 2},
{"i": "panel-latency-ts", "x": 0, "y": 2, "w": 6, "h": 5},
{"i": "panel-request-ts", "x": 6, "y": 2, "w": 6, "h": 5},
{"i": "panel-token-usage", "x": 0, "y": 7, "w": 12, "h": 5},
{"i": "panel-top-endpoints", "x": 0, "y": 12, "w": 12, "h": 5}
],
"widgets": [
{
"id": "panel-request-rate",
"title": "Total Request Count",
"description": "Total number of trace spans in the selected time range",
"panelTypes": "value",
"query": {
"queryType": "clickhouse_sql",
"clickhouse_sql": [
{
"name": "A",
"query": "SELECT toFloat64(count()) as value FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND timestamp >= $start_datetime AND timestamp <= $end_datetime",
"legend": "Requests",
"disabled": false
}
]
}
},
{
"id": "panel-error-count",
"title": "Error Count",
"description": "Total number of spans with errors",
"panelTypes": "value",
"query": {
"queryType": "clickhouse_sql",
"clickhouse_sql": [
{
"name": "A",
"query": "SELECT toFloat64(countIf(has_error = true)) as value FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND timestamp >= $start_datetime AND timestamp <= $end_datetime",
"legend": "Errors",
"disabled": false
}
]
}
},
{
"id": "panel-avg-latency",
"title": "Avg Latency (ms)",
"description": "Average span duration across all endpoints",
"panelTypes": "value",
"query": {
"queryType": "clickhouse_sql",
"clickhouse_sql": [
{
"name": "A",
"query": "SELECT avg(durationNano) / 1000000 as value FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND timestamp >= $start_datetime AND timestamp <= $end_datetime",
"legend": "Latency (ms)",
"disabled": false
}
]
}
},
{
"id": "panel-total-tokens-value",
"title": "Total Tokens Consumed",
"description": "Total LLM tokens consumed in the selected time range",
"panelTypes": "value",
"query": {
"queryType": "clickhouse_sql",
"clickhouse_sql": [
{
"name": "A",
"query": "SELECT toFloat64(sum(attributes_number['llm.usage.total_tokens'])) as value FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND mapContains(attributes_number, 'llm.usage.total_tokens') AND timestamp >= $start_datetime AND timestamp <= $end_datetime",
"legend": "Total Tokens",
"disabled": false
}
]
}
},
{
"id": "panel-latency-ts",
"title": "Latency Over Time (Avg ms)",
"description": "Average latency per minute",
"panelTypes": "graph",
"query": {
"queryType": "clickhouse_sql",
"clickhouse_sql": [
{
"name": "A",
"query": "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) as time, avg(durationNano) / 1000000 as value FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND timestamp >= $start_datetime AND timestamp <= $end_datetime GROUP BY time ORDER BY time ASC",
"legend": "Avg Latency (ms)",
"disabled": false
}
]
}
},
{
"id": "panel-request-ts",
"title": "Request Count Over Time",
"description": "Number of requests per minute",
"panelTypes": "graph",
"query": {
"queryType": "clickhouse_sql",
"clickhouse_sql": [
{
"name": "A",
"query": "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) as time, toFloat64(count()) as value FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND timestamp >= $start_datetime AND timestamp <= $end_datetime GROUP BY time ORDER BY time ASC",
"legend": "Requests/min",
"disabled": false
}
]
}
},
{
"id": "panel-token-usage",
"title": "AI Token Usage Over Time",
"description": "Prompt, Completion, and Total tokens consumed by the AI Agent over time",
"panelTypes": "graph",
"query": {
"queryType": "clickhouse_sql",
"clickhouse_sql": [
{
"name": "A",
"query": "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) as time, sum(attributes_number['llm.usage.prompt_tokens']) as value FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND mapContains(attributes_number, 'llm.usage.prompt_tokens') AND timestamp >= $start_datetime AND timestamp <= $end_datetime GROUP BY time ORDER BY time ASC",
"legend": "Prompt Tokens",
"disabled": false
},
{
"name": "B",
"query": "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) as time, sum(attributes_number['llm.usage.completion_tokens']) as value FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND mapContains(attributes_number, 'llm.usage.completion_tokens') AND timestamp >= $start_datetime AND timestamp <= $end_datetime GROUP BY time ORDER BY time ASC",
"legend": "Completion Tokens",
"disabled": false
},
{
"name": "C",
"query": "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) as time, sum(attributes_number['llm.usage.total_tokens']) as value FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND mapContains(attributes_number, 'llm.usage.total_tokens') AND timestamp >= $start_datetime AND timestamp <= $end_datetime GROUP BY time ORDER BY time ASC",
"legend": "Total Tokens",
"disabled": false
}
]
}
},
{
"id": "panel-top-endpoints",
"title": "Top Endpoints by Request Count",
"description": "Most-called API endpoints with average latency and error count",
"panelTypes": "table",
"query": {
"queryType": "clickhouse_sql",
"clickhouse_sql": [
{
"name": "A",
"query": "SELECT httpRoute as endpoint, httpMethod as method, toFloat64(count()) as calls, avg(durationNano) / 1000000 as avg_latency_ms, toFloat64(countIf(has_error = true)) as errors FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName = 'rudraone-backend' AND httpRoute != '' AND timestamp >= $start_datetime AND timestamp <= $end_datetime GROUP BY httpRoute, httpMethod ORDER BY calls DESC LIMIT 20",
"legend": "Endpoints",
"disabled": false
}
]
}
}
]
}
```
标签:API集成, SigNoz, 人工智能, 可观测性, 实时音频流处理, 应急响应系统, 测试用例, 用户代理, 用户模式Hook绕过, 自动化攻击, 语音识别与合成, 请求拦截