猿媒BOT OpenAPI 功能只能使用平台余额

基础配置

baseUrl: "https://bot.apetd.com/api/openapi"
headers: {
    apikey: "yuanmei-r5lbdhot32hvcfd1wg12"
}

接口

应用

V1应用对话(将于7月18日弃用,请更换V2版)

通过 api 形式去请求应用进行对话。目前 gpt 模型需要完整传入 prompts,claude可以只传 chatId。

请求

curl --location 'https://bot.apetd.com/api/openapi/chat/chat' \
--header 'apikey: 63f9a12228d2a638d839e11b-r5lbdhot32h33fd1wg12' \
--header 'Content-Type: application/json' \
--data '{
    "chatId": "可选,claude才有效",
    "modelId": "642989b537d6e4b98e4a1037",
    "isStream": false,
    "prompts": [
        {
            "obj": "System", 
            "value": "下面是 AI 和 Human 的对话,讨论 laf 的内容。"
        },
        {
            "obj": "Human", 
            "value": "什么是 laf"
        },
        {
            "obj": "AI", 
            "value": "laf 是云开发平台...."
        },
        {
            "obj": "Human", 
            "value": "laf 可以做什么?"
        },
    ]
}'

响应

{
    "code": 200,
    "statusText": "",
    "data": "laf是********"
 }

V2 应用对话

V2 对话完全兼容 openai 的接口!如果你有第三方项目,可以直接通过修改 BaseUrl 和 Authorization 来访问 猿媒BOT 应用

请求

  • Authorization: 由 3 部分组成: Bearer ,apikey 和 appId。

例如: ‘Bearer yuanmei-z51pkjqm9nrk03a1rx2funoy-642adec15f04d67d4613efdb’。Bearer需手动添加,apiKey 直接在应用页生成,appId 在应用也 API 栏取。

  • chatId: string | undefined 。
    • 为 undefined 时(不传入),不使用 猿媒BOT 提供的上下文功能,完全通过传入的 messages 构建上下文。
    • 为空字符 ” 时,表示新窗口第一次对话。响应值会有一个 newChatId
    • 为非空字符串时,意味着使用 chatId 进行对话,自动从 猿媒BOT 数据库取历史记录。
  • messages: 与 openai gpt 接口完全一致。
curl --location --request POST 'https://bot.apetd.com/api/openapi/v1/chat/completions' \
--header 'Authorization: Bearer apikey-appId' \
--header 'User-Agent: Apifox/1.0.0 (https://www.apifox.cn)' \
--header 'Content-Type: application/json' \
--data-raw '{
    "chatId":"",
    "stream":false,
    "messages": [
        {
            "content": "导演是谁",
            "role": "user"
        }
    ]
}'

响应特殊参数说明:

  • quoteLen: 当次请求引用知识库的数量(查询具体引用接口后面再补)
  • newChatId(string | null): 新对话的ID,下次只需要传入这个 id,就会拥有上下文

Stream 响应

event: chatResponse
data: {"newChatId": "648f0fc12e0d47315f3bc30e","quoteLen":5}

event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":""},"index":0,"finish_reason":null}]}

event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"电"},"index":0,"finish_reason":null}]}

event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"影"},"index":0,"finish_reason":null}]}

event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"《"},"index":0,"finish_reason":null}]}

event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"铃"},"index":0,"finish_reason":null}]}

event: answer
data: [DONE]

非 Stream 响应

{
    "rawSearch": [
        {
            "id": "19339",
            "q": "电影《铃芽之旅》的导演是谁?",
            "a": "电影《铃芽之旅》的导演是新海诚。",
            "source": ""
        },
        {
            "id": "8099",
            "q": "本作的主人公是谁?",
            "a": "本作的主人公是名叫铃芽的少女。",
            "source": "手动修改"
        }
    ],
    "newChatId": "648f0fc12e0d47315f3bc30e",
    "id": "",
    "model": "gpt-3.5-turbo-16k",
    "usage": {
        "prompt_tokens": 0,
        "completion_tokens": 0,
        "total_tokens": 373
    },
    "choices": [
        {
            "message": [
                {
                    "role": "assistant",
                    "content": "电影《玲芽之旅》的导演是新海诚。"
                }
            ],
            "finish_reason": "stop",
            "index": 0
        }
    ]
}

应用搜索(Beta版,仅做测试,不要上生产)

仅搜索,不进行对话,会返回搜索的结果。目前仅支持搜索最后一个问题,即 prompts[prompts.length-1]

请求

  • similarity – 向量距离,取值 0~1,越大表示越相似
  • limit – 搜索多少条
curl --location --request POST 'https://bot.apetd.com/api/openapi/kb/appKbSearch' \
--header 'apikey: 222' \
--header 'Content-Type: application/json' \
--data-raw '{
    "appId": "642adec15f01d67d4613efdb",
    "similarity": 0.8,
    "limit": 5,
    "prompts": [
        {
            "obj": "Human",
            "value": "导演是谁"
        }
    ]
}'

响应

  • rawSearch – 数据库原始数据
  • userSystemPrompt- 提示词
  • quotePrompt – 知识库相关的提示词
{
    "code": 200,
    "statusText": "",
    "data": {
        "code": 200,
        "rawSearch": [
            {
                "id": "19339",
                "q": "电影《铃芽之旅》的导演是谁?",
                "a": "电影《铃芽之旅》的导演是新海诚。"
            }
        ],
        "userSystemPrompt": {
                "obj": "System",
                "value": "知识库:电影《铃芽之旅》的导演是谁?\n电影《铃芽之旅》的导演是新海诚。\n电影《铃芽之旅》的编剧是谁?\n新海诚是本片的编剧。\n本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。\n电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。\n电影《铃芽之旅》的制作团队中有哪位著名人士?\n川村元气是本片的制作团队成员之一。\n电影中的后门是指什么?\n后门是连接往生者世界“常世”与人类所处的“现世”的门扉,平常仰赖人们心中的重量和思绪镇定土地和门。若是人心的重量消失,等于从人们的记忆中消失或思绪减少,将会导致后门被打开。一个人一生中最多只能通过一道后门。\n谁担任电影《铃芽之旅》中草太的祖父宗像羊朗的声演人员?\n松本白鹦担任电影《铃芽之旅》中草太的祖父宗像羊朗的声演人员。\n电影中的关门师是指什么?\n关门师是为了防范“门”被打开延伸的灾难,负责将出现在世上的“门”封闭的特殊人群。关门师进行封闭“门”的过程中,会先想著曾在该土地上生活的人们,感受该土地过往的景色与人事物,借此打开“门”的钥匙孔,再咏唱归还土地予神明的咒文,使用特制的钥匙将“门”锁上。\n电影《铃芽之旅》的女主角是谁?\n电影的女主角是铃芽。\n电影《铃芽之旅》的女主角是谁?\n电影《铃芽之旅》的女主角是岩户铃芽,由原菜乃华配音。\n谁担任电影《铃芽之旅》中岩户环的配音?\n深津绘里担任电影《铃芽之旅》中岩户环的配音。\n谁担任电影《铃芽之旅》中海部千果的配音?\n花濑琴音担任电影《铃芽之旅》中海部千果的配音。\n谁负责了电影《铃芽之旅》的音乐创作?\n电影《铃芽之旅》的音乐由RADWIMPS和作曲家阵内一真共同创作。\n谁是《铃芽之旅》的女主角?\n岩户铃芽是《铃芽之旅》的女主角。\n电影里的哪个角色希望透过圣地巡礼促进当地活化?\n町政策企划科的川守田正人课长。"
        },
        "quotePrompt": {
              "obj": "System",
              "value": "知识库:电影《铃芽之旅》的导演是谁?\n电影《铃芽之旅》的导演是新海诚。\n电影《铃芽之旅》的编剧是谁?\n新海诚是本片的编剧。\n本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。\n电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。\n电影《铃芽之旅》的制作团队中有哪位著名人士?\n川村元气是本片的制作团队成员之一。\n电影中的后门是指什么?\n后门是连接往生者世界“常世”与人类所处的“现世”的门扉,平常仰赖人们心中的重量和思绪镇定土地和门。若是人心的重量消失,等于从人们的记忆中消失或思绪减少,将会导致后门被打开。一个人一生中最多只能通过一道后门。\n谁担任电影《铃芽之旅》中草太的祖父宗像羊朗的声演人员?\n松本白鹦担任电影《铃芽之旅》中草太的祖父宗像羊朗的声演人员。\n电影中的关门师是指什么?\n关门师是为了防范“门”被打开延伸的灾难,负责将出现在世上的“门”封闭的特殊人群。关门师进行封闭“门”的过程中,会先想著曾在该土地上生活的人们,感受该土地过往的景色与人事物,借此打开“门”的钥匙孔,再咏唱归还土地予神明的咒文,使用特制的钥匙将“门”锁上。\n电影《铃芽之旅》的女主角是谁?\n电影的女主角是铃芽。\n电影《铃芽之旅》的女主角是谁?\n电影《铃芽之旅》的女主角是岩户铃芽,由原菜乃华配音。\n谁担任电影《铃芽之旅》中岩户环的配音?\n深津绘里担任电影《铃芽之旅》中岩户环的配音。\n谁担任电影《铃芽之旅》中海部千果的配音?\n花濑琴音担任电影《铃芽之旅》中海部千果的配音。\n谁负责了电影《铃芽之旅》的音乐创作?\n电影《铃芽之旅》的音乐由RADWIMPS和作曲家阵内一真共同创作。\n谁是《铃芽之旅》的女主角?\n岩户铃芽是《铃芽之旅》的女主角。\n电影里的哪个角色希望透过圣地巡礼促进当地活化?\n町政策企划科的川守田正人课长。"
         }
    }
}

知识库

知识库添加数据

请求

mode: index | qa
index 模式: 直接将 q 转成向量存起来,a 直接入库。
qa 模式: 只关注 data 里的 q,将 q 丢给大模型,让其根据 prompt 拆分成 qa 问答对(已经默认加格式输出)。

token 限制: 
index 模式: q + a 的总 token 最好不要超过 3000,否则对话时候容易超限制。
qa 模式: q 不要超过 3300 token

promot: 引导 qa 拆分的提示词,只需要告诉模型“这是什么”。
curl --location --request POST 'https://bot.apetd.com/api/openapi/kb/pushData' \
--header 'apikey: 22222' \
--header 'Content-Type: application/json' \
--data-raw '{
    "kbId": "64663f451ba1676dbdef0499",
    "mode": "index",
    "prompt": "qa 拆分引导词,index 模式下可以忽略",
    "data": [
        {
            "a": "test",
            "q": "1111"
        },
        {
            "a": "test2",
            "q": "22222"
        }
    ]
}'

响应

{
    "code": 200,
    "statusText": "",
    "message": "共插入 2 条数据",
    "data": 2
}

错误码

unAuthorization - api key鉴权错误、
insufficientQuota - 账号余额不足
unAuthModel - 无权使用模型
unAuthKb - 无权使用知识库

例子

V1 流响应例子

const streamFetch = ({ url, data, onMessage }) =>
  new Promise(async (resolve, reject) => {
    try {
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          apikey: '63f9a12228d2a638d839e11b-r5l1dho132hvcfd1wg12'
        },
        body: JSON.stringify(data),
      });
      const reader = res.body?.getReader();
      if (!reader) return;
      const decoder = new TextDecoder();
      let responseText = '';

      const read = async () => {
        const { done, value } = await reader?.read();
        if (done) {
          if (res.status === 200) {
            resolve(responseText);
          } else {
            try {
              const parseError = JSON.parse(responseText);
              reject(parseError?.message || '请求异常');
            } catch (err) {
              reject('请求异常');
            }
          }

          return;
        }
        const text = decoder.decode(value).replace(/<br\/>/g, '\n');
        res.status === 200 && onMessage(text);
        responseText += text;
        read();
      };
      read();
    } catch (err) {
      console.log(err, '====');
      reject(typeof err === 'string' ? err : err?.message || '请求异常');
    }
  });

async function test() {
    const responseText = await streamFetch({
        url: 'https://bot.apetd.com/api/openapi/chat/chat',
        data: {
            "modelId":"22222222",
            "prompts": [{
                "obj": "Human",
                "value": "laf是什么"
            }]
        },
        onMessage(e) {
            // e为收到的消息,可以做拼接
            resule += e
        }
    })
    console.log(responseText )
}

V2 流响应例子

type YuanmeiBotWebChatProps = {
  chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
  appId?: string;
};
type yuanmeiShareChatProps = {
  password?: string;
  shareId?: string;
};
export type Props = CreateChatCompletionRequest &
  YuanmeiBotWebChatProps &
  YuanmeiShareChatProps & {
    messages: MessageItemType[];
  };
export type ChatResponseType = {
  newChatId: string;
  quoteLen?: number;
};
export enum sseResponseEventEnum {
  error = 'error',
  answer = 'answer',
  chatResponse = 'chatResponse'
}
interface StreamFetchProps {
  data: Props;
  onMessage: (text: string) => void;
  abortSignal: AbortController;
}
export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) =>
  new Promise<ChatResponseType & { responseText: string; errMsg: string }>(
    async (resolve, reject) => {
      try {
        const response = await window.fetch('/api/openapi/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          signal: abortSignal.signal,
          body: JSON.stringify({
            ...data,
            stream: true
          })
        });

        if (response.status !== 200) {
          const err = await response.json();
          return reject(err);
        }

        if (!response?.body) {
          throw new Error('Request Error');
        }

        const reader = response.body?.getReader();

        // response data
        let responseText = '';
        let newChatId = '';
        let quoteLen = 0;
        let errMsg = '';

        const read = async () => {
          try {
            const { done, value } = await reader.read();
            if (done) {
              if (response.status === 200) {
                return resolve({
                  responseText,
                  newChatId,
                  quoteLen,
                  errMsg
                });
              } else {
                return reject('响应过程出现异常~');
              }
            }
            const chunkResponse = parseStreamChunk(value);

            chunkResponse.forEach((item) => {
              // parse json data
              const data = (() => {
                try {
                  return JSON.parse(item.data);
                } catch (error) {
                  return item.data;
                }
              })();

              if (item.event === sseResponseEventEnum.answer && data !== '[DONE]') {
                const answer: string = data?.choices?.[0].delta.content || '';
                onMessage(answer);
                responseText += answer;
              } else if (item.event === sseResponseEventEnum.chatResponse) {
                const chatResponse = data as ChatResponseType;
                newChatId = chatResponse.newChatId;
                quoteLen = chatResponse.quoteLen || 0;
              } else if (item.event === sseResponseEventEnum.error) {
                errMsg = getErrText(data, '流响应错误');
              }
            });
            read();
          } catch (err: any) {
            if (err?.message === 'The user aborted a request.') {
              return resolve({
                responseText,
                newChatId,
                quoteLen,
                errMsg
              });
            }
            reject(getErrText(err, '请求异常'));
          }
        };
        read();
      } catch (err: any) {
        console.log(err);

        reject(getErrText(err, '请求异常'));
      }
    }
  );
  
  //============================================================
  
  // 流请求,获取数据
  const { newChatId, quoteLen } = await streamFetch({
    data: {
      messages, // 问题, [{role:"user",content:"Laf 是什么?"}]
      chatId, // 对话的 ID
      appId: modelId, // 应用的 ID
      model: '' // 固定填空
    },
    onMessage: (text: string) => {
      console.log(text)
    },
    abortSignal // 中断请求,可选
  });

在第三方应用中使用 猿媒BOT

使用 V2 版 chat 接口,可以轻松的在其他三方应用中使用 猿媒BOT。

  1. 获取秘钥(新建一个,记得复制出来,关了得新建了
猿媒BOT Api 使用文档
  1. 组合秘钥

利用刚复制的 API 秘钥加上 AppId 组合成一个新的秘钥,格式为: Bearer API秘钥-AppId,例如:

Bearer yuanmei-z51pkjqm9nrk03a1rx2funoy-642adec15f04d67d4613efdb

注意:

  1. Bearer后面有一个空格
  2. 大部分项目自动会填Bearer ,你只需要粘贴后面部分即可:
yuanmei-z51pkjqm9nrk03a1rx2funoy-642adec15f04d67d4613efdb
  1. 复制接口地址
https://bot.apetd.com/api/openapi

一些问题

如何取 modelId / appId

V3.8之后的接口改成了 appId 。两者是同一个东西,主要看接口实际字段。

应用编辑页内点击API可获取

猿媒BOT Api 使用文档

更多内容

在线咨询
qr_code_scanner 扫码关注

扫码关注微信

关注我们获取最新资讯
关注微信公众号
立即扫码关注我们

分享本页
返回顶部