@agentforge-io/llm-langchain 0.1.0 → 0.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/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/providers/gemini-provider.d.ts +16 -0
- package/dist/providers/gemini-provider.js +212 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
// The host wires which provider runs per tenant; this package only ships
|
|
17
17
|
// the adapters, not the resolution logic.
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.OpenAIProvider = void 0;
|
|
19
|
+
exports.GeminiProvider = exports.OpenAIProvider = void 0;
|
|
20
20
|
var openai_provider_1 = require("./providers/openai-provider");
|
|
21
21
|
Object.defineProperty(exports, "OpenAIProvider", { enumerable: true, get: function () { return openai_provider_1.OpenAIProvider; } });
|
|
22
|
+
var gemini_provider_1 = require("./providers/gemini-provider");
|
|
23
|
+
Object.defineProperty(exports, "GeminiProvider", { enumerable: true, get: function () { return gemini_provider_1.GeminiProvider; } });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LLMProvider, LLMProviderCapabilities, LLMStreamEvent, LLMStreamParams } from '@agentforge-io/core/ai';
|
|
2
|
+
export interface GeminiProviderOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Provider id surfaced to the platform resolver. Defaults to `'gemini'`. */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Human-readable label. Defaults to `'Google Gemini'`. */
|
|
7
|
+
displayName?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class GeminiProvider implements LLMProvider {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly displayName: string;
|
|
12
|
+
readonly capabilities: LLMProviderCapabilities;
|
|
13
|
+
private readonly apiKey;
|
|
14
|
+
constructor(opts: GeminiProviderOptions);
|
|
15
|
+
stream(params: LLMStreamParams): AsyncGenerator<LLMStreamEvent>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Gemini provider (via LangChain) ────────────────────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// Adapts `@langchain/google-genai`'s `ChatGoogleGenerativeAI` to the
|
|
5
|
+
// framework-free `LLMProvider` contract. Mirrors `OpenAIProvider`'s shape
|
|
6
|
+
// so the rest of the system (registry, resolver, agent runner) treats
|
|
7
|
+
// every provider uniformly.
|
|
8
|
+
//
|
|
9
|
+
// Notable differences from OpenAI:
|
|
10
|
+
// - Gemini's tool-calling event shape uses `tool_calls` on the final
|
|
11
|
+
// AIMessage rather than `tool_call_chunks` arriving incrementally.
|
|
12
|
+
// LangChain normalises both into a similar surface but we read the
|
|
13
|
+
// final tool_calls off the LAST chunk's `.tool_calls` array.
|
|
14
|
+
// - Gemini's stop reasons are STOP / MAX_TOKENS / SAFETY / RECITATION /
|
|
15
|
+
// OTHER. We collapse SAFETY/RECITATION/OTHER into `end_turn` because
|
|
16
|
+
// none of them carry runner-actionable semantics — the model just
|
|
17
|
+
// stopped early. The platform's logging layer reads the raw reason
|
|
18
|
+
// separately for telemetry.
|
|
19
|
+
//
|
|
20
|
+
// Like `OpenAIProvider`, this class doesn't load LangChain modules until
|
|
21
|
+
// `stream()` is called — `ChatGoogleGenerativeAI` is constructed per turn
|
|
22
|
+
// (the model id varies). Safe because the HTTP client is stateless.
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.GeminiProvider = void 0;
|
|
25
|
+
const google_genai_1 = require("@langchain/google-genai");
|
|
26
|
+
const messages_1 = require("@langchain/core/messages");
|
|
27
|
+
class GeminiProvider {
|
|
28
|
+
constructor(opts) {
|
|
29
|
+
this.capabilities = {
|
|
30
|
+
supportsTools: true,
|
|
31
|
+
supportsStreaming: true,
|
|
32
|
+
// Gemini accepts `temperature` on every modern (1.5+) family.
|
|
33
|
+
supportsTemperature: true,
|
|
34
|
+
// Gemini DOES allow multiple function calls per turn — they arrive
|
|
35
|
+
// in the same response's `functionCalls` array, which LangChain
|
|
36
|
+
// surfaces as `tool_calls`.
|
|
37
|
+
supportsParallelTools: true,
|
|
38
|
+
};
|
|
39
|
+
this.id = opts.id ?? 'gemini';
|
|
40
|
+
this.displayName = opts.displayName ?? 'Google Gemini';
|
|
41
|
+
this.apiKey = opts.apiKey;
|
|
42
|
+
}
|
|
43
|
+
async *stream(params) {
|
|
44
|
+
const llm = new google_genai_1.ChatGoogleGenerativeAI({
|
|
45
|
+
apiKey: this.apiKey,
|
|
46
|
+
model: params.model,
|
|
47
|
+
temperature: params.temperature,
|
|
48
|
+
maxOutputTokens: params.maxTokens,
|
|
49
|
+
streaming: true,
|
|
50
|
+
});
|
|
51
|
+
const bound = params.tools && params.tools.length > 0
|
|
52
|
+
? llm.bindTools(params.tools.map(toLangChainTool))
|
|
53
|
+
: llm;
|
|
54
|
+
const messages = toLangChainMessages(params.systemPrompt, params.messages);
|
|
55
|
+
let textBuffer = '';
|
|
56
|
+
// Gemini emits tool_calls as a finalised list on the closing
|
|
57
|
+
// chunk (not incremental like OpenAI). We track the last chunk's
|
|
58
|
+
// `tool_calls` and parse them at end-of-stream.
|
|
59
|
+
let finalToolCalls = [];
|
|
60
|
+
let usageInput = 0;
|
|
61
|
+
let usageOutput = 0;
|
|
62
|
+
let stopReason = 'end_turn';
|
|
63
|
+
const stream = await bound.stream(messages);
|
|
64
|
+
for await (const chunk of stream) {
|
|
65
|
+
const text = typeof chunk.content === 'string'
|
|
66
|
+
? chunk.content
|
|
67
|
+
: chunk.content
|
|
68
|
+
.map((c) => typeof c === 'string'
|
|
69
|
+
? c
|
|
70
|
+
: 'text' in c
|
|
71
|
+
? c.text
|
|
72
|
+
: '')
|
|
73
|
+
.join('');
|
|
74
|
+
if (text) {
|
|
75
|
+
textBuffer += text;
|
|
76
|
+
yield { type: 'text_delta', delta: text };
|
|
77
|
+
}
|
|
78
|
+
const chunkToolCalls = chunk
|
|
79
|
+
.tool_calls;
|
|
80
|
+
if (chunkToolCalls && chunkToolCalls.length > 0) {
|
|
81
|
+
finalToolCalls = chunkToolCalls;
|
|
82
|
+
}
|
|
83
|
+
const usageMeta = chunk.usage_metadata;
|
|
84
|
+
if (usageMeta) {
|
|
85
|
+
usageInput = usageMeta.input_tokens ?? usageInput;
|
|
86
|
+
usageOutput = usageMeta.output_tokens ?? usageOutput;
|
|
87
|
+
}
|
|
88
|
+
const finishReason = chunk
|
|
89
|
+
.response_metadata?.finish_reason ??
|
|
90
|
+
chunk.finish_reason;
|
|
91
|
+
if (finishReason) {
|
|
92
|
+
stopReason = normalizeFinishReason(finishReason);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const finalContent = [];
|
|
96
|
+
if (textBuffer) {
|
|
97
|
+
finalContent.push({ type: 'text', text: textBuffer });
|
|
98
|
+
}
|
|
99
|
+
for (const call of finalToolCalls) {
|
|
100
|
+
const id = call.id ?? `call_${Math.random().toString(36).slice(2, 10)}`;
|
|
101
|
+
yield {
|
|
102
|
+
type: 'tool_use_start',
|
|
103
|
+
toolUseId: id,
|
|
104
|
+
toolName: call.name,
|
|
105
|
+
input: call.args,
|
|
106
|
+
};
|
|
107
|
+
finalContent.push({
|
|
108
|
+
type: 'tool_use',
|
|
109
|
+
id,
|
|
110
|
+
name: call.name,
|
|
111
|
+
input: call.args,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (finalToolCalls.length > 0 && stopReason !== 'max_tokens') {
|
|
115
|
+
stopReason = 'tool_use';
|
|
116
|
+
}
|
|
117
|
+
yield {
|
|
118
|
+
type: 'usage_delta',
|
|
119
|
+
usage: {
|
|
120
|
+
inputTokens: usageInput,
|
|
121
|
+
outputTokens: usageOutput,
|
|
122
|
+
totalTokens: usageInput + usageOutput,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
yield {
|
|
126
|
+
type: 'message_stop',
|
|
127
|
+
stopReason,
|
|
128
|
+
content: finalContent,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.GeminiProvider = GeminiProvider;
|
|
133
|
+
// ─── Translation helpers ────────────────────────────────────────────────────
|
|
134
|
+
function toLangChainTool(t) {
|
|
135
|
+
return {
|
|
136
|
+
type: 'function',
|
|
137
|
+
function: {
|
|
138
|
+
name: t.name,
|
|
139
|
+
description: t.description,
|
|
140
|
+
parameters: t.input_schema,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function toLangChainMessages(systemPrompt, messages) {
|
|
145
|
+
const out = [];
|
|
146
|
+
if (systemPrompt) {
|
|
147
|
+
out.push(new messages_1.SystemMessage(systemPrompt));
|
|
148
|
+
}
|
|
149
|
+
for (const m of messages) {
|
|
150
|
+
if (typeof m.content === 'string') {
|
|
151
|
+
out.push(m.role === 'user'
|
|
152
|
+
? new messages_1.HumanMessage(m.content)
|
|
153
|
+
: new messages_1.AIMessage(m.content));
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (m.role === 'user') {
|
|
157
|
+
const textParts = [];
|
|
158
|
+
for (const block of m.content) {
|
|
159
|
+
if (block.type === 'text') {
|
|
160
|
+
textParts.push(block.text);
|
|
161
|
+
}
|
|
162
|
+
else if (block.type === 'tool_result') {
|
|
163
|
+
if (textParts.length > 0) {
|
|
164
|
+
out.push(new messages_1.HumanMessage(textParts.join('\n')));
|
|
165
|
+
textParts.length = 0;
|
|
166
|
+
}
|
|
167
|
+
out.push(new messages_1.ToolMessage({
|
|
168
|
+
tool_call_id: block.tool_use_id,
|
|
169
|
+
content: block.content,
|
|
170
|
+
status: block.is_error ? 'error' : 'success',
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (textParts.length > 0) {
|
|
175
|
+
out.push(new messages_1.HumanMessage(textParts.join('\n')));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
const textParts = [];
|
|
180
|
+
const toolCalls = [];
|
|
181
|
+
for (const block of m.content) {
|
|
182
|
+
if (block.type === 'text')
|
|
183
|
+
textParts.push(block.text);
|
|
184
|
+
else if (block.type === 'tool_use')
|
|
185
|
+
toolCalls.push({
|
|
186
|
+
id: block.id,
|
|
187
|
+
name: block.name,
|
|
188
|
+
args: block.input,
|
|
189
|
+
type: 'tool_call',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
out.push(new messages_1.AIMessage({
|
|
193
|
+
content: textParts.join('\n'),
|
|
194
|
+
tool_calls: toolCalls,
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
function normalizeFinishReason(raw) {
|
|
201
|
+
switch (raw.toUpperCase()) {
|
|
202
|
+
case 'TOOL_CALLS':
|
|
203
|
+
case 'FUNCTION_CALL':
|
|
204
|
+
return 'tool_use';
|
|
205
|
+
case 'MAX_TOKENS':
|
|
206
|
+
case 'LENGTH':
|
|
207
|
+
return 'max_tokens';
|
|
208
|
+
case 'STOP':
|
|
209
|
+
default:
|
|
210
|
+
return 'end_turn';
|
|
211
|
+
}
|
|
212
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/llm-langchain",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "LangChain-backed LLM providers (OpenAI, Grok, Gemini) implementing the framework-free `LLMProvider` contract from @agentforge-io/core. Drop-in replacements for AnthropicProvider — same stream events, same tool schema, no changes to the agent runner.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|