@drx974/n8n-nodes-deepseek-fixed 0.1.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 ADDED
@@ -0,0 +1,68 @@
1
+ # n8n-nodes-deepseek-fixed-romy
2
+
3
+ Nœud DeepSeek Chat Model "maison" pour l'AI Agent n8n. Corrige les deux bugs
4
+ documentés qui cassent le Tool Calling avec DeepSeek dans n8n :
5
+
6
+ 1. **Rejet du schéma `strict`** : DeepSeek renvoie une erreur 400
7
+ ("Required properties must match...") quand n8n envoie `strict: true`
8
+ dans la définition JSON Schema des tools. Ce nœud ne transmet jamais ce
9
+ flag, quel que soit ce que l'AI Agent essaie de passer.
10
+
11
+ 2. **`reasoning_content` non renvoyé** : en Thinking Mode, DeepSeek exige que
12
+ le `reasoning_content` du tour précédent soit renvoyé tel quel dès que ce
13
+ tour contenait des tool_calls, sinon 400 ("The reasoning_content in the
14
+ thinking mode must be passed back to the API"). Ce nœud stocke ce champ
15
+ sur chaque message assistant et le réinjecte automatiquement au tour
16
+ suivant — Thinking Mode reste utilisable même avec des tools.
17
+
18
+ Par défaut, Thinking Mode est **désactivé** (le plus sûr avec Tool Calling) ;
19
+ vous pouvez l'activer (`high` / `max`) si vous voulez le raisonnement
20
+ chain-of-thought sur cet agent.
21
+
22
+ ## Build
23
+
24
+ ```bash
25
+ npm install
26
+ npm run build
27
+ ```
28
+
29
+ ## Déploiement recommandé (le plus sûr, évite l'incident déjà rencontré)
30
+
31
+ Ne PAS faire `npm install` à la main dans le container n8n (ça duplique les
32
+ dépendances et peut casser le chargement des nodes, comme déjà vécu).
33
+
34
+ **Méthode recommandée : publier sur npm puis installer via l'UI n8n**
35
+
36
+ ```bash
37
+ npm login # une fois, avec un compte npm (peut être le vôtre)
38
+ npm publish --access public
39
+ ```
40
+
41
+ Puis, dans n8n : **Settings → Community Nodes → Install**, coller le nom du
42
+ package `n8n-nodes-deepseek-fixed-romy`, cocher la case de risque, Install,
43
+ puis **redémarrer le container n8n** (obligatoire pour que le node se charge,
44
+ surtout en mode queue).
45
+
46
+ Si vous préférez ne pas publier publiquement, publiez sous un nom scope privé
47
+ (`@votre-compte/n8n-nodes-deepseek-fixed`) avec un compte npm payant "private
48
+ package", ou utilisez un registre npm privé si vous en avez un (Verdaccio,
49
+ GitHub Packages, etc.) — le flux d'installation via l'UI reste identique.
50
+
51
+ ## Utilisation dans l'AI Agent
52
+
53
+ 1. Ajouter le nœud **DeepSeek Chat Model (Fixed)**
54
+ 2. Credential : coller votre clé API DeepSeek (`DeepSeek Fixed API`)
55
+ 3. Le connecter à l'entrée "Chat Model" de l'AI Agent
56
+ 4. Laisser **Thinking Mode = Disabled** pour du Tool Calling simple, ou
57
+ passer à `high`/`max` si vous voulez le raisonnement — les deux
58
+ fonctionnent avec les tools.
59
+ 5. Connecter vos tools comme d'habitude.
60
+
61
+ ## Limites connues
62
+
63
+ - Pas de support du streaming token-par-token (`_generate` uniquement,
64
+ pas de `_streamResponseChunks`) — suffisant pour l'usage AI Agent classique,
65
+ mais à garder en tête si vous branchez ça sur un chat en streaming.
66
+ - Testé par compilation TypeScript stricte contre `@langchain/core@1.2.2` et
67
+ `n8n-workflow@2.16.0` (versions au 10/07/2026) — à revalider si n8n monte
68
+ une version majeure différente de LangChain en interne.
@@ -0,0 +1,7 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class DeepSeekFixedApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DeepSeekFixedApi = void 0;
4
+ class DeepSeekFixedApi {
5
+ constructor() {
6
+ this.name = 'deepSeekFixedApi';
7
+ this.displayName = 'DeepSeek Fixed API';
8
+ this.documentationUrl = 'https://api-docs.deepseek.com/';
9
+ this.properties = [
10
+ {
11
+ displayName: 'API Key',
12
+ name: 'apiKey',
13
+ type: 'string',
14
+ typeOptions: { password: true },
15
+ default: '',
16
+ required: true,
17
+ },
18
+ {
19
+ displayName: 'Base URL',
20
+ name: 'baseUrl',
21
+ type: 'string',
22
+ default: 'https://api.deepseek.com',
23
+ description: 'Override if you proxy DeepSeek through your own endpoint',
24
+ },
25
+ ];
26
+ }
27
+ }
28
+ exports.DeepSeekFixedApi = DeepSeekFixedApi;
@@ -0,0 +1,56 @@
1
+ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
2
+ import type { BaseChatModelParams, BindToolsInput } from '@langchain/core/language_models/chat_models';
3
+ import type { BaseMessage } from '@langchain/core/messages';
4
+ import type { ChatResult } from '@langchain/core/outputs';
5
+ import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
6
+ export type ThinkingMode = 'disabled' | 'high' | 'max';
7
+ export interface ChatDeepSeekFixedParams extends BaseChatModelParams {
8
+ apiKey: string;
9
+ baseURL?: string;
10
+ model: string;
11
+ temperature?: number;
12
+ maxTokens?: number;
13
+ thinkingMode?: ThinkingMode;
14
+ timeout?: number;
15
+ }
16
+ /**
17
+ * Minimal, self-contained LangChain ChatModel for DeepSeek that fixes the two
18
+ * documented n8n / DeepSeek incompatibilities when used behind the AI Agent node:
19
+ *
20
+ * 1. DeepSeek rejects tool definitions carrying `"strict": true` in the JSON
21
+ * schema (400 "Required properties must match..."). We never emit `strict`.
22
+ *
23
+ * 2. In Thinking Mode, DeepSeek requires the `reasoning_content` of a prior
24
+ * assistant turn to be echoed back once that turn included tool_calls,
25
+ * otherwise it 400s with "The reasoning_content in the thinking mode must
26
+ * be passed back to the API." We stash it on additional_kwargs and replay
27
+ * it automatically on the next turn.
28
+ *
29
+ * Thinking Mode defaults to "disabled", which sidesteps rule 2 entirely and is
30
+ * the recommended setting whenever the model is wired into an AI Agent with
31
+ * tools. Set it to "high"/"max" if you need chain-of-thought and are fine with
32
+ * the extra latency/token cost — rule 2's handling keeps tool calls working
33
+ * either way.
34
+ */
35
+ export declare class ChatDeepSeekFixed extends BaseChatModel {
36
+ apiKey: string;
37
+ baseURL: string;
38
+ modelName: string;
39
+ temperature?: number;
40
+ maxTokens?: number;
41
+ thinkingMode: ThinkingMode;
42
+ timeout: number;
43
+ private client;
44
+ private boundTools;
45
+ constructor(fields: ChatDeepSeekFixedParams);
46
+ _llmType(): string;
47
+ /**
48
+ * Fix #1: convert tools without ever passing a `strict` flag through, no
49
+ * matter what the caller (n8n's AI Agent) asks for. `convertToOpenAITool`
50
+ * only sets `strict` when explicitly told to, so simply not forwarding it
51
+ * here is enough to keep it out of the wire payload.
52
+ */
53
+ bindTools(tools: BindToolsInput[], _kwargs?: Record<string, unknown>): ReturnType<NonNullable<BaseChatModel['bindTools']>>;
54
+ private toOpenAIMessages;
55
+ _generate(messages: BaseMessage[], _options: this['ParsedCallOptions'], _runManager?: CallbackManagerForLLMRun): Promise<ChatResult>;
56
+ }
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ChatDeepSeekFixed = void 0;
7
+ const chat_models_1 = require("@langchain/core/language_models/chat_models");
8
+ const messages_1 = require("@langchain/core/messages");
9
+ const function_calling_1 = require("@langchain/core/utils/function_calling");
10
+ const openai_1 = __importDefault(require("openai"));
11
+ /**
12
+ * Minimal, self-contained LangChain ChatModel for DeepSeek that fixes the two
13
+ * documented n8n / DeepSeek incompatibilities when used behind the AI Agent node:
14
+ *
15
+ * 1. DeepSeek rejects tool definitions carrying `"strict": true` in the JSON
16
+ * schema (400 "Required properties must match..."). We never emit `strict`.
17
+ *
18
+ * 2. In Thinking Mode, DeepSeek requires the `reasoning_content` of a prior
19
+ * assistant turn to be echoed back once that turn included tool_calls,
20
+ * otherwise it 400s with "The reasoning_content in the thinking mode must
21
+ * be passed back to the API." We stash it on additional_kwargs and replay
22
+ * it automatically on the next turn.
23
+ *
24
+ * Thinking Mode defaults to "disabled", which sidesteps rule 2 entirely and is
25
+ * the recommended setting whenever the model is wired into an AI Agent with
26
+ * tools. Set it to "high"/"max" if you need chain-of-thought and are fine with
27
+ * the extra latency/token cost — rule 2's handling keeps tool calls working
28
+ * either way.
29
+ */
30
+ class ChatDeepSeekFixed extends chat_models_1.BaseChatModel {
31
+ constructor(fields) {
32
+ super(fields);
33
+ this.boundTools = [];
34
+ this.apiKey = fields.apiKey;
35
+ this.baseURL = fields.baseURL ?? 'https://api.deepseek.com';
36
+ this.modelName = fields.model;
37
+ this.temperature = fields.temperature;
38
+ this.maxTokens = fields.maxTokens;
39
+ this.thinkingMode = fields.thinkingMode ?? 'disabled';
40
+ this.timeout = fields.timeout ?? 60000;
41
+ this.client = new openai_1.default({
42
+ apiKey: this.apiKey,
43
+ baseURL: this.baseURL,
44
+ timeout: this.timeout,
45
+ });
46
+ }
47
+ _llmType() {
48
+ return 'deepseek-fixed';
49
+ }
50
+ /**
51
+ * Fix #1: convert tools without ever passing a `strict` flag through, no
52
+ * matter what the caller (n8n's AI Agent) asks for. `convertToOpenAITool`
53
+ * only sets `strict` when explicitly told to, so simply not forwarding it
54
+ * here is enough to keep it out of the wire payload.
55
+ */
56
+ bindTools(tools, _kwargs) {
57
+ this.boundTools = tools.map((tool) => (0, function_calling_1.convertToOpenAITool)(tool));
58
+ // n8n's AI Agent supplies a fresh model instance per execution via
59
+ // supplyData(), so mutating in place and returning `this` is safe here
60
+ // and avoids depending on Runnable.bind(), which this LangChain version
61
+ // no longer exposes on BaseChatModel.
62
+ return this;
63
+ }
64
+ toOpenAIMessages(messages) {
65
+ return messages.map((message) => {
66
+ const type = message._getType();
67
+ if (type === 'human') {
68
+ return { role: 'user', content: message.content };
69
+ }
70
+ if (type === 'system') {
71
+ return { role: 'system', content: message.content };
72
+ }
73
+ if (type === 'tool') {
74
+ const toolMessage = message;
75
+ return {
76
+ role: 'tool',
77
+ tool_call_id: toolMessage.tool_call_id,
78
+ content: toolMessage.content,
79
+ };
80
+ }
81
+ // assistant / ai turn
82
+ const aiMessage = message;
83
+ const toolCalls = aiMessage.tool_calls && aiMessage.tool_calls.length > 0
84
+ ? aiMessage.tool_calls.map((toolCall) => ({
85
+ id: toolCall.id ?? '',
86
+ type: 'function',
87
+ function: {
88
+ name: toolCall.name,
89
+ arguments: JSON.stringify(toolCall.args ?? {}),
90
+ },
91
+ }))
92
+ : undefined;
93
+ // Fix #2: replay reasoning_content we stashed from DeepSeek's previous
94
+ // response, only relevant when Thinking Mode is active.
95
+ const storedReasoning = aiMessage.additional_kwargs?.reasoning_content;
96
+ const extra = this.thinkingMode !== 'disabled' && storedReasoning
97
+ ? { reasoning_content: storedReasoning }
98
+ : {};
99
+ return {
100
+ role: 'assistant',
101
+ content: aiMessage.content ?? '',
102
+ ...(toolCalls ? { tool_calls: toolCalls } : {}),
103
+ ...extra,
104
+ };
105
+ });
106
+ }
107
+ async _generate(messages, _options, _runManager) {
108
+ const openAIMessages = this.toOpenAIMessages(messages);
109
+ const body = {
110
+ model: this.modelName,
111
+ messages: openAIMessages,
112
+ max_tokens: this.maxTokens,
113
+ };
114
+ // DeepSeek ignores temperature/top_p/penalties in Thinking Mode (silently,
115
+ // no error) — omit it in that case to avoid misleading configuration.
116
+ if (this.thinkingMode === 'disabled') {
117
+ body.temperature = this.temperature;
118
+ }
119
+ if (this.boundTools.length > 0) {
120
+ body.tools = this.boundTools;
121
+ }
122
+ if (this.thinkingMode !== 'disabled') {
123
+ body.extra_body = {
124
+ thinking: { type: 'enabled' },
125
+ ...(this.thinkingMode === 'max' ? { reasoning_effort: 'max' } : {}),
126
+ };
127
+ }
128
+ const completion = await this.client.chat.completions.create(body);
129
+ const choice = completion.choices[0];
130
+ const rawMessage = choice.message;
131
+ const toolCalls = (rawMessage.tool_calls ?? [])
132
+ .filter((toolCall) => 'function' in toolCall)
133
+ .map((toolCall) => {
134
+ let args = {};
135
+ try {
136
+ args = JSON.parse(toolCall.function.arguments || '{}');
137
+ }
138
+ catch {
139
+ args = {};
140
+ }
141
+ return {
142
+ id: toolCall.id,
143
+ name: toolCall.function.name,
144
+ args,
145
+ type: 'tool_call',
146
+ };
147
+ });
148
+ const aiMessage = new messages_1.AIMessage({
149
+ content: rawMessage.content ?? '',
150
+ tool_calls: toolCalls,
151
+ additional_kwargs: rawMessage.reasoning_content
152
+ ? { reasoning_content: rawMessage.reasoning_content }
153
+ : {},
154
+ });
155
+ const generation = {
156
+ text: rawMessage.content ?? '',
157
+ message: aiMessage,
158
+ };
159
+ return { generations: [generation] };
160
+ }
161
+ }
162
+ exports.ChatDeepSeekFixed = ChatDeepSeekFixed;
@@ -0,0 +1,5 @@
1
+ import type { INodeType, INodeTypeDescription, ISupplyDataFunctions, SupplyData } from 'n8n-workflow';
2
+ export declare class LmChatDeepSeekFixed implements INodeType {
3
+ description: INodeTypeDescription;
4
+ supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData>;
5
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LmChatDeepSeekFixed = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const ChatDeepSeekFixed_1 = require("./ChatDeepSeekFixed");
6
+ class LmChatDeepSeekFixed {
7
+ constructor() {
8
+ this.description = {
9
+ displayName: 'DeepSeek Chat Model (Fixed)',
10
+ name: 'lmChatDeepSeekFixed',
11
+ icon: 'fa:comment',
12
+ iconColor: 'black',
13
+ group: ['transform'],
14
+ version: 1,
15
+ description: 'DeepSeek chat model for the AI Agent — fixes the strict tool-schema rejection and the reasoning_content passback error',
16
+ defaults: {
17
+ name: 'DeepSeek Chat Model (Fixed)',
18
+ },
19
+ codex: {
20
+ categories: ['AI'],
21
+ subcategories: {
22
+ AI: ['Language Models'],
23
+ },
24
+ },
25
+ // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
26
+ inputs: [],
27
+ // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
28
+ outputs: [n8n_workflow_1.NodeConnectionTypes.AiLanguageModel],
29
+ outputNames: ['Model'],
30
+ credentials: [
31
+ {
32
+ name: 'deepSeekFixedApi',
33
+ required: true,
34
+ },
35
+ ],
36
+ properties: [
37
+ {
38
+ displayName: 'Model',
39
+ name: 'model',
40
+ type: 'string',
41
+ default: 'deepseek-v4-flash',
42
+ description: 'e.g. deepseek-v4-flash, deepseek-v4-pro',
43
+ },
44
+ {
45
+ displayName: 'Thinking Mode',
46
+ name: 'thinkingMode',
47
+ type: 'options',
48
+ options: [
49
+ { name: 'Disabled (Recommended With Tool Calling)', value: 'disabled' },
50
+ { name: 'Enabled — High Effort', value: 'high' },
51
+ { name: 'Enabled — Max Effort', value: 'max' },
52
+ ],
53
+ default: 'disabled',
54
+ description: 'When enabled, reasoning_content is tracked and replayed automatically so tool calling keeps working',
55
+ },
56
+ {
57
+ displayName: 'Temperature',
58
+ name: 'temperature',
59
+ type: 'number',
60
+ default: 0.7,
61
+ typeOptions: { minValue: 0, maxValue: 2, numberPrecision: 2 },
62
+ description: 'Ignored by DeepSeek whenever Thinking Mode is enabled',
63
+ },
64
+ {
65
+ displayName: 'Max Tokens',
66
+ name: 'maxTokens',
67
+ type: 'number',
68
+ default: 2048,
69
+ },
70
+ {
71
+ displayName: 'Timeout (ms)',
72
+ name: 'timeout',
73
+ type: 'number',
74
+ default: 60000,
75
+ },
76
+ ],
77
+ };
78
+ }
79
+ async supplyData(itemIndex) {
80
+ const credentials = await this.getCredentials('deepSeekFixedApi');
81
+ const model = this.getNodeParameter('model', itemIndex);
82
+ const thinkingMode = this.getNodeParameter('thinkingMode', itemIndex);
83
+ const temperature = this.getNodeParameter('temperature', itemIndex);
84
+ const maxTokens = this.getNodeParameter('maxTokens', itemIndex);
85
+ const timeout = this.getNodeParameter('timeout', itemIndex);
86
+ const chatModel = new ChatDeepSeekFixed_1.ChatDeepSeekFixed({
87
+ apiKey: credentials.apiKey,
88
+ baseURL: credentials.baseUrl || 'https://api.deepseek.com',
89
+ model,
90
+ thinkingMode,
91
+ temperature,
92
+ maxTokens,
93
+ timeout,
94
+ });
95
+ return { response: chatModel };
96
+ }
97
+ }
98
+ exports.LmChatDeepSeekFixed = LmChatDeepSeekFixed;
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@drx974/n8n-nodes-deepseek-fixed",
3
+ "version": "0.1.0",
4
+ "description": "DeepSeek Chat Model for n8n AI Agent — fixes strict tool-schema rejection and reasoning_content passback",
5
+ "keywords": [
6
+ "n8n-community-node-package"
7
+ ],
8
+ "license": "MIT",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "main": "dist/nodes/LmChatDeepSeekFixed/LmChatDeepSeekFixed.node.js",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "n8n": {
17
+ "n8nNodesApiVersion": 1,
18
+ "credentials": [
19
+ "dist/credentials/DeepSeekFixedApi.credentials.js"
20
+ ],
21
+ "nodes": [
22
+ "dist/nodes/LmChatDeepSeekFixed/LmChatDeepSeekFixed.node.js"
23
+ ]
24
+ },
25
+ "scripts": {
26
+ "build": "tsc"
27
+ },
28
+ "peerDependencies": {
29
+ "@langchain/core": "*"
30
+ },
31
+ "dependencies": {
32
+ "openai": "^6.46.0"
33
+ },
34
+ "devDependencies": {
35
+ "@langchain/core": "^1.2.2",
36
+ "@types/node": "^24.0.0",
37
+ "n8n-workflow": "^2.16.0",
38
+ "typescript": "^5.9.3"
39
+ }
40
+ }