Noesis / Integrations
WebSocket client
Connect to Noesis from your own code in Python or Node.js, and the relay helper the telephony guides use.
Use these clients when your own code has the call's audio, for example a media server, a softphone or a voice bot. Each opens one socket per call, sends the callee's audio, and passes every answer to your code.
Python
Needs pip install websockets (version 14 or later).
# noesis_client.py: one Noesis socket per call
import asyncio, json, os
import websockets
STREAM = "wss://api.noesis.detectif.ai/v1/detect/stream"
HEADERS = {"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}"}
async def analyse_call(frames, call_id, on_answer, encoding="mulaw", sample_rate=8000):
"""frames: an async iterator of the callee's audio, 20 to 100 ms per frame."""
url = f"{STREAM}?encoding={encoding}&sample_rate={sample_rate}&call_id={call_id}"
try:
ws = await websockets.connect(url, additional_headers=HEADERS)
except Exception:
return # never block the call on Noesis
async def answers():
try:
async for raw in ws:
event = json.loads(raw)
if event["type"] in ("classification", "summary"):
await on_answer(event)
except websockets.ConnectionClosed:
pass
reader = asyncio.create_task(answers())
try:
async for frame in frames:
await ws.send(frame)
await ws.send(json.dumps({"type": "end"}))
except websockets.ConnectionClosed:
pass # Noesis closed early: the call carries on
try:
await asyncio.wait_for(reader, timeout=10) # the summary arrives after "end"
except asyncio.TimeoutError:
await ws.close()Node.js
Needs Node.js 18 or later and npm install ws.
// noesis-client.mjs: one Noesis socket per call
import WebSocket from "ws";
const STREAM = "wss://api.noesis.detectif.ai/v1/detect/stream";
export function analyseCall({ callId, onAnswer, encoding = "mulaw", sampleRate = 8000 }) {
const url = `${STREAM}?encoding=${encoding}&sample_rate=${sampleRate}&call_id=${encodeURIComponent(callId)}`;
const ws = new WebSocket(url, { headers: { Authorization: `Bearer ${process.env.DETECTIF_API_KEY}` } });
ws.on("error", () => {}); // never block the call on Noesis
ws.on("message", (raw) => {
const event = JSON.parse(raw);
if (event.type === "classification" || event.type === "summary") onAnswer(event);
});
return {
send(frame) { if (ws.readyState === WebSocket.OPEN) ws.send(frame); },
end() { if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: "end" })); },
};
}Use it from your media handler:
const noesis = analyseCall({ callId, onAnswer: (e) => console.log(e.label) });
// for each frame of the callee's audio:
noesis.send(frame);
// when the call ends:
noesis.end();Browsers
A browser cannot set the Authorization header on a WebSocket, and must never hold an API key. Have your server create a stream token for the call, and open the socket with it:
const ws = new WebSocket(`wss://api.noesis.detectif.ai/v1/detect/stream?token=${token}&encoding=pcm_s16le&sample_rate=16000`);Relay helper
Most telephony platforms send a call's audio to a WebSocket or TCP server that you run. The Telnyx, Exotel, Vonage, FreeSWITCH, Asterisk, Twilio and Plivo guides run a small relay: it receives the platform's stream, forwards the callee's audio to Noesis, and passes the answers to your code. They share this helper:
# noesis_relay.py: forward one call's audio to Noesis and pass its answers to your code
import asyncio, json, os
from urllib.parse import urlencode
import websockets
STREAM = "wss://api.noesis.detectif.ai/v1/detect/stream"
HEADERS = {"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}"}
class NoesisCall:
"""One call. Never raises: if Noesis is unreachable or closes, the call carries on."""
def __init__(self, on_answer, **params):
self.on_answer = on_answer
self.query = urlencode({k: v for k, v in params.items() if v is not None})
self.ws = self.reader = None
async def open(self):
try:
self.ws = await websockets.connect(f"{STREAM}?{self.query}", additional_headers=HEADERS)
except Exception:
self.ws = None
return
self.reader = asyncio.create_task(self._read())
async def _read(self):
try:
async for raw in self.ws:
event = json.loads(raw)
if event["type"] in ("classification", "summary", "error"):
await self.on_answer(event)
except websockets.ConnectionClosed:
pass
async def send(self, data):
"""Binary audio, or a JSON message as text."""
if self.ws is None:
return
try:
await self.ws.send(data)
except websockets.ConnectionClosed:
self.ws = None
async def close(self):
await self.send(json.dumps({"type": "end"}))
if self.reader is not None:
try:
await asyncio.wait_for(self.reader, timeout=10) # the summary follows "end"
except asyncio.TimeoutError:
pass
if self.ws is not None:
await self.ws.close()on_answer receives each classification, the summary and any error event. The relay must be reachable from the platform over wss:// (or TCP for Asterisk), for example behind your load balancer or a TLS proxy.