SSE流式传输->Vue的使用

1,217 阅读1分钟

大模型分析完成返回消息是EventStream

image.png

1.通过fetch来进行流的读取

      fetch("http://xxx/chat/kb_chat", {
        method: "post",
        headers: {
          "Content-Type": "application/json", // 如果发送JSON数据
        },
        body: JSON.stringify({
          query: "xxx",
          mode: "local_kb",
          kb_name: "demo",
          top_k: 3,
          score_threshold: 2,
          history: [],
          stream: true,
          model: "glm4-chat",
          temperature: 0.7,
          max_tokens: 0,
          prompt_name: "default",
          return_direct: false,
        }),
      })
        .then((response) => {
          if (!response.ok) {
            throw new Error("Network response was not ok");
          }

          // 使用response.body.getReader()来读取流
          const reader = response.body.getReader();

          function read() {
            reader
              .read()
              .then(({ value, done }) => {
                // value是一个Uint8Array,需要转换为字符串
                if (value) {
                  const textDecoder = new TextDecoder("utf-8");
                  const data = textDecoder.decode(value);
                  console.log(data); // 处理数据
                  if (!done) {
                    read(); // 继续读取
                  }
                }
              })
              .catch(console.error);
          }
          read(); // 开始读取
        })
        .catch(console.error);
image.png

2.@microsoft/fetch-event-source

npm install @microsoft/fetch-event-source@2.0.1

    import { fetchEventSource } from "@microsoft/fetch-event-source";

    async fun() {
      await fetchEventSource(`${process.env.VUE_APP_LANG_API}/chat/kb_chat`, {
        method: "post",
        headers: {
          "Content-Type": "application/json", // 如果发送JSON数据
        },
        body: JSON.stringify({
          query: "xxxx",
          mode: "local_kb",
          kb_name: "xx",
          top_k: 3,
          score_threshold: 2,
          history: [],
          stream: true,
          model: "glm4-chat",
          temperature: 0.7,
          max_tokens: 0,
          prompt_name: "default",
          return_direct: false,
        }),
        onmessage(ev) {
          // console.log(ev.data);
          let xx = JSON.parse(ev.data);
          console.log(xx);
        },
      });
    },

添加更好的错误处理
class RetriableError extends Error { }
class FatalError extends Error { }

fetchEventSource('/api/sse', {
    async onopen(response) {
        if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
            return; // everything's good
        } else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
            // client-side errors are usually non-retriable:
            throw new FatalError();
        } else {
            throw new RetriableError();
        }
    },
    onmessage(msg) {
        // if the server emits an error message, throw an exception
        // so it gets handled by the onerror callback below:
        if (msg.event === 'FatalError') {
            throw new FatalError(msg.data);
        }
    },
    onclose() {
        // if the server closes the connection unexpectedly, retry:
        throw new RetriableError();
    },
    onerror(err) {
        if (err instanceof FatalError) {
            throw err; // rethrow to stop the operation
        } else {
            // do nothing to automatically retry. You can also
            // return a specific retry interval here.
        }
    }
});

在我测试的时候发现 切换浏览器标签页 会重新发送一次链接 还有出现下图这种也会重新发送新的链接

image.png

最终解决办法 参考

    xxx() {
      this.fenxiLoad = true; // 标记是否结束
      let that = this;
      class RetriableError extends Error {}
      class FatalError extends Error {}
      this.content = "";
      // 文件对话
      fetchEventSource(`${process.env.VUE_APP_LANG_API}/chat/kb_chat`, {
        method: "post",
        openWhenHidden: true,
        headers: {
          "Content-Type": "application/json", // 如果发送JSON数据
        },
        body: JSON.stringify({
          query: "xxx",
          mode: "local_kb",
          kb_name: "666",
          top_k: 3,
          score_threshold: 2,
          history: [],
          stream: true,
          model: "glm4-chat",
          temperature: 0.7,
          max_tokens: 0,
          prompt_name: "default",
          return_direct: false,
        }),
        onmessage(ev) {
          let xx = JSON.parse(ev.data);
          console.log(xx);
          let choices = (xx.choices && xx.choices[0].delta.content) || "";
          that.content += choices;
        },
        onclose() {
          that.fenxiLoad = false;
          // if the server closes the connection unexpectedly, retry:
          throw new RetriableError();
        },
        onerror(err) {
          that.fenxiLoad = false;
          if (err instanceof FatalError) {
            throw err; // rethrow to stop the operation
          } else {
            // do nothing to automatically retry. You can also
            // return a specific retry interval here.
            
            // alert("请重试");
            // that.peopleFenxi();

            console.log(err.message);

            if (err.message) {
              that.$message.error("分析失败了,请点击重新分析");
            } else {
              that.$message.success("分析完成!");
            }

            return 100000000000000;
          }
        },
      });
    },