硅基流动nodejs流式输出以及工具调用

使用JavaScript的api直接在前端问答速度虽然快但是有token直接暴露的风险。
现在使用nodejs也可以快速进行流式输出并且可以隐藏用户敏感信息。

const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;

//启动服务node index.js
app.get('/test', async (req, res) => {
  try {
    // 设置流式响应头(保持不变)
    res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    const userQuestion = req.query.questions || '默认问题:你好!';

    const response = await axios({
      method: 'POST',
      url: 'https://api.siliconflow.cn/v1/chat/completions',
      headers: {
        Authorization: `Bearer sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`,
        'Content-Type': 'application/json'
      },
      data: {
        model: "Qwen/Qwen2.5-72B-Instruct",
        messages: [{ role: "user", content: userQuestion }],
        stream: true,
        max_tokens: 2048,
        stop: ["END"]
      },
      responseType: 'stream'
    });

    let buffer = ''; // 新增:内容缓冲区
    let shouldStop = false; // 新增:停止标志
    
    response.data.on('data', (chunk) => {
      if (shouldStop) return;
      
      const lines = chunk.toString('utf8').split('\n').filter(line => line.trim() !== '');
      
      lines.forEach(line => {
        if (line.startsWith('data: ') && line !== 'data: [DONE]') {
          try {
            const jsonData = JSON.parse(line.replace(/^data: /, ''));
            if (jsonData.choices?.[0]?.delta?.content) {
              const content = jsonData.choices[0].delta.content;
              
              // 修正2:客户端检测停止词
              buffer += content;
              if (buffer.includes('END')) {
                shouldStop = true;
                // 发送END前的有效内容
                const validContent = buffer.split('END')[0];
                // 发送流式输出结果
                res.write(validContent);
                res.end();
                return;
              }
              
              res.write(content);
            }
          } catch (error) {
            console.error('处理错误:', error);
          }
        }
      });
    });

    response.data.on('end', () => {
      if (!shouldStop) res.end();
    });

  } catch (error) {
    console.error('请求出错:', error);
    res.status(500).json({ error: '服务器内部错误' });
  }
});

app.listen(port, () => {
  console.log(`Example app listening on http://localhost:${port}`);
});


如果不需要流式输出的话按下面的方式即可

app.post('/test', async (req, res) => {
  try {
    console.log(req.body);

    const userQuestion = req.body.questions || '';
    // 设置流式响应头(保持不变)
    res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    // 发送请求获取配置信息
    const response = await axios.get('http://www.config.cn', {
      headers: {
        Accept: 'application/json',
        Authorization: `token信息`,
      },
    });
    // 假设API返回的数据在response.data中
    // 根据实际API返回的数据结构,你可能需要调整这里的属性名
    const configData = response.data;

    let model_name_yt = configData.data[0].model_name_yt;//获取的模型名称
    let model_key_yt = configData.data[0].model_key_yt;//获取的模型apiKey
    let model_url_yt = configData.data[0].model_url_yt;//访问模型地址的url
    let model_prompt_check = configData.data[0].model_prompt_check;//设置的提示词信息

    Authorization_key = 'Bearer '+model_key_yt

    templateStr = model_prompt_check.replace("${questions}", userQuestion); // 执行替换
    console.log(templateStr)
  
    const response_json = await axios({
      method: 'POST',
      url: model_url_yt,
      headers: {
        Authorization: Authorization_key,
        'Content-Type': 'application/json'
      },
      data: {
        model: model_name_yt,
        messages: [{ role: "user", content: templateStr }],
        max_tokens: 2048,
      },
    });
    const messageContent = response_json.data;
    let contentInfo = messageContent.choices[0].message.content;
    res.json(contentInfo);//返回模型输出的内容
  } catch (error) {
    console.error('请求出错:', error);
    res.status(500).json({ error: '服务器内部错误' });
  }
});

在流式输出中使用tools工具

const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;

// 使用中间件解析JSON格式的请求体
app.use(express.json());

//启动服务node index.js
app.get('/test', async (req, res) => {
  try {
    // 设置流式响应头(保持不变)
    res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    const userQuestion = req.query.questions || '默认问题:你好!';

    const response = await axios({
      method: 'POST',

      url: 'https://api.siliconflow.cn/v1/chat/completions',
      headers: {
      Authorization: `Bearer sk-*******************************************`,
        'Content-Type': 'application/json'
      },
      data: {
        model: "Qwen/Qwen2.5-72B-Instruct",
        messages: [{ role: "user", content: userQuestion }],
        stream: true,
        max_tokens: 2048,
        tools: [
            {
              type: "function",
              function: {
                  name: "multiplication",
                  description: "Calculate the multiplication of two numbers",
                  parameters: {
                    number1:"数字1",
                    number2:"数字2"
                  }
              },
              strict: false
            },
            {
              type: "function",
              function: {
                  name: "weather",
                  description: "获取天气预报数据",
              },
              strict: false
            }
        ],
        stop: ["END"],
      },
      responseType: 'stream'
    });

    let buffer = ''; // 新增:内容缓冲区
    let shouldStop = false; // 新增:停止标志
    
    response.data.on('data', (chunk) => {
      if (shouldStop) return;
      
      const lines = chunk.toString('utf8').split('\n').filter(line => line.trim() !== '');
      
      lines.forEach(line => {
        if (line.startsWith('data: ') && line !== 'data: [DONE]') {
          try {
            
            const jsonData = JSON.parse(line.replace(/^data: /, ''));
            //console.log(jsonData);
            if (jsonData.choices?.[0]?.delta?.content) {
              const content = jsonData.choices[0].delta.content;
              console.log(content);
              
              // 修正2:客户端检测停止词
              buffer += content;
              if (buffer.includes('END')) {
                shouldStop = true;
                // 发送END前的有效内容
                const validContent = buffer.split('END')[0];
                // 发送流式输出结果
                res.write(validContent);
                res.end();
                return;
              }
              if (content == '<tool_call>') {
                res.write("");
              }else{
                res.write(content);
              }
              
            }

            //*********************************************************** */
            // 检查是否有 tool_calls 字段
            console.log(line);
            if (jsonData.choices?.[0]?.delta?.tool_calls) {
              const function_name = jsonData.choices[0].delta.tool_calls[0].function.name;
              //console.log(function_name);
              
              if (function_name == 'weather'){
                return_obj = weather()
                console.log('天气查询:'+return_obj);
                res.write(return_obj);
              }
              if (function_name == 'multiplication'){
                // arguments = jsonData.choices[0].delta.tool_calls[0].function.arguments;
                // let jsonObj = JSON.parse(arguments);
                // console.log(jsonObj);
                // let num1 = jsonObj.number1
                // let num2 = jsonObj.number2
                //return_obj = multiplication(num1,num2)
              }
            }

          } catch (error) {
            console.error('处理错误:', error);
          }
        }
      });
    });

    response.data.on('end', () => {
      if (!shouldStop) res.end();
    });

  } catch (error) {
    console.error('请求出错:', error);
    res.status(500).json({ error: '服务器内部错误' });
  }
});


// 乘法函数
function multiplication(num1, num2) {
  return num1 * num2;
}

// 天气函数示例
function weather() {
  console.log('天气查询');
  return '今天天气很好万里无云。';
}


app.listen(port, () => {
  console.log(`Example app listening on http://localhost:${port}`);
});

流式输出时多工具调用的实现方式

const express = require('express');
const axios = require('axios');
const app = express();
const port = 3456;

// 使用中间件解析JSON格式的请求体
app.use(express.json());

// 启动服务 node index.js
app.get('/test', async (req, res) => {
    try {
      // 设置流式响应头
      res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
      res.setHeader('Cache-Control', 'no-cache');
      res.setHeader('Connection', 'keep-alive');
  
      const userQuestion = req.query.questions || '默认问题:你好!';
  
        const response = await axios({
        method: 'POST',

        url: 'https://api.siliconflow.cn/v1/chat/completions',
        headers: {
        Authorization: `Bearer sk-*****************************`,
            'Content-Type': 'application/json'
        },
        data: {
            model: "Qwen/Qwen2.5-72B-Instruct",
            messages: [{ role: "system", content: "你是荆州市的问答助手,回复荆州市相关的问题,当判断需要使用工具时请直接调用工具不要做其他回复" },{ role: "user", content: userQuestion }],
            stream: true,
            max_tokens: 2048,
            tools: [
                {
                type: "function",
                function: {
                    name: "multiplication",
                    description: "Calculate the multiplication of two numbers",
                    parameters: {
                        number1:"数字1",
                        number2:"数字2"
                    }
                },
                strict: false
                },
                {
                type: "function",
                function: {
                    name: "weather",
                    description: "获取天气预报数据",
                    parameters: {
                        city:"城市"
                    }
                },
                strict: false
                }
            ],
            stop: ["END"],
        },
        responseType: 'stream'
        });
  
      // 存储所有工具调用的信息
      const toolCalls = {};
      let currentToolCallId = null;
      let isToolCallInProgress = false;
  
      response.data.on('data', async (chunk) => {
        const lines = chunk.toString('utf8').split('\n').filter(line => line.trim() !== '');
  
        for (let line of lines) {
          if (line.startsWith('data: ') && line !== 'data: [DONE]') {
            try {
              const jsonData = JSON.parse(line.replace(/^data: /, ''));
              
              // 处理常规内容输出
              if (jsonData.choices?.[0]?.delta?.content) {
                res.write(line + "\n");
              }
  
              // 处理工具调用
              if (jsonData.choices?.[0]?.delta?.tool_calls) {
                isToolCallInProgress = true;
                
                // 处理每个工具调用
                for (const toolCall of jsonData.choices[0].delta.tool_calls) {
                  const { index, id, type, function: fn } = toolCall;
                  
                  // 新的工具调用开始
                  if (id && !toolCalls[id]) {
                    currentToolCallId = id;
                    toolCalls[id] = {
                      index,
                      type,
                      name: fn?.name || '',
                      arguments: '',
                      complete: false
                    };
                  }
                  
                  // 更新当前工具调用的信息
                  if (currentToolCallId && toolCalls[currentToolCallId]) {
                    // 更新工具名称(如果有)
                    if (fn?.name) {
                      toolCalls[currentToolCallId].name = fn.name;
                    }
                    
                    // 收集参数片段
                    if (fn?.arguments) {
                      toolCalls[currentToolCallId].arguments += fn.arguments;
                    }
                  }
                }
                res.write(line + "\n"); // 转发工具调用信息
              }
            } catch (error) {
              console.error('处理错误:', error);
            }
          } else if (line === 'data: [DONE]') {
            // 处理所有工具调用结果
            if (isToolCallInProgress) {
              console.log(toolCalls);
              try {
                // 收集所有需要处理的工具调用
                const validToolCalls = Object.entries(toolCalls)
                  .filter(([id, call]) => id && call.name && call.arguments && !call.complete);
                
                // 按原始顺序处理工具调用
                const sortedToolCalls = validToolCalls.sort((a, b) => a[1].index - b[1].index);
                
                for (const [id, call] of sortedToolCalls) {
                  try {
                    const args = JSON.parse(call.arguments);
                    let toolResult;
                    
                    // 根据工具名称调用不同的函数
                    switch (call.name) {
                      case 'weather':
                        toolResult = await getWeather(args.city);
                        break;
                        
                      case 'multiplication':
                        toolResult = await multiplyNumbers(args.number1, args.number2);
                        break;
                        
                      default:
                        toolResult = { error: `未知工具: ${call.name}` };
                    }
                    
                    // 发送工具调用结果
                    const toolResponse = {
                      id: `tool_${id}`,
                      object: "chat.completion.chunk",
                      created: Math.floor(Date.now() / 1000),
                      model: "Qwen/Qwen2.5-72B-Instruct",
                      choices: [{
                        index: call.index,
                        delta: {
                          role: "assistant",
                          content: JSON.stringify(toolResult)
                        },
                        finish_reason: "stop"
                      }]
                    };
                    
                    res.write(`data: ${JSON.stringify(toolResponse)}\n`);
                  } catch (parseError) {
                    console.error(`解析工具${call.name}参数错误:`, parseError);
                  }
                }
              } catch (error) {
                console.error('处理工具调用错误:', error);
              }
            }
            
            res.write('data: [DONE]\n');
            res.end();
          }
        }
      });
  
      response.data.on('error', (error) => {
        console.error('流式响应错误:', error);
        res.end();
      });
  
    } catch (error) {
      console.error('请求出错:', error);
      res.status(500).json({ error: '服务器内部错误' });
    }
});

// 天气函数示例
async function getWeather(city) {
  console.log(`天气查询 - ${city}`);
  // 天气查询接口获取的数据
  const res = { "city": city, "msg": `${city}今天的天气很好` };
  const resString = JSON.stringify(res);

  const responseToolsJson = await axios({
    method: 'POST',
    url: 'https://api.siliconflow.cn/v1/chat/completions',
    headers: {
      Authorization: 'Bearer sk-********************************',
      'Content-Type': 'application/json'
    },
    data: {
      model: 'Qwen/Qwen2-7B-Instruct',
      messages: [{ role: "system", content: '##要求\n提取json中的参数拼接成用户可以理解的语句,直接输出结果即可不需要添加自己的描述。' }, { role: "user", content: resString }],
      max_tokens: 2048,
    },
  });

  const messageContent = responseToolsJson.data.choices[0].message.content;
  console.log(responseToolsJson.data);

  return { city: city, msg: messageContent };
}
// 乘法函数
async function multiplyNumbers(num1, num2) {
  const result = num1 * num2;
  return {
    operation: "multiplication",
    number1: num1,
    number2: num2,
    result: result,
    message: `${num1}乘以${num2}等于${result}`
  };
}

app.listen(port, () => {
  console.log(`Example app listening on http://localhost:${port}`);
});
<think>好的,我现在需要帮助用户解决如何在Node.js中通过DeepSeek大模型实现流式响应的智能聊天应用的问题。首先,我得理清楚用户的需求。用户提到了流式响应,这意味着需要处理数据的分块传输,而不是等待整个响应完成后再返回给客户端。这通常涉及使用服务器发送的事件(SSE)或者WebSocket,但根据引用内容,可能更倾向于使用API的流式响应功能。 接下来,我需要查看用户提供的引用内容,特别是引用[1]、[2]、[4]和[5],因为它们直接相关于Node.js调用DeepSeek API。引用[1]提到一个命令行聊天应用实现了流式响应和对话记录保存,引用[2]则强调Node.js的异步特性和错误处理,引用[4]提供了多语言的开发流程,而引用[5]涉及本地运行DeepSeek模型。不过用户的需求是调用DeepSeek的API,而不是本地运行,所以重点可能在前几个引用。 首先,用户需要知道如何调用DeepSeek的API,并且处理流式响应。根据引用[1]和[2],可能需要使用HTTP客户端库,如axios或node-fetch,并处理流式数据。引用[2]提到异步编程,所以可能需要使用async/await或者Promise来处理异步请求。此外,流式响应可能需要监听数据事件,逐步接收数据块。 接下来,我需要考虑代码结构。根据示例中的Python代码,可能需要在Node.js中设置一个函数,发送POST请求到DeepSeek的API端点,并处理返回的数据流。引用[1]中的示例可能用到了流式处理,例如使用fetch的响应体作为可读流,逐块读取数据。 然后,用户可能需要设置请求头,包含认证信息,比如API密钥。引用[1]和[2]都提到了API密钥的必要性,所以在代码中需要从环境变量中获取,避免硬编码。 另外,流式响应的处理可能需要将接收到的数据块拼接起来,并实时输出到控制台或其他前端界面。例如,在命令行应用中,每次收到数据块就打印出来,而不是等待整个响应完成。引用[5]提到了使用Ollama本地运行模型,但用户需要的是调用API,所以可以忽略这部分,但可能参考其流式处理的方法。 可能还需要处理对话历史,以保持上下文。引用[1]提到对话记录的自动保存,所以需要在代码中维护一个消息数组,每次请求时包含之前的对话历史。 错误处理也是关键,引用[2]提到完善的错误处理机制,所以在代码中需要捕获网络错误、API返回的错误状态码等,并进行相应处理。 最后,用户可能需要将实现封装成模块,方便维护和扩展,如引用[2]提到的代码结构清晰,易于维护。可能需要创建一个类,封装API调用流式处理、对话历史管理等功能。 总结步骤应该是:安装依赖、配置环境变量、创建API请求函数、处理流式响应、维护对话历史、实现实时输出、错误处理。同时,根据引用内容,可能需要参考流动(SiliconFlow)的API文档,确保请求参数正确,如model版本、messages结构等。 可能遇到的问题包括流式数据的正确解析,例如API返回的数据可能是JSON格式的块,或者是纯文本,需要根据实际情况处理。例如,某些API可能返回多个JSON对象,每个对象包含部分响应,需要逐个解析并提取内容部分。 测试时,可以使用示例代码发送请求,观察是否能实时接收到数据块,并确保对话历史正确更新。此外,需要处理网络中断或API限制的情况,添加重试机制或适当的用户提示。 最后,确保代码符合最佳实践,如使用环境变量管理敏感信息,模块化代码结构,添加注释以提高可读性,并处理可能的异常情况,如无效的API密钥或网络错误。</think>以下是Node.js实现DeepSeek流式聊天应用的完整解决方案: ### 一、环境准备 1. 安装必要依赖 ```bash npm install axios dotenv ``` 2. 创建`.env`文件存储API密钥 ```env DEEPSEEK_API_KEY=your_api_key_here DEEPSEEK_API_URL=https://api.siliconflow.cn/v1/chat/completions ``` ### 二、核心实现代码 ```javascript require('dotenv').config(); const axios = require('axios'); class DeepSeekChat { constructor() { this.history = []; this.config = { headers: { 'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}`, 'Content-Type': 'application/json', 'Accept': 'text/event-stream' }, responseType: 'stream' }; } async streamChat(prompt) { try { this.history.push({ role: 'user', content: prompt }); const requestBody = { model: 'deepseek-chat', messages: this.history, stream: true, temperature: 0.7 }; const response = await axios.post( process.env.DEEPSEEK_API_URL, requestBody, this.config ); let fullResponse = ''; const stream = response.data; return new Promise((resolve, reject) => { stream.on('data', chunk => { const lines = chunk.toString().split('\n').filter(line => line.trim()); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.replace('data: ', ''); if (data === '[DONE]') break; try { const json = JSON.parse(data); const content = json.choices[0].delta.content || ''; process.stdout.write(content); fullResponse += content; } catch (err) { console.error('解析错误:', err); } } } }); stream.on('end', () => { this.history.push({ role: 'assistant', content: fullResponse }); resolve(fullResponse); }); stream.on('error', err => reject(err)); }); } catch (error) { console.error('API请求失败:', error.response?.data || error.message); throw error; } } } ``` ### 三、使用示例 ```javascript const chat = new DeepSeekChat(); // 命令行交互示例 const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout }); async function chatLoop() { while (true) { const input = await new Promise(resolve => readline.question('\n用户: ', resolve) ); if (input.toLowerCase() === 'exit') break; try { process.stdout.write('助手: '); await chat.streamChat(input); } catch (err) { console.error('对话异常:', err); } } readline.close(); } chatLoop(); ``` ### 四、关键特性解析 1. **流式响应处理** 使用`responseType: 'stream'`配置接收数据流,通过事件监听器实时处理分块数据。这种方式相比传统响应方式可降低首字节时间(TTFB)[^2]。 2. **对话历史管理** 采用`history`数组维护上下文对话,每次请求携带完整对话记录实现连续对话能力。研究显示上下文长度直接影响模型响应质量[^4]。 3. **错误处理机制** 包含三级错误处理: - API请求异常捕获 - 流数据处理异常捕获 - JSON解析异常处理 4. **配置管理** 通过`.env`文件隔离敏感信息,符合12要素应用原则[^3]。 ### 五、性能优化建议 1. 使用连接池复用HTTP连接 2. 实现断线重连机制 3. 添加速率限制防止API过载 4. 使用WebSocket替代SSE(需API支持)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值