| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- 'use strict';
- const axios = require('axios');
- const DOCUMENT_EXTENSIONS = ['pdf', 'doc', 'docx'];
- const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp'];
- class AiDify {
- /**
- * 构造函数
- * @param {object} config
- * @param {string} config.apiKey - Dify API Key
- * @param {string} config.chatflowId - ChatFlow ID
- * @param {string} [config.baseUrl=https://api.dify.ai/v1] - Dify API 接口地址
- */
- constructor({ apiKey = 'app-fiJr1pNxjdKXpetko8ANawnY', chatflowId = '', baseUrl = 'http://workflow.smartcost.com.cn/v1' }) {
- if (!apiKey) {
- throw new Error('apiKey 不能为空');
- }
- this.apiKey = apiKey;
- this.chatflowId = chatflowId;
- this.baseUrl = baseUrl;
- }
- /**
- * 向 ChatFlow 发送请求
- * @param {string} userInput - 用户输入内容
- * @param {string} [userId='user_123'] - 用户 ID
- * @returns {Promise<object>} - Dify 返回的对象
- */
- async ask(userInput, userId = 'user_123') {
- const url = `${this.baseUrl}/chat-messages`;
- const headers = {
- 'Authorization': `Bearer ${this.apiKey}`,
- 'Content-Type': 'application/json',
- };
- const body = {
- inputs: {}, // 如果你的 ChatFlow 有 schema,可填写 key-value
- query: userInput,
- response_mode: 'blocking',
- user: userId,
- conversation_id: this.chatflowId,
- };
- try {
- const res = await axios.post(url, body, { headers });
- return res.data;
- } catch (err) {
- console.error('Dify 请求失败:', err.response || err.response.data || err.message);
- throw new Error('Dify 请求失败');
- }
- }
- async workflows(body) {
- const url = `${this.baseUrl}/workflows/run`;
- const headers = {
- 'Authorization': `Bearer ${this.apiKey}`,
- 'Content-Type': 'application/json',
- };
- try {
- const res = await axios.post(url, body, { headers });
- return res.data.data.outputs;
- } catch (err) {
- console.error('Dify 请求失败:', err.response || err.response.data || err.message);
- throw new Error('Dify 请求失败');
- }
- }
- async posCalcDetailRead(col, billsName, keyword, files, userId = `user_123`) {
- const body = {
- inputs: {
- header: col.map(x => { return x.title + (x.unit ? `(${x.unit})` : '') }).join(','),
- billsName,
- keyword,
- imgList: [],
- },
- user: userId,
- };
- for (const f of files) {
- body.inputs.imgList.push({
- type: "image",
- transfer_method: "remote_url",
- url: f.viewpath,
- })
- }
- return await this.workflows(body);
- }
- async imageTransExcel(files, userId = `user_123`) {
- const body = {
- inputs: { imgList: [] },
- user: userId,
- response_mode: 'blocking',
- };
- for (const f of files) {
- body.inputs.imgList.push({
- type: "image",
- transfer_method: "remote_url",
- url: f.viewpath,
- })
- }
- return await this.workflows(body);
- }
- async billsIdentify(name, unit, userId = `user_123`) {
- const body = {
- inputs: { billsName: name || '' + ( unit ? `(${unit})` : '') },
- user: userId,
- response_mode: 'blocking',
- };
- return await this.workflows(body);
- }
- /**
- * 根据文件名后缀判断 Dify 的 type 字段(image / document)
- */
- resolveDifyFileType(filename = '') {
- const ext = filename ? filename.split('.').pop().toLowerCase() : '';
- if (IMAGE_EXTENSIONS.includes(ext)) return 'image';
- if (DOCUMENT_EXTENSIONS.includes(ext)) return 'document';
- // 无法判断时默认按document处理,让Dify那边自行兜底
- return 'document';
- }
- async contractTrans(file, userId = 'user_123') {
- const body = {
- inputs: { fileList: [] },
- user: userId,
- response_mode: 'blocking',
- };
- if (!file.viewpath) {
- throw new Error('文件缺少viewpath字段');
- }
- const fileType = this.resolveDifyFileType(file.filename);
- body.inputs.fileList.push({
- type: fileType,
- transfer_method: 'remote_url',
- url: file.viewpath,
- });
- return await this.workflows(body);
- }
- /** 查询助手专用 Chatflow;只返回解析结果,不将业务数据发送给模型。 */
- async askAssistant({ question, path = '', user, conversationId = '' }) {
- let response;
- try {
- response = await axios.post(`${this.baseUrl.replace(/\/$/, '')}/chat-messages`, {
- inputs: { path },
- query: question,
- user,
- conversation_id: conversationId,
- response_mode: 'blocking',
- auto_generate_name: false,
- }, {
- headers: { Authorization: `Bearer ${this.apiKey}` },
- timeout: 110000,
- maxRedirects: 0,
- maxContentLength: 1024 * 1024,
- });
- } catch (err) {
- // 不透传 Axios 错误对象,其中包含 Authorization 和用户问题。
- const status = err.response && err.response.status;
- if (status === 401 || status === 403) throw new Error('查询助手 Dify 授权失败,请检查服务端配置');
- if (status === 404 && conversationId) throw new Error('助手会话已失效,请清空对话后重试');
- if (err.code === 'ECONNABORTED') throw new Error('查询助手响应超时,请稍后重试');
- console.log(err);
- throw new Error('查询助手 Dify 调用失败,请检查已发布的 Chatflow 和服务地址');
- }
- const data = response.data || {};
- let result;
- try {
- // 只兼容包裹整个 JSON 的代码围栏,不猜测或修复损坏的参数。
- const text = typeof data.answer === 'string' ? data.answer.trim() : '';
- result = JSON.parse(text.replace(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i, '$1'));
- } catch (err) {
- throw new Error('查询助手返回的 JSON 无法解析,请检查 Chatflow 的 Answer 配置');
- }
- if (!result || result.version !== '1.1.0' ||
- !['manual', 'business', 'capability'].includes(result.type) ||
- !['answer', 'query', 'clarify', 'error'].includes(result.status) ||
- typeof result.answer !== 'string' || !Array.isArray(result.missing_fields) ||
- !Array.isArray(result.sources) ||
- (result.status === 'query' && (result.type !== 'business' || !result.query ||
- result.answer !== '' || result.missing_fields.length !== 0)) ||
- (result.type !== 'business' && (result.query !== null || !['answer', 'error'].includes(result.status))) ||
- (result.status !== 'query' && !result.answer.trim())) {
- throw new Error('查询助手返回格式不符合 1.1.0 协议,请检查 Chatflow 配置');
- }
- if (typeof data.conversation_id !== 'string' || !/^[a-f0-9-]{36}$/i.test(data.conversation_id)) {
- throw new Error('查询助手未返回有效会话 ID');
- }
- return { result, conversationId: data.conversation_id };
- }
- }
- module.exports = AiDify;
|