You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 03:10:54