AI代理加入通话无响应排查:Webhook测试无错误提示问题
问题描述
将AI代理加入通话后无响应,使用webhook测试,已确认OpenAI账户有可用额度,但未收到任何错误提示。
运行应用及webhook的命令:
ngrok http --domain=nicolasa-unequivalent-temperance.ngrok-free.dev 3000
相关代码(src/app/api/webhook/route.ts):
import { and, ConsoleLogWriter, eq, not } from "drizzle-orm"; import { NextRequest, NextResponse } from "next/server"; import { CallEndedEvent, MessageNewEvent, CallTranscriptionReadyEvent, CallSessionParticipantLeftEvent, CallRecordingReadyEvent, CallSessionStartedEvent } from "@stream-io/node-sdk"; import { db } from "@/db"; import { agents, meetings } from "@/db/schema"; import { streamVideo } from "@/lib/stream-video"; import { inngest } from "@/inngest/client"; function verifySignatureWithSDK(body: string, signature: string): boolean { return streamVideo.verifyWebhook(body, signature); } export async function POST(req: NextRequest) { const signature = req.headers.get("x-signature"); const apiKey = req.headers.get("x-api-key"); if (!signature || !apiKey) { return NextResponse.json( { error: "Missing signature or API key" }, { status: 400 } ); } const body = await req.text(); if (!verifySignatureWithSDK(body, signature)) { return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); } let payload: unknown; try { payload = JSON.parse(body) as Record<string, unknown>; } catch { return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }) } const eventType = (payload as Record<string, unknown>)?.type; // 1 console.log(`[Webhook] Received event: ${eventType}`); if (eventType === "call.session_started") { const event = payload as CallSessionStartedEvent; const meetingId = event.call.custom?.meetingId; // 2 console.log(`[Webhook] Session Started. Meeting ID: ${meetingId}`); // <-- LOG 2: Check Meeting ID if (!meetingId) { return NextResponse.json({ error: "Missing meetingId" }, { status: 400 }); } const [existingMeeting] = await db .select() .from(meetings) .where( and( eq(meetings.id, meetingId), not(eq(meetings.status, "completed")), not(eq(meetings.status, "active")), not(eq(meetings.status, "cancelled")), not(eq(meetings.status, "processing")) ) ); if (!existingMeeting) { // 3 console.error(`[Webhook ERROR] Meeting not found for ID: ${meetingId}`); return NextResponse.json({ error: "Meeting not found" }, { status: 404 }); } // 4 console.log(`[Webhook] Found Meeting in DB. Agent ID: ${existingMeeting.agentId}`); // <-- LOG 3: Confirm DB lookup await db .update(meetings) .set({ status: "active", startedAt: new Date(), }) .where(eq(meetings.id, existingMeeting.id)); const [existingAgent] = await db .select() .from(agents) .where(eq(agents.id, existingMeeting.agentId)); if (!existingAgent) { // 5 console.error(`[Webhook ERROR] Agent not found for ID: ${existingMeeting.agentId}`); return NextResponse.json({ error: "Agent not found" }, { status: 404 }); } // <-- CRITICAL LOGS 4 & 5: Check instructions and key existence const instructions = existingAgent.instructions; console.log(instructions); console.log(`[Webhook] Found Agent: ${existingAgent.id}. Instructions Length: ${instructions?.length ?? 0}`); const call = streamVideo.video.call("default", meetingId); console.log(`[Webhook] Calling connectOpenAi...`); // <-- LOG 6: Before SDK call const realtimeClient = await streamVideo.video.connectOpenAi({ call, openAiApiKey: process.env.OPENAI_API_KEY!, agentUserId: existingAgent.id, model:"gpt-4o-realtime-preview-2025-06-03", }); console.log(`[Webhook] connectOpenAi SUCCESS`); console.log(`[Webhook] connectOpenAi SUCCESS. Updating session instructions...`); // <-- LOG 7: After SDK call await realtimeClient.updateSession({ instructions: existingAgent.instructions, }); realtimeClient.on("conversation.item.input_audio_transcription_completed", (event:any) => { console.log(`[Webhook] User said: ${event.transcript}`); }); realtimeClient.on("conversation.item.created", (event:any) => { console.log(`[Webhook] Agent response:`, event); }); console.log(`[Webhook] Agent setup complete!`); } else if (eventType === "call.session_participant_left") { const event = payload as CallSessionParticipantLeftEvent; const meetingId = event.call_cid.split(":")[1]; console.log(`[Webhook] Handled participant left event.`); if (!meetingId) { return NextResponse.json({ error: "Missing meetingId" }, { status: 400 }); } const call = streamVideo.video.call("default", meetingId); await call.end(); } else if (eventType === "call.session_ended") { const event = payload as CallEndedEvent; const meetingId = event.call.custom?.meetingId; if (!meetingId) { return NextResponse.json({ error: "Missing meetingId" }, { status: 400 }); } await db .update(meetings) .set({ status: "processing", endedAt: new Date(), }) .where(and(eq(meetings.id, meetingId), eq(meetings.status, "active"))); } else if (eventType === "call.transcription_ready") { const event = payload as CallTranscriptionReadyEvent; const meetingId = event.call_cid.split(":")[1]; const [updatedMeeting] = await db .update(meetings) .set({ transcriptUrl: event.call_transcription.url, }) .where(eq(meetings.id, meetingId)) .returning(); // TODO: call Inngest background job to summarize the transcript if (!updatedMeeting) { return NextResponse.json({ error: "Meeting not found" }, { status: 400 }); } } else if (eventType === "call.recording_ready") { const event = payload as CallRecordingReadyEvent; const meetingId = event.call_cid.split(":")[1]; await db .update(meetings) .set({ recordingUrl: event.call_recording.url, }) .where(eq(meetings.id, meetingId)); } return NextResponse.json({ status: "ok" }); }
使用的版本信息:
"openai": "^6.6.0", "@stream-io/node-sdk": "^0.4.24", "@stream-io/openai-realtime-api": "^0.3.3", "@stream-io/video-react-sdk": "^1.18.0"
排查建议
检查OpenAI模型权限与API密钥
确认你的OpenAI账户已开通gpt-4o-realtime-preview-2025-06-03模型的访问权限,该模型需要单独申请。在connectOpenAi调用前添加日志验证API密钥是否正常加载:console.log("OpenAI API Key loaded:", !!process.env.OPENAI_API_KEY);补全错误监听逻辑
当前代码未监听实时客户端的错误事件,容易遗漏连接异常。添加以下代码捕获错误:realtimeClient.on("error", (error) => { console.error("[Webhook] Realtime client error:", error); });验证Agent指令有效性
确认existingAgent.instructions不为空且格式正确,若指令为空,AI代理没有响应逻辑。可临时替换为测试指令验证:await realtimeClient.updateSession({ instructions: "用户说话后请立即给出简短回应" });检查会话连接状态
添加日志查看实时客户端的连接状态,确认是否成功建立WebSocket连接:console.log("Realtime client state:", realtimeClient.connectionState);版本兼容性排查
当前openai@6.6.0与@stream-io/openai-realtime-api@0.3.3可能存在兼容性问题,尝试将openai降级到^4.0.0(Stream官方推荐版本),或升级Stream相关SDK到最新稳定版。网络连通性验证
确认服务器/ngrok代理能正常访问OpenAI实时API的WebSocket端点(wss://api.openai.com/v1/realtime),避免防火墙或网络策略阻断连接。
内容的提问来源于stack exchange,提问作者Abdisa
相关产品推荐
相关产品推荐

