Skip to content

Commit e81e448

Browse files
committed
[ACTION] Update Google Gemini actions
1 parent 89dd08d commit e81e448

File tree

8 files changed

+246
-88
lines changed

8 files changed

+246
-88
lines changed

components/google_gemini/actions/common/generate-content.mjs

+89
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import app from "../../google_gemini.app.mjs";
22
import constants from "../../common/constants.mjs";
3+
import { ConfigurationError } from "@pipedream/platform";
34

45
export default {
56
props: {
@@ -20,6 +21,94 @@ export default {
2021
}),
2122
],
2223
},
24+
text: {
25+
propDefinition: [
26+
app,
27+
"text",
28+
],
29+
},
30+
responseFormat: {
31+
propDefinition: [
32+
app,
33+
"responseFormat",
34+
],
35+
},
36+
history: {
37+
type: "string[]",
38+
label: "Conversation History",
39+
description: "Previous messages in the conversation. Each item must be a valid JSON string with `text` and `role` (either `user` or `model`). Example: `{\"text\": \"Hello\", \"role\": \"user\"}`",
40+
optional: true,
41+
},
42+
safetySettings: {
43+
type: "string[]",
44+
label: "Safety Settings",
45+
description: "Configure content filtering for different harm categories. Each item must be a valid JSON string with `category` (one of: `HARASSMENT`, `HATE_SPEECH`, `SEXUALLY_EXPLICIT`, `DANGEROUS`, `CIVIC`) and `threshold` (one of: `BLOCK_NONE`, `BLOCK_ONLY_HIGH`, `BLOCK_MEDIUM_AND_ABOVE`, `BLOCK_LOW_AND_ABOVE`). Example: `{\"category\": \"HARASSMENT\", \"threshold\": \"BLOCK_MEDIUM_AND_ABOVE\"}`",
46+
optional: true,
47+
},
48+
},
49+
methods: {
50+
formatHistoryToContent(history) {
51+
if (!history?.length) {
52+
return [];
53+
}
54+
55+
return history.map((itemStr) => {
56+
let item;
57+
try {
58+
item = JSON.parse(itemStr);
59+
} catch (e) {
60+
throw new ConfigurationError(`Invalid JSON in history item: ${itemStr}`);
61+
}
62+
63+
if (!item.text || !item.role || ![
64+
"user",
65+
"model",
66+
].includes(item.role)) {
67+
throw new ConfigurationError("Each history item must include `text` and `role` (either `user` or `model`)");
68+
}
69+
70+
return {
71+
parts: [
72+
{
73+
text: item.text,
74+
},
75+
],
76+
role: item.role,
77+
};
78+
});
79+
},
80+
formatSafetySettings(safetySettings) {
81+
if (!safetySettings?.length) {
82+
return undefined;
83+
}
84+
85+
return safetySettings.map((settingStr) => {
86+
let setting;
87+
try {
88+
setting = JSON.parse(settingStr);
89+
} catch (e) {
90+
throw new ConfigurationError(`Invalid JSON in safety setting: ${settingStr}`);
91+
}
92+
93+
if (!setting.category || !setting.threshold) {
94+
throw new ConfigurationError("Each safety setting must include 'category' and 'threshold'");
95+
}
96+
97+
const category = constants.HARM_CATEGORIES[setting.category];
98+
if (!category) {
99+
throw new ConfigurationError(`Invalid category '${setting.category}'. Must be one of: ${Object.keys(constants.HARM_CATEGORIES).join(", ")}`);
100+
}
101+
102+
if (!constants.BLOCK_THRESHOLDS[setting.threshold]) {
103+
throw new ConfigurationError(`Invalid threshold '${setting.threshold}'. Must be one of: ${Object.keys(constants.BLOCK_THRESHOLDS).join(", ")}`);
104+
}
105+
106+
return {
107+
category,
108+
threshold: setting.threshold,
109+
};
110+
});
111+
},
23112
},
24113
async additionalProps() {
25114
const {

components/google_gemini/actions/generate-content-from-text-and-image/generate-content-from-text-and-image.mjs

+32-40
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from "fs";
2+
import mime from "mime";
23
import { ConfigurationError } from "@pipedream/platform";
34
import common from "../common/generate-content.mjs";
45
import utils from "../../common/utils.mjs";
@@ -8,44 +9,30 @@ export default {
89
key: "google_gemini-generate-content-from-text-and-image",
910
name: "Generate Content from Text and Image",
1011
description: "Generates content from both text and image input using the Gemini API. [See the documentation](https://ai.google.dev/tutorials/rest_quickstart#text-and-image_input)",
11-
version: "0.1.2",
12+
version: "0.2.0",
1213
type: "action",
1314
props: {
1415
...common.props,
15-
text: {
16+
mediaPaths: {
1617
propDefinition: [
1718
common.props.app,
18-
"text",
19-
],
20-
},
21-
mimeType: {
22-
propDefinition: [
23-
common.props.app,
24-
"mimeType",
25-
],
26-
},
27-
imagePaths: {
28-
propDefinition: [
29-
common.props.app,
30-
"imagePaths",
31-
],
32-
},
33-
responseFormat: {
34-
propDefinition: [
35-
common.props.app,
36-
"responseFormat",
19+
"mediaPaths",
3720
],
3821
},
3922
},
4023
methods: {
41-
fileToGenerativePart(path, mimeType) {
42-
if (!path) {
24+
...common.methods,
25+
fileToGenerativePart(filePath) {
26+
if (!filePath) {
4327
return;
4428
}
29+
30+
const mimeType = mime.getType(filePath);
31+
4532
return {
4633
inline_data: {
4734
mime_type: mimeType,
48-
data: Buffer.from(fs.readFileSync(path)).toString("base64"),
35+
data: Buffer.from(fs.readFileSync(filePath)).toString("base64"),
4936
},
5037
};
5138
},
@@ -55,8 +42,9 @@ export default {
5542
app,
5643
model,
5744
text,
58-
imagePaths,
59-
mimeType,
45+
history,
46+
mediaPaths,
47+
safetySettings,
6048
responseFormat,
6149
responseSchema,
6250
maxOutputTokens,
@@ -66,28 +54,32 @@ export default {
6654
stopSequences,
6755
} = this;
6856

69-
if (!Array.isArray(imagePaths)) {
70-
throw new ConfigurationError("Image paths must be an array.");
57+
if (!Array.isArray(mediaPaths)) {
58+
throw new ConfigurationError("Image/Video paths must be an array.");
7159
}
7260

73-
if (!imagePaths.length) {
74-
throw new ConfigurationError("At least one image path must be provided.");
61+
if (!mediaPaths.length) {
62+
throw new ConfigurationError("At least one media path must be provided.");
7563
}
7664

65+
const contents = [
66+
...this.formatHistoryToContent(history),
67+
{
68+
parts: [
69+
...mediaPaths.map((path) => this.fileToGenerativePart(path)),
70+
{
71+
text,
72+
},
73+
],
74+
},
75+
];
76+
7777
const response = await app.generateContent({
7878
$,
7979
model,
8080
data: {
81-
contents: [
82-
{
83-
parts: [
84-
{
85-
text,
86-
},
87-
...imagePaths.map((path) => this.fileToGenerativePart(path, mimeType)),
88-
],
89-
},
90-
],
81+
contents,
82+
safetySettings: this.formatSafetySettings(safetySettings),
9183
...(
9284
responseFormat || maxOutputTokens || temperature || topP || topK || stopSequences?.length
9385
? {

components/google_gemini/actions/generate-content-from-text/generate-content-from-text.mjs

+17-25
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,15 @@ export default {
66
key: "google_gemini-generate-content-from-text",
77
name: "Generate Content from Text",
88
description: "Generates content from text input using the Google Gemini API. [See the documentation](https://ai.google.dev/tutorials/rest_quickstart#text-only_input)",
9-
version: "0.1.2",
9+
version: "0.2.0",
1010
type: "action",
11-
props: {
12-
...common.props,
13-
text: {
14-
propDefinition: [
15-
common.props.app,
16-
"text",
17-
],
18-
},
19-
responseFormat: {
20-
propDefinition: [
21-
common.props.app,
22-
"responseFormat",
23-
],
24-
},
25-
},
2611
async run({ $ }) {
2712
const {
2813
app,
2914
model,
3015
text,
16+
history,
17+
safetySettings,
3118
responseFormat,
3219
responseSchema,
3320
maxOutputTokens,
@@ -37,19 +24,24 @@ export default {
3724
stopSequences,
3825
} = this;
3926

27+
const contents = [
28+
...this.formatHistoryToContent(history),
29+
{
30+
parts: [
31+
{
32+
text,
33+
},
34+
],
35+
role: "user",
36+
},
37+
];
38+
4039
const response = await app.generateContent({
4140
$,
4241
model,
4342
data: {
44-
contents: [
45-
{
46-
parts: [
47-
{
48-
text,
49-
},
50-
],
51-
},
52-
],
43+
contents,
44+
safetySettings: this.formatSafetySettings(safetySettings),
5345
...(
5446
responseFormat || maxOutputTokens || temperature || topP || topK || stopSequences?.length
5547
? {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import app from "../../google_gemini.app.mjs";
2+
import constants from "../../common/constants.mjs";
3+
4+
export default {
5+
key: "google_gemini-generate-embeddings",
6+
name: "Generate Embeddings",
7+
description: "Generate embeddings from text input using Google Gemini. [See the documentation](https://ai.google.dev/gemini-api/docs/embeddings)",
8+
version: "0.0.1",
9+
type: "action",
10+
props: {
11+
app,
12+
model: {
13+
reloadProps: false,
14+
propDefinition: [
15+
app,
16+
"model",
17+
() => ({
18+
filter: ({
19+
description,
20+
supportedGenerationMethods,
21+
}) => ![
22+
"discontinued",
23+
"deprecated",
24+
].some((keyword) => description?.includes(keyword))
25+
&& supportedGenerationMethods?.includes(constants.MODEL_METHODS.EMBED_CONTENT),
26+
}),
27+
],
28+
},
29+
text: {
30+
propDefinition: [
31+
app,
32+
"text",
33+
],
34+
description: "The text to generate embeddings for",
35+
},
36+
taskType: {
37+
type: "string",
38+
label: "Task Type",
39+
description: "The type of task for which the embeddings will be used",
40+
options: Object.values(constants.TASK_TYPE),
41+
optional: true,
42+
},
43+
},
44+
async run({ $ }) {
45+
const response = await this.app.embedContent({
46+
model: this.model,
47+
data: {
48+
taskType: this.taskType,
49+
content: {
50+
parts: [
51+
{
52+
text: this.text,
53+
},
54+
],
55+
},
56+
},
57+
});
58+
59+
$.export("$summary", "Successfully generated embeddings");
60+
return response;
61+
},
62+
};

components/google_gemini/common/constants.mjs

+30
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,40 @@ const VERSION_PATH = "/v1beta";
33

44
const MODEL_METHODS = {
55
GENERATE_CONTENT: "generateContent",
6+
EMBED_CONTENT: "embedContent",
7+
};
8+
9+
const HARM_CATEGORIES = {
10+
HARASSMENT: "HARM_CATEGORY_HARASSMENT",
11+
HATE_SPEECH: "HARM_CATEGORY_HATE_SPEECH",
12+
SEXUALLY_EXPLICIT: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
13+
DANGEROUS: "HARM_CATEGORY_DANGEROUS_CONTENT",
14+
CIVIC: "HARM_CATEGORY_CIVIC_INTEGRITY",
15+
};
16+
17+
const BLOCK_THRESHOLDS = {
18+
BLOCK_NONE: "BLOCK_NONE",
19+
BLOCK_ONLY_HIGH: "BLOCK_ONLY_HIGH",
20+
BLOCK_MEDIUM_AND_ABOVE: "BLOCK_MEDIUM_AND_ABOVE",
21+
BLOCK_LOW_AND_ABOVE: "BLOCK_LOW_AND_ABOVE",
22+
};
23+
24+
const TASK_TYPE = {
25+
SEMANTIC_SIMILARITY: "SEMANTIC_SIMILARITY",
26+
CLASSIFICATION: "CLASSIFICATION",
27+
CLUSTERING: "CLUSTERING",
28+
RETRIEVAL_DOCUMENT: "RETRIEVAL_DOCUMENT",
29+
RETRIEVAL_QUERY: "RETRIEVAL_QUERY",
30+
QUESTION_ANSWERING: "QUESTION_ANSWERING",
31+
FACT_VERIFICATION: "FACT_VERIFICATION",
32+
CODE_RETRIEVAL_QUERY: "CODE_RETRIEVAL_QUERY",
633
};
734

835
export default {
936
BASE_URL,
1037
VERSION_PATH,
1138
MODEL_METHODS,
39+
TASK_TYPE,
40+
HARM_CATEGORIES,
41+
BLOCK_THRESHOLDS,
1242
};

0 commit comments

Comments
 (0)