plugin-custom-llm 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/server/llm-providers/custom-llm.js +139 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@ NocoBase plugin for integrating external LLM providers that support OpenAI-compa
|
|
|
7
7
|
- **OpenAI-compatible**: Works with any LLM provider exposing `/chat/completions` endpoint
|
|
8
8
|
- **Auto content detection**: Handles both string and array content blocks (`[{type: 'text', text: '...'}]`)
|
|
9
9
|
- **Response mapping**: Transform non-standard API responses to OpenAI format via JSON config (supports streaming SSE and JSON)
|
|
10
|
-
- **Reasoning content**: Display thinking/reasoning from DeepSeek-compatible providers
|
|
10
|
+
- **Reasoning content**: Display thinking/reasoning from DeepSeek-compatible providers (multi-path detection)
|
|
11
|
+
- **Stream keepalive**: Prevent proxy/gateway timeouts during long model thinking phases
|
|
12
|
+
- **Tool calling support**: Gemini-compatible tool schema fixing (Zod + JSON Schema)
|
|
11
13
|
- **Configurable**: JSON config editors for request and response customization
|
|
12
14
|
- **Locale support**: English, Vietnamese, Chinese
|
|
13
15
|
|
|
@@ -23,6 +25,11 @@ Upload `plugin-custom-llm-x.x.x.tgz` via NocoBase Plugin Manager UI, then enable
|
|
|
23
25
|
|---|---|
|
|
24
26
|
| **Base URL** | LLM endpoint URL, e.g. `https://your-llm-server.com/v1` |
|
|
25
27
|
| **API Key** | Authentication key |
|
|
28
|
+
| **Disable Streaming** | Disable streaming for models that return empty stream values |
|
|
29
|
+
| **Stream Keep Alive** | Enable keepalive to prevent timeouts during long thinking phases |
|
|
30
|
+
| **Keep Alive Interval** | Interval in ms between keepalive signals (default: 5000) |
|
|
31
|
+
| **Keep Alive Content** | Visual indicator text during keepalive (default: `...`) |
|
|
32
|
+
| **Timeout** | Custom timeout in ms for slow-responding models |
|
|
26
33
|
| **Request config (JSON)** | Optional. Extra request configuration |
|
|
27
34
|
| **Response config (JSON)** | Optional. Response parsing and mapping configuration |
|
|
28
35
|
|
|
@@ -72,6 +79,26 @@ Upload `plugin-custom-llm-x.x.x.tgz` via NocoBase Plugin Manager UI, then enable
|
|
|
72
79
|
|
|
73
80
|
Standard OpenAI-compatible parameters: temperature, max tokens, top P, frequency/presence penalty, response format, timeout, max retries.
|
|
74
81
|
|
|
82
|
+
## Changelog
|
|
83
|
+
|
|
84
|
+
### v1.2.0
|
|
85
|
+
|
|
86
|
+
- **Fix**: Keepalive no longer interferes with tool call sequences (prevents tool call corruption)
|
|
87
|
+
- **Fix**: Gemini-compatible tool schema fixing — handles Zod schemas via dual-phase approach (pre/post conversion)
|
|
88
|
+
- **Fix**: Keepalive content no longer contaminates saved messages in DB
|
|
89
|
+
- **Fix**: Response metadata extraction with long ID sanitization (>128 chars truncated)
|
|
90
|
+
- **Fix**: Multi-path reasoning content detection (`additional_kwargs` + `kwargs.additional_kwargs`)
|
|
91
|
+
- **Fix**: Improved error recovery in keepalive consumer (immediate error propagation)
|
|
92
|
+
|
|
93
|
+
### v1.1.1
|
|
94
|
+
|
|
95
|
+
- Stream keepalive proxy for long thinking phases
|
|
96
|
+
- Response mapping for non-standard LLM APIs
|
|
97
|
+
|
|
98
|
+
### v1.0.0
|
|
99
|
+
|
|
100
|
+
- Initial release with OpenAI-compatible LLM provider support
|
|
101
|
+
|
|
75
102
|
## License
|
|
76
103
|
|
|
77
104
|
Apache-2.0
|
package/dist/client/index.js
CHANGED
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):"function"==typeof define&&define.amd?define("plugin-custom-llm",["react","@nocobase/plugin-ai/client","@nocobase/client","@nocobase/utils/client","antd","react-i18next"],t):"object"==typeof exports?exports["plugin-custom-llm"]=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):e["plugin-custom-llm"]=t(e.react,e["@nocobase/plugin-ai/client"],e["@nocobase/client"],e["@nocobase/utils/client"],e.antd,e["react-i18next"])}(self,function(e,t,n,o,r,i){return function(){"use strict";var a={772:function(e){e.exports=n},645:function(e){e.exports=t},584:function(e){e.exports=o},721:function(e){e.exports=r},156:function(t){t.exports=e},238:function(e){e.exports=i}},c={};function
|
|
10
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):"function"==typeof define&&define.amd?define("plugin-custom-llm",["react","@nocobase/plugin-ai/client","@nocobase/client","@nocobase/utils/client","antd","react-i18next"],t):"object"==typeof exports?exports["plugin-custom-llm"]=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):e["plugin-custom-llm"]=t(e.react,e["@nocobase/plugin-ai/client"],e["@nocobase/client"],e["@nocobase/utils/client"],e.antd,e["react-i18next"])}(self,function(e,t,n,o,r,i){return function(){"use strict";var a={772:function(e){e.exports=n},645:function(e){e.exports=t},584:function(e){e.exports=o},721:function(e){e.exports=r},156:function(t){t.exports=e},238:function(e){e.exports=i}},c={};function u(e){var t=c[e];if(void 0!==t)return t.exports;var n=c[e]={exports:{}};return a[e](n,n.exports,u),n.exports}u.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return u.d(t,{a:t}),t},u.d=function(e,t){for(var n in t)u.o(t,n)&&!u.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},u.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},u.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var l={};return!function(){u.r(l),u.d(l,{PluginCustomLLMClient:function(){return g},default:function(){return S}});var e=u(772),t=u(156),n=u.n(t),o=u(584),r=u(238),i="@nocobase/plugin-custom-llm",a=u(721),c=u(645),p=function(){var t=(0,r.useTranslation)(i,{nsMode:"fallback"}).t;return n().createElement("div",{style:{marginBottom:24}},n().createElement(a.Collapse,{bordered:!1,size:"small",items:[{key:"options",label:t("Options"),forceRender:!0,children:n().createElement(e.SchemaComponent,{schema:{type:"void",name:"custom-llm",properties:{temperature:{title:(0,o.tval)("Temperature",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:.7,"x-component-props":{step:.1,min:0,max:2}},maxCompletionTokens:{title:(0,o.tval)("Max completion tokens",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:-1},topP:{title:(0,o.tval)("Top P",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:1,"x-component-props":{step:.1,min:0,max:1}},frequencyPenalty:{title:(0,o.tval)("Frequency penalty",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:0,"x-component-props":{step:.1,min:-2,max:2}},presencePenalty:{title:(0,o.tval)("Presence penalty",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:0,"x-component-props":{step:.1,min:-2,max:2}},responseFormat:{title:(0,o.tval)("Response format",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Select",enum:[{label:t("Text"),value:"text"},{label:t("JSON"),value:"json_object"}],default:"text"},timeout:{title:(0,o.tval)("Timeout (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:6e4},maxRetries:{title:(0,o.tval)("Max retries",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:1}}}})}]}))},s={components:{ProviderSettingsForm:function(){return n().createElement(e.SchemaComponent,{schema:{type:"void",properties:{apiKey:{title:(0,o.tval)("API Key",{ns:i}),type:"string",required:!0,"x-decorator":"FormItem","x-component":"TextAreaWithGlobalScope"},disableStream:{title:(0,o.tval)("Disable streaming",{ns:i}),type:"boolean","x-decorator":"FormItem","x-component":"Checkbox","x-content":(0,o.tval)("Disable streaming description",{ns:i})},streamKeepAlive:{title:(0,o.tval)("Stream keepalive",{ns:i}),type:"boolean","x-decorator":"FormItem","x-component":"Checkbox","x-content":(0,o.tval)("Stream keepalive description",{ns:i})},keepAliveIntervalMs:{title:(0,o.tval)("Keepalive interval (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber","x-component-props":{placeholder:"5000",min:1e3,step:1e3,style:{width:"100%"}},description:(0,o.tval)("Keepalive interval description",{ns:i})},keepAliveContent:{title:(0,o.tval)("Keepalive content",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input","x-component-props":{placeholder:"..."},description:(0,o.tval)("Keepalive content description",{ns:i})},timeout:{title:(0,o.tval)("Timeout (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber","x-component-props":{placeholder:"120000",min:0,step:1e3,style:{width:"100%"}},description:(0,o.tval)("Timeout description",{ns:i})},requestConfig:{title:(0,o.tval)("Request config (JSON)",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{placeholder:JSON.stringify({extraHeaders:{},extraBody:{},modelKwargs:{}},null,2),rows:6,style:{fontFamily:"monospace",fontSize:12}},description:(0,o.tval)("Request config description",{ns:i})},responseConfig:{title:(0,o.tval)("Response config (JSON)",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{placeholder:JSON.stringify({contentPath:"auto",reasoningKey:"reasoning_content",responseMapping:{content:"message.response"}},null,2),rows:8,style:{fontFamily:"monospace",fontSize:12}},description:(0,o.tval)("Response config description",{ns:i})}}}})},ModelSettingsForm:function(){return n().createElement(e.SchemaComponent,{components:{Options:p,ModelSelect:c.ModelSelect},schema:{type:"void",properties:{model:{title:(0,o.tval)("Model",{ns:i}),type:"string",required:!0,"x-decorator":"FormItem","x-component":"ModelSelect"},options:{type:"void","x-component":"Options"}}}})}}};function m(e,t,n,o,r,i,a){try{var c=e[i](a),u=c.value}catch(e){n(e);return}c.done?t(u):Promise.resolve(u).then(o,r)}function f(e){return function(){var t=this,n=arguments;return new Promise(function(o,r){var i=e.apply(t,n);function a(e){m(i,o,r,a,c,"next",e)}function c(e){m(i,o,r,a,c,"throw",e)}a(void 0)})}}function d(e,t,n){return(d=x()?Reflect.construct:function(e,t,n){var o=[null];o.push.apply(o,t);var r=new(Function.bind.apply(e,o));return n&&b(r,n.prototype),r}).apply(null,arguments)}function y(e){return(y=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function b(e,t){return(b=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function v(e){var t="function"==typeof Map?new Map:void 0;return(v=function(e){if(null===e||-1===Function.toString.call(e).indexOf("[native code]"))return e;if("function"!=typeof e)throw TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return d(e,arguments,y(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),b(n,e)})(e)}function x(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(x=function(){return!!e})()}function h(e,t){var n,o,r,i,a={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){var u=[i,c];if(n)throw TypeError("Generator is already executing.");for(;a;)try{if(n=1,o&&(r=2&u[0]?o.return:u[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,u[1])).done)return r;switch(o=0,r&&(u=[2&u[0],r.value]),u[0]){case 0:case 1:r=u;break;case 4:return a.label++,{value:u[1],done:!1};case 5:a.label++,o=u[1],u=[0];continue;case 7:u=a.ops.pop(),a.trys.pop();continue;default:if(!(r=(r=a.trys).length>0&&r[r.length-1])&&(6===u[0]||2===u[0])){a=0;continue}if(3===u[0]&&(!r||u[1]>r[0]&&u[1]<r[3])){a.label=u[1];break}if(6===u[0]&&a.label<r[1]){a.label=r[1],r=u;break}if(r&&a.label<r[2]){a.label=r[2],a.ops.push(u);break}r[2]&&a.ops.pop(),a.trys.pop();continue}u=t.call(e,a)}catch(e){u=[6,e],o=0}finally{n=r=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}}}var g=function(e){var t;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function n(){var e,t;if(!(this instanceof n))throw TypeError("Cannot call a class as a function");return e=n,t=arguments,e=y(e),function(e,t){var n;if(t&&("object"==((n=t)&&"undefined"!=typeof Symbol&&n.constructor===Symbol?"symbol":typeof n)||"function"==typeof t))return t;if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(this,x()?Reflect.construct(e,t||[],y(this).constructor):e.apply(this,t))}return n.prototype=Object.create(e&&e.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),e&&b(n,e),t=[{key:"afterAdd",value:function(){return f(function(){return h(this,function(e){return[2]})})()}},{key:"beforeLoad",value:function(){return f(function(){return h(this,function(e){return[2]})})()}},{key:"load",value:function(){var e=this;return f(function(){return h(this,function(t){return e.aiPlugin.aiManager.registerLLMProvider("custom-llm",s),[2]})})()}},{key:"aiPlugin",get:function(){return this.app.pm.get("ai")}}],function(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}(n.prototype,t),n}(v(e.Plugin)),S=g}(),l}()});
|
package/dist/externalVersion.js
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
"@nocobase/client": "2.0.
|
|
12
|
-
"@nocobase/plugin-ai": "2.0.
|
|
11
|
+
"@nocobase/client": "2.0.20",
|
|
12
|
+
"@nocobase/plugin-ai": "2.0.20",
|
|
13
13
|
"react-i18next": "11.18.6",
|
|
14
|
-
"@nocobase/server": "2.0.
|
|
15
|
-
"@nocobase/flow-engine": "2.0.
|
|
16
|
-
"@nocobase/database": "2.0.
|
|
14
|
+
"@nocobase/server": "2.0.20",
|
|
15
|
+
"@nocobase/flow-engine": "2.0.20",
|
|
16
|
+
"@nocobase/database": "2.0.20",
|
|
17
17
|
"react": "18.2.0",
|
|
18
|
-
"@nocobase/utils": "2.0.
|
|
18
|
+
"@nocobase/utils": "2.0.20",
|
|
19
19
|
"antd": "5.24.2"
|
|
20
20
|
};
|
|
@@ -225,9 +225,10 @@ function createMappingFetch(responseMapping) {
|
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
function wrapWithStreamKeepAlive(model, options) {
|
|
228
|
-
const
|
|
228
|
+
const streamMethodName = typeof model._streamResponseChunks === "function" ? "_streamResponseChunks" : "_stream";
|
|
229
|
+
const originalStream = model[streamMethodName].bind(model);
|
|
229
230
|
const { intervalMs, keepAliveContent } = options;
|
|
230
|
-
model
|
|
231
|
+
model[streamMethodName] = async function* (messages, opts, runManager) {
|
|
231
232
|
const ChatGenerationChunk = getChatGenerationChunk();
|
|
232
233
|
const AIMessageChunk = getAIMessageChunk();
|
|
233
234
|
const baseIterator = originalStream(messages, opts, runManager);
|
|
@@ -235,9 +236,16 @@ function wrapWithStreamKeepAlive(model, options) {
|
|
|
235
236
|
let streamDone = false;
|
|
236
237
|
let streamError = null;
|
|
237
238
|
let notifyReady = null;
|
|
239
|
+
let hasToolCallChunks = false;
|
|
240
|
+
let hasErrored = false;
|
|
238
241
|
const consumer = (async () => {
|
|
242
|
+
var _a, _b;
|
|
239
243
|
try {
|
|
240
244
|
for await (const chunk of baseIterator) {
|
|
245
|
+
const msg = chunk == null ? void 0 : chunk.message;
|
|
246
|
+
if (((_a = msg == null ? void 0 : msg.tool_call_chunks) == null ? void 0 : _a.length) || ((_b = msg == null ? void 0 : msg.tool_calls) == null ? void 0 : _b.length)) {
|
|
247
|
+
hasToolCallChunks = true;
|
|
248
|
+
}
|
|
241
249
|
buffer.push(chunk);
|
|
242
250
|
if (notifyReady) {
|
|
243
251
|
notifyReady();
|
|
@@ -246,6 +254,11 @@ function wrapWithStreamKeepAlive(model, options) {
|
|
|
246
254
|
}
|
|
247
255
|
} catch (err) {
|
|
248
256
|
streamError = err;
|
|
257
|
+
hasErrored = true;
|
|
258
|
+
if (notifyReady) {
|
|
259
|
+
notifyReady();
|
|
260
|
+
notifyReady = null;
|
|
261
|
+
}
|
|
249
262
|
} finally {
|
|
250
263
|
streamDone = true;
|
|
251
264
|
if (notifyReady) {
|
|
@@ -259,6 +272,7 @@ function wrapWithStreamKeepAlive(model, options) {
|
|
|
259
272
|
while (buffer.length > 0) {
|
|
260
273
|
yield buffer.shift();
|
|
261
274
|
}
|
|
275
|
+
hasToolCallChunks = false;
|
|
262
276
|
if (streamDone) break;
|
|
263
277
|
const waitForChunk = new Promise((resolve) => {
|
|
264
278
|
notifyReady = resolve;
|
|
@@ -272,9 +286,14 @@ function wrapWithStreamKeepAlive(model, options) {
|
|
|
272
286
|
]);
|
|
273
287
|
if (timer) clearTimeout(timer);
|
|
274
288
|
if (result === "timeout" && !streamDone && buffer.length === 0) {
|
|
289
|
+
if (streamError || hasErrored) break;
|
|
290
|
+
if (hasToolCallChunks) continue;
|
|
275
291
|
const keepAliveChunk = new ChatGenerationChunk({
|
|
276
|
-
message: new AIMessageChunk({
|
|
277
|
-
|
|
292
|
+
message: new AIMessageChunk({
|
|
293
|
+
content: KEEPALIVE_PREFIX,
|
|
294
|
+
additional_kwargs: { __keepalive: true }
|
|
295
|
+
}),
|
|
296
|
+
text: KEEPALIVE_PREFIX
|
|
278
297
|
});
|
|
279
298
|
yield keepAliveChunk;
|
|
280
299
|
}
|
|
@@ -291,6 +310,81 @@ function wrapWithStreamKeepAlive(model, options) {
|
|
|
291
310
|
function isKeepAlive(text) {
|
|
292
311
|
return typeof text === "string" && text.startsWith(KEEPALIVE_PREFIX);
|
|
293
312
|
}
|
|
313
|
+
function fixEmptyToolProperties(model) {
|
|
314
|
+
var _a;
|
|
315
|
+
const originalBind = (_a = model.bindTools) == null ? void 0 : _a.bind(model);
|
|
316
|
+
if (!originalBind) return model;
|
|
317
|
+
const PLACEHOLDER_PROP = {
|
|
318
|
+
_placeholder: { type: "string", description: "No parameters required" }
|
|
319
|
+
};
|
|
320
|
+
function fixPropertiesInSchema(schema) {
|
|
321
|
+
if (!schema || typeof schema !== "object") return;
|
|
322
|
+
if (schema.properties && typeof schema.properties === "object" && Object.keys(schema.properties).length === 0) {
|
|
323
|
+
schema.properties = { ...PLACEHOLDER_PROP };
|
|
324
|
+
}
|
|
325
|
+
for (const key of ["anyOf", "oneOf", "allOf"]) {
|
|
326
|
+
if (Array.isArray(schema[key])) {
|
|
327
|
+
schema[key].forEach((sub) => fixPropertiesInSchema(sub));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
model.bindTools = function(tools, kwargs) {
|
|
332
|
+
var _a2;
|
|
333
|
+
const fixedTools = tools.map((tool) => {
|
|
334
|
+
var _a3, _b;
|
|
335
|
+
if (!tool || typeof tool !== "object") return tool;
|
|
336
|
+
if (typeof ((_a3 = tool.schema) == null ? void 0 : _a3.safeParse) === "function") {
|
|
337
|
+
return tool;
|
|
338
|
+
}
|
|
339
|
+
const schema = tool.schema;
|
|
340
|
+
if (schema && typeof schema === "object" && !schema.safeParse) {
|
|
341
|
+
const props = schema.properties;
|
|
342
|
+
if (props && typeof props === "object" && Object.keys(props).length === 0) {
|
|
343
|
+
return {
|
|
344
|
+
...tool,
|
|
345
|
+
schema: {
|
|
346
|
+
...schema,
|
|
347
|
+
properties: { ...PLACEHOLDER_PROP }
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const funcParams = (_b = tool.function) == null ? void 0 : _b.parameters;
|
|
353
|
+
if (funcParams == null ? void 0 : funcParams.properties) {
|
|
354
|
+
if (typeof funcParams.properties === "object" && Object.keys(funcParams.properties).length === 0) {
|
|
355
|
+
return {
|
|
356
|
+
...tool,
|
|
357
|
+
function: {
|
|
358
|
+
...tool.function,
|
|
359
|
+
parameters: {
|
|
360
|
+
...funcParams,
|
|
361
|
+
properties: { ...PLACEHOLDER_PROP }
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return tool;
|
|
368
|
+
});
|
|
369
|
+
const result = originalBind(fixedTools, kwargs);
|
|
370
|
+
try {
|
|
371
|
+
const config = (result == null ? void 0 : result.kwargs) ?? (result == null ? void 0 : result.defaultOptions);
|
|
372
|
+
if ((config == null ? void 0 : config.tools) && Array.isArray(config.tools)) {
|
|
373
|
+
for (const tool of config.tools) {
|
|
374
|
+
if ((_a2 = tool == null ? void 0 : tool.function) == null ? void 0 : _a2.parameters) {
|
|
375
|
+
fixPropertiesInSchema(tool.function.parameters);
|
|
376
|
+
}
|
|
377
|
+
if (tool == null ? void 0 : tool.parameters) {
|
|
378
|
+
fixPropertiesInSchema(tool.parameters);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
return result;
|
|
385
|
+
};
|
|
386
|
+
return model;
|
|
387
|
+
}
|
|
294
388
|
class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
295
389
|
get baseURL() {
|
|
296
390
|
return null;
|
|
@@ -304,7 +398,9 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
304
398
|
return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.responseConfig);
|
|
305
399
|
}
|
|
306
400
|
createModel() {
|
|
307
|
-
|
|
401
|
+
var _a;
|
|
402
|
+
const { apiKey, disableStream, timeout, streamKeepAlive, keepAliveIntervalMs, keepAliveContent } = this.serviceOptions || {};
|
|
403
|
+
const baseURL = (_a = this.serviceOptions) == null ? void 0 : _a.baseURL;
|
|
308
404
|
const { responseFormat } = this.modelOptions || {};
|
|
309
405
|
const reqConfig = this.requestConfig;
|
|
310
406
|
const resConfig = this.responseConfig;
|
|
@@ -341,7 +437,8 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
341
437
|
if (resConfig.responseMapping) {
|
|
342
438
|
config.configuration.fetch = createMappingFetch(resConfig.responseMapping);
|
|
343
439
|
}
|
|
344
|
-
|
|
440
|
+
let model = new ChatOpenAI(config);
|
|
441
|
+
model = fixEmptyToolProperties(model);
|
|
345
442
|
if (streamKeepAlive && !disableStream) {
|
|
346
443
|
return wrapWithStreamKeepAlive(model, {
|
|
347
444
|
intervalMs: Number(keepAliveIntervalMs) || 5e3,
|
|
@@ -354,11 +451,12 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
354
451
|
const resConfig = this.responseConfig;
|
|
355
452
|
const text = extractTextContent(chunk, resConfig.contentPath);
|
|
356
453
|
if (isKeepAlive(text)) {
|
|
357
|
-
return
|
|
454
|
+
return KEEPALIVE_PREFIX;
|
|
358
455
|
}
|
|
359
456
|
return stripToolCallTags(text);
|
|
360
457
|
}
|
|
361
458
|
parseResponseMessage(message) {
|
|
459
|
+
var _a, _b;
|
|
362
460
|
const { content: rawContent, messageId, metadata, role, toolCalls, attachments, workContext } = message;
|
|
363
461
|
const content = {
|
|
364
462
|
...rawContent ?? {},
|
|
@@ -379,6 +477,10 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
379
477
|
content.content = content.content.replace(new RegExp(escapedPrefix + ".*?(?=" + escapedPrefix + "|$)", "g"), "");
|
|
380
478
|
content.content = stripToolCallTags(content.content);
|
|
381
479
|
}
|
|
480
|
+
if (((_b = (_a = content.metadata) == null ? void 0 : _a.additional_kwargs) == null ? void 0 : _b.__keepalive) !== void 0) {
|
|
481
|
+
const { __keepalive, ...cleanKwargs } = content.metadata.additional_kwargs;
|
|
482
|
+
content.metadata = { ...content.metadata, additional_kwargs: cleanKwargs };
|
|
483
|
+
}
|
|
382
484
|
return {
|
|
383
485
|
key: messageId,
|
|
384
486
|
content,
|
|
@@ -386,15 +488,43 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
386
488
|
};
|
|
387
489
|
}
|
|
388
490
|
parseReasoningContent(chunk) {
|
|
389
|
-
var _a;
|
|
491
|
+
var _a, _b, _c;
|
|
390
492
|
const resConfig = this.responseConfig;
|
|
391
493
|
const reasoningKey = resConfig.reasoningKey || "reasoning_content";
|
|
392
|
-
const reasoning = (_a = chunk == null ? void 0 : chunk.additional_kwargs) == null ? void 0 : _a[reasoningKey];
|
|
494
|
+
const reasoning = ((_a = chunk == null ? void 0 : chunk.additional_kwargs) == null ? void 0 : _a[reasoningKey]) ?? ((_c = (_b = chunk == null ? void 0 : chunk.kwargs) == null ? void 0 : _b.additional_kwargs) == null ? void 0 : _c[reasoningKey]);
|
|
393
495
|
if (reasoning && typeof reasoning === "string") {
|
|
394
496
|
return { status: "streaming", content: reasoning };
|
|
395
497
|
}
|
|
396
498
|
return null;
|
|
397
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Extract response metadata from LLM output for post-save enrichment.
|
|
502
|
+
* Sanitizes overly long message IDs from Gemini or other providers.
|
|
503
|
+
*/
|
|
504
|
+
parseResponseMetadata(output) {
|
|
505
|
+
var _a, _b;
|
|
506
|
+
try {
|
|
507
|
+
const generation = (_b = (_a = output == null ? void 0 : output.generations) == null ? void 0 : _a[0]) == null ? void 0 : _b[0];
|
|
508
|
+
if (!generation) return [null, null];
|
|
509
|
+
const message = generation.message;
|
|
510
|
+
let id = message == null ? void 0 : message.id;
|
|
511
|
+
if (!id) return [null, null];
|
|
512
|
+
if (typeof id === "string" && id.length > 128) {
|
|
513
|
+
id = id.substring(0, 128);
|
|
514
|
+
}
|
|
515
|
+
const metadata = {};
|
|
516
|
+
if (message == null ? void 0 : message.response_metadata) {
|
|
517
|
+
metadata.finish_reason = message.response_metadata.finish_reason;
|
|
518
|
+
metadata.system_fingerprint = message.response_metadata.system_fingerprint;
|
|
519
|
+
}
|
|
520
|
+
if (message == null ? void 0 : message.usage_metadata) {
|
|
521
|
+
metadata.usage_metadata = message.usage_metadata;
|
|
522
|
+
}
|
|
523
|
+
return Object.keys(metadata).length > 0 ? [id, metadata] : [null, null];
|
|
524
|
+
} catch {
|
|
525
|
+
return [null, null];
|
|
526
|
+
}
|
|
527
|
+
}
|
|
398
528
|
parseResponseError(err) {
|
|
399
529
|
return (err == null ? void 0 : err.message) ?? "Unexpected LLM service error";
|
|
400
530
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "AI LLM: Custom (OpenAI Compatible)",
|
|
4
4
|
"displayName.zh-CN": "AI LLM:自定义(OpenAI 兼容)",
|
|
5
5
|
"description": "OpenAI-compatible LLM provider with auto response format detection for external LLM services.",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.2.0",
|
|
7
7
|
"main": "dist/server/index.js",
|
|
8
8
|
"nocobase": {
|
|
9
9
|
"supportedVersions": [
|