cf works搞定你的coze.cn api

不废话了,先上代码

const FOLLOW_UP_ENABLED = true; // 将此值设置为 true 或 false 以控制是否输出 follow_up 内容

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

const encoder = new TextEncoder();

const API_URL = "https://api.coze.cn/open_api/v2/chat";

async function handleRequest(request) {
  if (request.method === "OPTIONS") {
    return handleOptionsRequest(request);
  }

  if (request.method !== "POST") {
    return new Response("OK", { status: 200 });
  }

  const authorizationHeader = request.headers.get("Authorization");
  if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) {
    return new Response("未提供有效的API密钥", { status: 200 });
  }
  const apiKey = authorizationHeader.slice(7);

  const requestBody = await request.json();
  const messages = requestBody.messages;
  const isStream = requestBody.stream;
  const model = requestBody.model;

  const newRequestBody = {
    conversation_id: "123",
    bot_id: model,
    user: "29032201862555",
    query: messages[messages.length - 1].content,
    stream: isStream,
    chat_history: messages.slice(0, -1).map(message => ({
      role: message.role,
      content: message.content,
      content_type: "text"
    }))
  };

  const response = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${apiKey}`
    },
    body: JSON.stringify(newRequestBody)
  });

  const corsHeaders = {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",
    "Access-Control-Allow-Headers": "*",
    "Access-Control-Allow-Credentials": "true",
  };

  if (isStream) {
    let decoder = new TextDecoder();
    let reader = response.body.getReader();
    let contentBuffer = "";
    let followUps = [];

    return new Response(new ReadableStream({
      async start(controller) {
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;

          let chunk = decoder.decode(value);
          contentBuffer += chunk;

          while (contentBuffer.includes("\n")) {
            const newlineIndex = contentBuffer.indexOf("\n");
            const line = contentBuffer.slice(0, newlineIndex);
            contentBuffer = contentBuffer.slice(newlineIndex + 1);

            if (line.startsWith("data:")) {
              try {
                const data = JSON.parse(line.slice(5));
                if (data.event === "message") {
                  const message = data.message;
                  if (message.type === "follow_up" && FOLLOW_UP_ENABLED) {
                    followUps.push(message.content);
                  } else if (message.type !== "verbose" && message.type !== "follow_up" && message.type !== "tool_response") {
                    const formattedData = {
                      id: "chatcmpl-" + Math.random().toString(36).slice(2),
                      object: "chat.completion.chunk",
                      created: Math.floor(Date.now() / 1000),
                      model: model,
                      choices: [{
                        index: 0,
                        delta: {
                          content: message.content
                        },
                        finish_reason: data.is_finish ? "stop" : null
                      }]
                    };
                    controller.enqueue(encoder.encode(`data: ${JSON.stringify(formattedData)}\n\n`));
                  }
                }
              } catch (error) {
                console.error("解析JSON时出错:", error, "原始数据:", line);
              }
            }
          }
        }

        if (FOLLOW_UP_ENABLED && followUps.length > 0) {
          const followUpText = "\n----follow_up----\n" + followUps.join("\n");
          const followUpData = {
            id: "chatcmpl-" + Math.random().toString(36).slice(2),
            object: "chat.completion.chunk",
            created: Math.floor(Date.now() / 1000),
            model: model,
            choices: [{
              index: 0,
              delta: {
                content: followUpText
              },
              finish_reason: "stop"
            }]
          };
          controller.enqueue(encoder.encode(`data: ${JSON.stringify(followUpData)}\n\n`));
        }

        const doneData = {
          id: "chatcmpl-" + Math.random().toString(36).slice(2),
          object: "chat.completion.chunk",
          created: Math.floor(Date.now() / 1000),
          model: model,
          choices: [{
            index: 0,
            delta: {},
            finish_reason: "stop"
          }]
        };
        controller.enqueue(encoder.encode(`data: ${JSON.stringify(doneData)}\n\n`));
        controller.enqueue(encoder.encode(`data: [DONE]\n\n`));

        controller.close();
      }
    }), {
      headers: { ...corsHeaders, "Content-Type": "text/event-stream" }
    });
  } else {
    const responseText = await response.text();
    const data = JSON.parse(responseText);
    
    const answerMessages = data.messages.filter(message => message.type !== "verbose" && message.type !== "follow_up" && message.type !== "tool_response");
    const followUpMessages = FOLLOW_UP_ENABLED ? data.messages.filter(message => message.type === "follow_up") : [];
    
    const formattedData = {
      id: "chatcmpl-" + Math.random().toString(36).slice(2),
      object: "chat.completion",
      created: Math.floor(Date.now() / 1000),
      model: model,
      choices: [{
        index: 0,
        message: {
          role: "assistant",
          content: answerMessages.map(message => message.content).join("") +
            (FOLLOW_UP_ENABLED && followUpMessages.length > 0 ? "\n----follow_up----\n" + followUpMessages.map(message => message.content).join("\n") : "")
        },
        finish_reason: "stop"
      }],
      usage: {
        prompt_tokens: JSON.stringify(newRequestBody).length,
        completion_tokens: answerMessages.reduce((sum, message) => sum + message.content.length, 0) +
          (FOLLOW_UP_ENABLED && followUpMessages.length > 0 ? followUpMessages.reduce((sum, message) => sum + message.content.length, 0) + 20 : 0),
        total_tokens: JSON.stringify(newRequestBody).length +
          answerMessages.reduce((sum, message) => sum + message.content.length, 0) +
          (FOLLOW_UP_ENABLED && followUpMessages.length > 0 ? followUpMessages.reduce((sum, message) => sum + message.content.length, 0) + 20 : 0)
      }
    };
    return new Response(JSON.stringify(formattedData), {
      headers: { ...corsHeaders, "Content-Type": "application/json" }
    });
  }
}

function handleOptionsRequest(request) {
  const headers = {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",
    "Access-Control-Allow-Headers": "*",
    "Access-Control-Allow-Credentials": "true",
    "Access-Control-Max-Age": "43200",
  };

  return new Response(null, {
    status: 204,
    headers: headers,
  });
}

将bot_id作为模型传进去就行,api用自己申请的
放个演示站
https://worker-proud-sea-coze.qwer202171.workers.dev/

31 个赞

顶!

2 个赞

先点赞

2 个赞

别着急部署,还有一点小修,稍等

2 个赞

提醒一下

'type'=='tool_response'

这个的content最好忽略掉,一般都特别长(用搜索的话是这样)

2 个赞

这个是什么参数,我只对FOLLOW_UP进行了处理,可选开关,已更新,可以自己部署了

1 个赞

太强了

1 个赞

楼主牛逼,请问 coze.com 有没有办法?

1 个赞

我没用过国际版coze的api,我是听说国服api免费就搓了个对接oneapi了,至于画图语音什么的我没用过也不知道咋改,等我有需求了再更新

1 个赞

bot用了插件的话是插件的返回吧,这东西混在响应的流前面,它的content不处理容易加在结果上。

if (message.type !== "verbose" && message.type !== "follow_up")

这样判断的话

message.type =='tool_response'

会把插件的content也带上。

1 个赞

太强了凹凸曼!

我没加上插件,这个工作量就比较大了,我目前只适配了默认发布的文本模型,等我试用后再说,还有就是比较担心这个后期收费,不太想花太多时间

细心一点会发现都是基于我的授人以渔模板改来的,代码全程都是ai改的:rofl::rofl:

佬,我的意思是改一下那个if判断就行

反正就是强

coze.cn 这个注册还得手机号,不敢搞啊

你是说如果加了插件会多个tool_response类型的数据,要忽略掉是吧

1 个赞

是的佬,是这样的

不一定要+86,+1也能注册

已更新

1 个赞