@hamp10/agentforge 0.2.9 → 0.2.10
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/package.json +1 -1
- package/src/OllamaAgent.js +51 -6
package/package.json
CHANGED
package/src/OllamaAgent.js
CHANGED
|
@@ -104,6 +104,32 @@ const TOOLS = [
|
|
|
104
104
|
}
|
|
105
105
|
];
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Detect text-based tool calls from model content.
|
|
109
|
+
* Some models (qwen3-vl:8b) output tool calls as JSON text lines in content
|
|
110
|
+
* instead of using the OpenAI tool_calls format.
|
|
111
|
+
* Returns array of {name, arguments} if ALL non-empty lines are valid tool calls, else null.
|
|
112
|
+
*/
|
|
113
|
+
function _parseTextToolCalls(content) {
|
|
114
|
+
if (!content) return null;
|
|
115
|
+
const lines = content.trim().split('\n').map(l => l.trim()).filter(Boolean);
|
|
116
|
+
if (lines.length === 0) return null;
|
|
117
|
+
const calls = [];
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
try {
|
|
120
|
+
const obj = JSON.parse(line);
|
|
121
|
+
if (typeof obj.name === 'string' && obj.arguments && typeof obj.arguments === 'object') {
|
|
122
|
+
calls.push({ name: obj.name, arguments: obj.arguments });
|
|
123
|
+
} else {
|
|
124
|
+
return null; // Valid JSON but not a tool call — treat whole content as text
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
return null; // Non-JSON line — treat whole content as text
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return calls.length > 0 ? calls : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
107
133
|
/**
|
|
108
134
|
* LocalModelAgent — drop-in replacement for OpenClawCLI.
|
|
109
135
|
* Runs an agentic tool-use loop against ANY OpenAI-compatible local model server.
|
|
@@ -336,10 +362,7 @@ export class OllamaAgent extends EventEmitter {
|
|
|
336
362
|
thinkBuffer = inThinkBlock ? thinkBuffer.slice(thinkBuffer.lastIndexOf('<think>')) : '';
|
|
337
363
|
|
|
338
364
|
streamContent += out;
|
|
339
|
-
|
|
340
|
-
if (out) {
|
|
341
|
-
this.emit('agent_output', { agentId, output: out });
|
|
342
|
-
}
|
|
365
|
+
// Don't emit per-token — we check for JSON tool calls after the full turn
|
|
343
366
|
}
|
|
344
367
|
}
|
|
345
368
|
}
|
|
@@ -355,8 +378,30 @@ export class OllamaAgent extends EventEmitter {
|
|
|
355
378
|
if (thoughtContent) {
|
|
356
379
|
console.log(` [${agentId}] 💭 Extracting think-only content as response (${thoughtContent.length} chars)`);
|
|
357
380
|
streamContent = thoughtContent;
|
|
358
|
-
|
|
359
|
-
|
|
381
|
+
// Don't emit here — detection block below handles it
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ── Detect text-based tool calls or emit text content ─────────────────
|
|
386
|
+
// qwen3-vl:8b outputs tool calls as one JSON object per line in content.
|
|
387
|
+
// If detected, convert to streamToolCalls and suppress the raw JSON output.
|
|
388
|
+
// Otherwise, emit the text content to the dashboard.
|
|
389
|
+
if (Object.keys(streamToolCalls).length === 0 && streamContent) {
|
|
390
|
+
const textCalls = _parseTextToolCalls(streamContent);
|
|
391
|
+
if (textCalls) {
|
|
392
|
+
console.log(` [${agentId}] 🔍 ${textCalls.length} text-based tool call(s) detected — converting to function calls`);
|
|
393
|
+
textCalls.forEach((tc, i) => {
|
|
394
|
+
streamToolCalls[i] = {
|
|
395
|
+
id: `text-${i}`,
|
|
396
|
+
type: 'function',
|
|
397
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
|
|
398
|
+
};
|
|
399
|
+
});
|
|
400
|
+
streamContent = ''; // Don't display raw JSON to user
|
|
401
|
+
} else {
|
|
402
|
+
// Regular text response — emit to dashboard
|
|
403
|
+
allOutput += streamContent;
|
|
404
|
+
if (streamContent.trim()) this.emit('agent_output', { agentId, output: streamContent });
|
|
360
405
|
}
|
|
361
406
|
}
|
|
362
407
|
|