Skip to content

Commit 3dd50d7

Browse files
committed
[ACTION] Update Google Gemini actions
1 parent 65e79d1 commit 3dd50d7

File tree

8 files changed

+270
-87
lines changed

8 files changed

+270
-87
lines changed

Diff for: components/google_gemini/actions/common/generate-content.mjs

+101
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,106 @@ 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 (typeof(history) === "string") {
52+
try {
53+
history = JSON.parse(history);
54+
} catch (e) {
55+
throw new ConfigurationError(`Invalid JSON in history: ${history}`);
56+
}
57+
}
58+
59+
if (Array.isArray(history) && !history?.length) {
60+
return [];
61+
}
62+
63+
return history.map((itemStr) => {
64+
let item = itemStr;
65+
if (typeof item === "string") {
66+
try {
67+
item = JSON.parse(itemStr);
68+
} catch (e) {
69+
throw new ConfigurationError(`Invalid JSON in history item: ${itemStr}`);
70+
}
71+
}
72+
73+
if (!item.text || !item.role || ![
74+
"user",
75+
"model",
76+
].includes(item.role)) {
77+
throw new ConfigurationError("Each history item must include `text` and `role` (either `user` or `model`)");
78+
}
79+
80+
return {
81+
parts: [
82+
{
83+
text: item.text,
84+
},
85+
],
86+
role: item.role,
87+
};
88+
});
89+
},
90+
formatSafetySettings(safetySettings) {
91+
if (!safetySettings?.length) {
92+
return undefined;
93+
}
94+
95+
return safetySettings.map((settingStr) => {
96+
let setting = settingStr;
97+
if (typeof setting === "string") {
98+
try {
99+
setting = JSON.parse(settingStr);
100+
} catch (e) {
101+
throw new ConfigurationError(`Invalid JSON in safety setting: ${settingStr}`);
102+
}
103+
}
104+
105+
if (!setting.category || !setting.threshold) {
106+
throw new ConfigurationError("Each safety setting must include 'category' and 'threshold'");
107+
}
108+
109+
const category = constants.HARM_CATEGORIES[setting.category];
110+
if (!category) {
111+
throw new ConfigurationError(`Invalid category '${setting.category}'. Must be one of: ${Object.keys(constants.HARM_CATEGORIES).join(", ")}`);
112+
}
113+
114+
if (!constants.BLOCK_THRESHOLDS[setting.threshold]) {
115+
throw new ConfigurationError(`Invalid threshold '${setting.threshold}'. Must be one of: ${Object.keys(constants.BLOCK_THRESHOLDS).join(", ")}`);
116+
}
117+
118+
return {
119+
category,
120+
threshold: setting.threshold,
121+
};
122+
});
123+
},
23124
},
24125
async additionalProps() {
25126
const {

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

+33-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,33 @@ 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+
role: "user",
75+
},
76+
];
77+
7778
const response = await app.generateContent({
7879
$,
7980
model,
8081
data: {
81-
contents: [
82-
{
83-
parts: [
84-
{
85-
text,
86-
},
87-
...imagePaths.map((path) => this.fileToGenerativePart(path, mimeType)),
88-
],
89-
},
90-
],
82+
contents,
83+
safetySettings: this.formatSafetySettings(safetySettings),
9184
...(
9285
responseFormat || maxOutputTokens || temperature || topP || topK || stopSequences?.length
9386
? {

Diff for: 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+
};

0 commit comments

Comments
 (0)