@flink-app/openai-adapter 2.0.0-alpha.100
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/LICENSE +21 -0
- package/README.md +624 -0
- package/dist/OpenAIAdapter.d.ts +245 -0
- package/dist/OpenAIAdapter.d.ts.map +1 -0
- package/dist/OpenAIAdapter.js +667 -0
- package/dist/OpenAIAdapter.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type { FlinkToolSchema, LLMAdapter, LLMMessage, LLMStreamChunk } from "@flink-app/flink/ai";
|
|
2
|
+
export interface OpenAIAdapterOptions {
|
|
3
|
+
/**
|
|
4
|
+
* OpenAI API key
|
|
5
|
+
*/
|
|
6
|
+
apiKey: string;
|
|
7
|
+
/**
|
|
8
|
+
* Model to use (e.g., "gpt-4.1", "gpt-5", "o4-mini")
|
|
9
|
+
*/
|
|
10
|
+
model: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional: Enable structured outputs with JSON schema validation
|
|
13
|
+
* Ensures the model output matches the provided schema with 100% reliability
|
|
14
|
+
*
|
|
15
|
+
* In Responses API, this is configured via text.format instead of response_format
|
|
16
|
+
* @see https://platform.openai.com/docs/guides/structured-outputs
|
|
17
|
+
*/
|
|
18
|
+
structuredOutput?: {
|
|
19
|
+
type: "json_schema";
|
|
20
|
+
name: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
schema: Record<string, unknown>;
|
|
23
|
+
strict?: boolean;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Optional: Enable debug logging for development and troubleshooting
|
|
27
|
+
*
|
|
28
|
+
* When enabled, logs:
|
|
29
|
+
* - Full request parameters sent to OpenAI
|
|
30
|
+
* - Tool call decisions made by the LLM
|
|
31
|
+
* - Token usage and performance metrics
|
|
32
|
+
*
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
debug?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* OpenAI adapter for Flink AI framework using the Responses API
|
|
39
|
+
*
|
|
40
|
+
* Built on OpenAI's Responses API (evolution of Chat Completions) which provides:
|
|
41
|
+
* - Step-aware reasoning: Model can return multiple tool calls in a single response
|
|
42
|
+
* - Explicit tool invocation: Tool calls and results are first-class typed items
|
|
43
|
+
* - Better performance: 3% improvement on SWE-bench, 5% on TAUBench
|
|
44
|
+
*
|
|
45
|
+
* ## Architecture
|
|
46
|
+
*
|
|
47
|
+
* IMPORTANT: The Responses API does NOT run your agent loop.
|
|
48
|
+
*
|
|
49
|
+
* What it provides:
|
|
50
|
+
* - Multiple tool calls in one response (but you execute them)
|
|
51
|
+
* - Structured step types (message, function_call, function_call_output)
|
|
52
|
+
* - Streaming event taxonomy
|
|
53
|
+
*
|
|
54
|
+
* What you still handle:
|
|
55
|
+
* - Tool execution (Flink's AgentRunner does this)
|
|
56
|
+
* - Multi-turn loops (call API → execute tools → call API again)
|
|
57
|
+
* - Deciding when to stop
|
|
58
|
+
* - Managing conversation state
|
|
59
|
+
*
|
|
60
|
+
* This is intentional - it gives you full control over the agent loop.
|
|
61
|
+
*
|
|
62
|
+
* ## Streaming-Only Design
|
|
63
|
+
*
|
|
64
|
+
* This adapter uses streaming as the primary method because:
|
|
65
|
+
* - Better user experience (time-to-first-token <500ms)
|
|
66
|
+
* - Prevents HTTP timeouts for long responses
|
|
67
|
+
* - FlinkAgent's lazy generator pattern supports both streaming and non-streaming UX
|
|
68
|
+
*
|
|
69
|
+
* For non-streaming consumption, simply await the final result:
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const response = agent.execute({ message: "Hello" });
|
|
72
|
+
* const result = await response.result; // Consumes stream but returns final result
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* ## System Instructions - Two Approaches
|
|
76
|
+
*
|
|
77
|
+
* You can provide system instructions in two ways:
|
|
78
|
+
*
|
|
79
|
+
* 1. **Top-level instructions** (via Flink's instructions parameter):
|
|
80
|
+
* - Automatically prepended as system message to conversation
|
|
81
|
+
* - Simpler approach, recommended for most cases
|
|
82
|
+
* - Following Vercel AI SDK pattern
|
|
83
|
+
*
|
|
84
|
+
* 2. **System role messages** (via messages array):
|
|
85
|
+
* - Add `{ role: "system", content: "..." }` to messages
|
|
86
|
+
* - Gives more control over message structure and placement
|
|
87
|
+
* - Useful when you need system context at specific points in conversation
|
|
88
|
+
*
|
|
89
|
+
* Both approaches can be used together - system messages in input array + top-level instructions.
|
|
90
|
+
*
|
|
91
|
+
* ## Features
|
|
92
|
+
* - ✅ Step-based tool calling with multiple calls per response
|
|
93
|
+
* - ✅ Streaming support with event-based updates
|
|
94
|
+
* - ✅ Structured outputs with JSON schema (100% reliability)
|
|
95
|
+
* - ✅ Support for all OpenAI models (GPT-4.1, GPT-5, o-series)
|
|
96
|
+
* - ✅ Flexible system instructions (top-level or in messages)
|
|
97
|
+
*
|
|
98
|
+
* ## Basic Usage
|
|
99
|
+
* ```typescript
|
|
100
|
+
* import { OpenAIAdapter } from "@flink-app/openai-adapter";
|
|
101
|
+
* import { FlinkApp } from "@flink-app/flink";
|
|
102
|
+
*
|
|
103
|
+
* const app = new FlinkApp({
|
|
104
|
+
* ai: {
|
|
105
|
+
* llms: {
|
|
106
|
+
* default: new OpenAIAdapter({
|
|
107
|
+
* apiKey: process.env.OPENAI_API_KEY!,
|
|
108
|
+
* model: "gpt-5"
|
|
109
|
+
* }),
|
|
110
|
+
* },
|
|
111
|
+
* },
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* ## Structured Outputs
|
|
116
|
+
* ```typescript
|
|
117
|
+
* const adapter = new OpenAIAdapter({
|
|
118
|
+
* apiKey: process.env.OPENAI_API_KEY!,
|
|
119
|
+
* model: "gpt-5",
|
|
120
|
+
* structuredOutput: {
|
|
121
|
+
* type: "json_schema",
|
|
122
|
+
* name: "user_profile",
|
|
123
|
+
* schema: {
|
|
124
|
+
* type: "object",
|
|
125
|
+
* properties: {
|
|
126
|
+
* name: { type: "string" },
|
|
127
|
+
* age: { type: "number" }
|
|
128
|
+
* },
|
|
129
|
+
* required: ["name", "age"],
|
|
130
|
+
* additionalProperties: false
|
|
131
|
+
* },
|
|
132
|
+
* strict: true
|
|
133
|
+
* }
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export declare class OpenAIAdapter implements LLMAdapter<FlinkToolSchema> {
|
|
138
|
+
private client;
|
|
139
|
+
private model;
|
|
140
|
+
private structuredOutput?;
|
|
141
|
+
debug: boolean;
|
|
142
|
+
private log;
|
|
143
|
+
constructor(options: OpenAIAdapterOptions);
|
|
144
|
+
constructor(apiKey: string, model: string);
|
|
145
|
+
stream(params: {
|
|
146
|
+
instructions: string;
|
|
147
|
+
messages: LLMMessage[];
|
|
148
|
+
tools: FlinkToolSchema[];
|
|
149
|
+
maxTokens: number;
|
|
150
|
+
temperature: number;
|
|
151
|
+
providerMetadata?: Record<string, any>;
|
|
152
|
+
}): AsyncGenerator<LLMStreamChunk>;
|
|
153
|
+
/**
|
|
154
|
+
* Convert Flink messages to OpenAI Responses API input format
|
|
155
|
+
*
|
|
156
|
+
* Always sends full conversation history for reliability.
|
|
157
|
+
*
|
|
158
|
+
* The Responses API accepts:
|
|
159
|
+
* 1. System messages: { role: "system", content: "..." }
|
|
160
|
+
* 2. User messages: { role: "user", content: "..." }
|
|
161
|
+
* 3. Assistant messages: { role: "assistant", content: "..." }
|
|
162
|
+
* 4. Function calls: { type: "function_call", ... } ← From previous responses
|
|
163
|
+
* 5. Function call outputs: { type: "function_call_output", ... } ← Tool results
|
|
164
|
+
*
|
|
165
|
+
* Multi-turn pattern:
|
|
166
|
+
* - User: "What's the weather?"
|
|
167
|
+
* - Assistant: "Let me check" + function_call
|
|
168
|
+
* - function_call_output: "22°C"
|
|
169
|
+
* - Assistant: "It's 22°C and sunny"
|
|
170
|
+
* - User: "What about tomorrow?"
|
|
171
|
+
*
|
|
172
|
+
* All message types are preserved for full conversation context.
|
|
173
|
+
*/
|
|
174
|
+
/**
|
|
175
|
+
* Build the OpenAI user content from text/image content blocks.
|
|
176
|
+
* Returns a plain string when there are no images (most common case),
|
|
177
|
+
* or an array of input_text / input_image items for multimodal messages.
|
|
178
|
+
* Returns null if there are no user-facing blocks.
|
|
179
|
+
*/
|
|
180
|
+
private buildOpenAIUserContent;
|
|
181
|
+
private convertMessagesToResponsesInput;
|
|
182
|
+
/**
|
|
183
|
+
* Convert Flink tools to Responses API format
|
|
184
|
+
*
|
|
185
|
+
* Responses API uses internally-tagged format:
|
|
186
|
+
* { type: "function", name: "...", description: "...", parameters: {...} }
|
|
187
|
+
*
|
|
188
|
+
* vs Chat Completions externally-tagged format:
|
|
189
|
+
* { type: "function", function: { name: "...", description: "...", parameters: {...} } }
|
|
190
|
+
*/
|
|
191
|
+
private convertToolsToResponsesFormat;
|
|
192
|
+
/**
|
|
193
|
+
* Sanitize JSON Schema for OpenAI's strict mode
|
|
194
|
+
*
|
|
195
|
+
* OpenAI's strict mode only supports a subset of JSON Schema.
|
|
196
|
+
* This function removes unsupported keywords that would cause the model
|
|
197
|
+
* to silently fail and return empty {} arguments.
|
|
198
|
+
*
|
|
199
|
+
* Unsupported keywords (stripped):
|
|
200
|
+
* - $schema (meta-schema reference)
|
|
201
|
+
* - minLength, maxLength, pattern (string constraints)
|
|
202
|
+
* - minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf (number constraints)
|
|
203
|
+
* - minItems, maxItems, uniqueItems (array constraints)
|
|
204
|
+
* - minProperties, maxProperties (object constraints)
|
|
205
|
+
* - format (string format hints like "email", "uri")
|
|
206
|
+
*
|
|
207
|
+
* Enforced requirements:
|
|
208
|
+
* - additionalProperties: false on all objects (required by strict mode)
|
|
209
|
+
* - required array must include all properties without default values
|
|
210
|
+
*
|
|
211
|
+
* Note: Removed constraints should be documented in the description field
|
|
212
|
+
* where the model can read them as natural language instructions.
|
|
213
|
+
*
|
|
214
|
+
* Reference: https://platform.openai.com/docs/guides/structured-outputs
|
|
215
|
+
*/
|
|
216
|
+
private sanitizeSchemaForStrictMode;
|
|
217
|
+
/**
|
|
218
|
+
* Check if the model is a reasoning model that doesn't support temperature
|
|
219
|
+
*
|
|
220
|
+
* Reasoning models use a different internal process and don't allow temperature control:
|
|
221
|
+
* - o1, o1-mini, o1-preview
|
|
222
|
+
* - o3, o3-mini
|
|
223
|
+
* - gpt-5 (uses reasoning approach)
|
|
224
|
+
*/
|
|
225
|
+
private isReasoningModel;
|
|
226
|
+
/**
|
|
227
|
+
* Normalize stop reason from Responses API to Flink format
|
|
228
|
+
*
|
|
229
|
+
* LIMITATION: This mapping is lossy. Responses API provides richer status:
|
|
230
|
+
* - "completed" → "end_turn"
|
|
231
|
+
* - "incomplete" → "max_tokens"
|
|
232
|
+
* - "failed" → "max_tokens" (loses error signal)
|
|
233
|
+
* - "cancelled" → "end_turn" (loses cancellation signal)
|
|
234
|
+
*
|
|
235
|
+
* Future improvement: Add LLMResponse.metadata field to preserve:
|
|
236
|
+
* - Raw status from provider
|
|
237
|
+
* - Safety stop reasons
|
|
238
|
+
* - Tool execution errors
|
|
239
|
+
* - Model-specific error codes
|
|
240
|
+
*
|
|
241
|
+
* For now, we map to Flink's limited set and lose granularity.
|
|
242
|
+
*/
|
|
243
|
+
private normalizeStopReason;
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=OpenAIAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OpenAIAdapter.d.ts","sourceRoot":"","sources":["../src/OpenAIAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAmB,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAcpH,MAAM,WAAW,oBAAoB;IACjC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE;QACf,IAAI,EAAE,aAAa,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,MAAM,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IAEF;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,qBAAa,aAAc,YAAW,UAAU,CAAC,eAAe,CAAC;IAC7D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,gBAAgB,CAAC,CAA2C;IAC7D,KAAK,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,GAAG,CAAmD;gBAElD,OAAO,EAAE,oBAAoB;gBAC7B,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAsBlC,MAAM,CAAC,MAAM,EAAE;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,UAAU,EAAE,CAAC;QACvB,KAAK,EAAE,eAAe,EAAE,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KAC1C,GAAG,cAAc,CAAC,cAAc,CAAC;IAyKlC;;;;;;;;;;;;;;;;;;;;OAoBG;IAEH;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAqB9B,OAAO,CAAC,+BAA+B;IAqGvC;;;;;;;;OAQG;IACH,OAAO,CAAC,6BAA6B;IAUrC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,2BAA2B;IA8JnC;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,mBAAmB;CAkB9B"}
|