Skip to content

Commit a8e0b73

Browse files
committed
feat(ai-gateway): add generate embeddings Action
1 parent 05a05af commit a8e0b73

File tree

3 files changed

+216
-2
lines changed

3 files changed

+216
-2
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started.
7979
### Action Nodes
8080

8181
<!-- PLUGINS:START - Do not remove. Auto-generated by discover-plugins -->
82-
- **AI Gateway**: Generate Text, Generate Image
82+
- **AI Gateway**: Generate Text, Generate Image, Generate Embeddings
8383
- **Blob**: Put Blob, List Blobs
8484
- **Clerk**: Get User, Create User, Update User, Delete User
8585
- **fal.ai**: Generate Image, Generate Video, Upscale Image, Remove Background, Image to Image
@@ -92,6 +92,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started.
9292
- **Stripe**: Create Customer, Get Customer, Create Invoice
9393
- **Superagent**: Guard, Redact
9494
- **v0**: Create Chat, Send Message
95+
- **Webflow**: List Sites, Get Site, Publish Site
9596
<!-- PLUGINS:END -->
9697

9798
## Code Generation

plugins/ai-gateway/index.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AiGatewayIcon } from "./icon";
55
const aiGatewayPlugin: IntegrationPlugin = {
66
type: "ai-gateway",
77
label: "AI Gateway",
8-
description: "Generate text and images using AI models",
8+
description: "Generate text, images, and embeddings using AI models",
99

1010
icon: AiGatewayIcon,
1111

@@ -141,6 +141,75 @@ const aiGatewayPlugin: IntegrationPlugin = {
141141
},
142142
],
143143
},
144+
{
145+
slug: "generate-embeddings",
146+
label: "Generate Embeddings",
147+
description: "Generate text embeddings using AI models",
148+
category: "AI Gateway",
149+
stepFunction: "generateEmbeddingsStep",
150+
stepImportPath: "generate-embeddings",
151+
outputFields: [
152+
{ field: "embedding", description: "Single embedding vector" },
153+
{ field: "embeddings", description: "Batch embedding vectors" },
154+
],
155+
configFields: [
156+
{
157+
key: "embeddingMode",
158+
label: "Embedding Mode",
159+
type: "select",
160+
defaultValue: "single",
161+
options: [
162+
{ value: "single", label: "Single Value" },
163+
{ value: "batch", label: "Batch Values" },
164+
],
165+
required: true,
166+
},
167+
{
168+
key: "embeddingModel",
169+
label: "Model",
170+
type: "select",
171+
defaultValue: "openai/text-embedding-3-small",
172+
options: [
173+
{ value: "amazon/titan-embed-text-v2", label: "Amazon Titan Embed Text V2"},
174+
{ value: "cohere/embed-v4.0", label: "Cohere Embed V4.0"},
175+
{ value: "google/gemini-embedding-001", label: "Gemini Embedding 001"},
176+
{ value: "google/text-embedding-005", label: "Google Text Embedding 005" },
177+
{ value: "google/text-multilingual-embedding-002", label: "Google Text Multilingual Embedding 002" },
178+
{ value: "mistral/codestral-embed", label: "Mistral Codestral Embed" },
179+
{ value: "mistral/mistral-embed", label: "Mistral Embed" },
180+
{ value: "openai/text-embedding-3-large", label: "OpenAI Text Embedding 3 Large" },
181+
{ value: "openai/text-embedding-3-small", label: "OpenAI Text Embedding 3 Small" },
182+
{ value: "openai/text-embedding-ada-002", label: "OpenAI Text Embedding Ada 002" },
183+
{ value: "voyage/voyage-3-large", label: "Voyage 3 Large" },
184+
{ value: "voyage/voyage-3.5", label: "Voyage 3.5" },
185+
{ value: "voyage/voyage-3.5-lite", label: "Voyage 3.5 Lite" },
186+
{ value: "voyage/voyage-code-3", label: "Voyage Code 3" },
187+
],
188+
required: true,
189+
},
190+
{
191+
key: "embeddingValue",
192+
label: "Text to Embed",
193+
type: "template-input",
194+
placeholder: "Enter text to embed. Use {{NodeName.field}} to reference previous outputs.",
195+
example: "sunny day at the beach",
196+
showWhen: { field: "embeddingMode", equals: "single" },
197+
required: true,
198+
},
199+
{
200+
key: "embeddingValues",
201+
label: "Texts to Embed",
202+
type: "template-textarea",
203+
placeholder:
204+
"Enter one text per line for batch embedding:\n- First text\n- Second text\nUse {{NodeName.field}} to reference previous outputs.",
205+
example:
206+
"sunny day at the beach\nrainy afternoon in the city\nsnowy night in the mountains",
207+
rows: 6,
208+
showWhen: { field: "embeddingMode", equals: "batch" },
209+
required: true,
210+
}
211+
],
212+
},
144213
],
145214
};
146215

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import "server-only";
2+
3+
import { createGateway, embed, embedMany } from "ai";
4+
import { fetchCredentials } from "@/lib/credential-fetcher";
5+
import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
6+
import { getErrorMessageAsync } from "@/lib/utils";
7+
import type { AiGatewayCredentials } from "../credentials";
8+
9+
type GenerateEmbeddingsResult =
10+
| { success: true; embedding: number[]; }
11+
| {
12+
success: true;
13+
embeddings: number[][];
14+
}
15+
| { success: false; error: string };
16+
17+
export type GenerateEmbeddingsCoreInput = {
18+
embeddingMode: "single" | "batch";
19+
embeddingModel: string;
20+
embeddingValue?: string;
21+
embeddingValues?: string;
22+
};
23+
24+
export type GenerateEmbeddingsInput = StepInput &
25+
GenerateEmbeddingsCoreInput & {
26+
integrationId?: string;
27+
};
28+
29+
30+
/**
31+
* Parse batch embedding values from textarea input (one per line)
32+
*/
33+
function parseEmbeddingValues(valuesText?: string): string[] {
34+
if (!valuesText) {
35+
return [];
36+
}
37+
38+
return valuesText
39+
.split("\n")
40+
.map((line) => line.trim())
41+
.filter((line) => line.length > 0);
42+
}
43+
44+
/**
45+
* Core logic - portable between app and export
46+
*/
47+
async function stepHandler(
48+
input: GenerateEmbeddingsCoreInput,
49+
credentials: AiGatewayCredentials,
50+
): Promise<GenerateEmbeddingsResult> {
51+
const apiKey = credentials.AI_GATEWAY_API_KEY;
52+
53+
if (!apiKey) {
54+
return {
55+
success: false,
56+
error:
57+
"AI_GATEWAY_API_KEY is not configured. Please add it in Project Integrations.",
58+
};
59+
}
60+
61+
const mode = input.embeddingMode || "single";
62+
const modelId = input.embeddingModel || "openai/text-embedding-3-small";
63+
64+
try {
65+
const gateway = createGateway({
66+
apiKey,
67+
});
68+
69+
// Handle single embedding mode
70+
if (mode === "single") {
71+
const value = input.embeddingValue?.trim() || "";
72+
73+
if (!value) {
74+
return {
75+
success: false,
76+
error: "Embedding value is required for single mode",
77+
};
78+
}
79+
80+
const { embedding } = await embed({
81+
model: gateway.textEmbeddingModel(modelId),
82+
value,
83+
});
84+
85+
return {
86+
success: true,
87+
embedding,
88+
};
89+
}
90+
91+
// Handle batch embedding mode
92+
if (mode === "batch") {
93+
const valuesText = input.embeddingValues?.trim() || "";
94+
const values = parseEmbeddingValues(valuesText);
95+
96+
if (values.length === 0) {
97+
return {
98+
success: false,
99+
error:
100+
"At least one embedding value is required for batch mode (one per line)",
101+
};
102+
}
103+
104+
const { embeddings } = await embedMany({
105+
model: gateway.textEmbeddingModel(modelId),
106+
values,
107+
});
108+
109+
return {
110+
success: true,
111+
embeddings,
112+
};
113+
}
114+
115+
return {
116+
success: false,
117+
error: `Invalid embedding mode: ${mode}`,
118+
};
119+
} catch (error) {
120+
const message = await getErrorMessageAsync(error);
121+
return {
122+
success: false,
123+
error: `Embedding generation failed: ${message}`,
124+
};
125+
}
126+
}
127+
128+
/**
129+
* App entry point - fetches credentials and wraps with logging
130+
*/
131+
export async function generateEmbeddingsStep(
132+
input: GenerateEmbeddingsInput,
133+
): Promise<GenerateEmbeddingsResult> {
134+
"use step";
135+
136+
const credentials = input.integrationId
137+
? await fetchCredentials(input.integrationId)
138+
: {};
139+
140+
return withStepLogging(input, () => stepHandler(input, credentials));
141+
}
142+
generateEmbeddingsStep.maxRetries = 0;
143+
144+
export const _integrationType = "ai-gateway";

0 commit comments

Comments
 (0)