agentnet 0.0.1
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.txt +202 -0
- package/README.md +488 -0
- package/package.json +23 -0
- package/src/agent/agent-loader.js +274 -0
- package/src/agent/agent.js +416 -0
- package/src/agent/client.js +14 -0
- package/src/agent/executor.js +320 -0
- package/src/agent/runtime.js +142 -0
- package/src/agent/runtimes/nats.js +379 -0
- package/src/errors/index.js +195 -0
- package/src/examples/agents-smartness.yaml +308 -0
- package/src/examples/agents.yaml +394 -0
- package/src/examples/def.js +74 -0
- package/src/examples/def2.js +65 -0
- package/src/examples/def3.js +67 -0
- package/src/examples/simple.js +103 -0
- package/src/index.js +115 -0
- package/src/llm/gemini.js +155 -0
- package/src/llm/gpt.js +155 -0
- package/src/store/store.js +167 -0
- package/src/utils/logger.js +209 -0
- package/src/utils/store.js +212 -0
- package/src/utils/validation.js +287 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Agent, NatsIO, Gemini } from "../index.js"
|
|
2
|
+
|
|
3
|
+
const basicAgent = await Agent()
|
|
4
|
+
.addIO(NatsIO({
|
|
5
|
+
servers: ['nats://localhost:4222']
|
|
6
|
+
}), {
|
|
7
|
+
tasks: ['basicagent'],
|
|
8
|
+
handoffs: ['mathagents.*']
|
|
9
|
+
})
|
|
10
|
+
.withLLM(Gemini, {
|
|
11
|
+
model: 'gemini-2.0-flash',
|
|
12
|
+
systemInstruction: `
|
|
13
|
+
You are an traveler agency agent.
|
|
14
|
+
Your role is to answer about the traveler (user) reservation.
|
|
15
|
+
`,
|
|
16
|
+
config: {
|
|
17
|
+
temperature: 0,
|
|
18
|
+
toolConfig: {
|
|
19
|
+
functionCallingConfig: {
|
|
20
|
+
mode: 'auto'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.on('prompt', async (state, input) => {
|
|
26
|
+
return input
|
|
27
|
+
})
|
|
28
|
+
.compile()
|
|
29
|
+
|
|
30
|
+
const mathAgent = await Agent()
|
|
31
|
+
.addIO(NatsIO({
|
|
32
|
+
servers: ['nats://localhost:4222']
|
|
33
|
+
}), {
|
|
34
|
+
tasks: ['mathagents.v1']
|
|
35
|
+
})
|
|
36
|
+
.withLLM(Gemini, {
|
|
37
|
+
model: 'gemini-2.0-flash',
|
|
38
|
+
systemInstruction: `
|
|
39
|
+
You are an traveler agency agent.
|
|
40
|
+
Your role is to answer about the traveler (user) reservation.
|
|
41
|
+
`,
|
|
42
|
+
config: {
|
|
43
|
+
temperature: 0,
|
|
44
|
+
toolConfig: {
|
|
45
|
+
functionCallingConfig: {
|
|
46
|
+
mode: 'auto'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
.addDiscoverySchema({
|
|
52
|
+
name: 'math_agent',
|
|
53
|
+
type: 'function',
|
|
54
|
+
description: 'Solve the math problem',
|
|
55
|
+
parameters: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
question: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'Math problem to solve',
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
required: ['question']
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
.on('prompt', async (state, input) => {
|
|
67
|
+
return input
|
|
68
|
+
})
|
|
69
|
+
.compile()
|
|
70
|
+
|
|
71
|
+
const result = await basicAgent.query("How 2 + 2 is?")
|
|
72
|
+
console.log(result)
|
|
73
|
+
|
|
74
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AgentLoader, AgentClient, NatsIO } from "../index.js"
|
|
2
|
+
|
|
3
|
+
const agents = await AgentLoader('./src/examples/agents.yaml')
|
|
4
|
+
|
|
5
|
+
const agentTravel = await agents.advancedTravelAgent
|
|
6
|
+
agentTravel.tools.flightSearchTool.bind(async (state, input) => {
|
|
7
|
+
return { answer: "The fly is from New York to Los Angeles at 10:00 AM" }
|
|
8
|
+
})
|
|
9
|
+
await agentTravel.compile()
|
|
10
|
+
|
|
11
|
+
const agentWeather = await agents.weatherAgent
|
|
12
|
+
agentWeather.tools.weatherSearchTool.bind(async (state, input) => {
|
|
13
|
+
return { answer: "The weather in New York is sunny" }
|
|
14
|
+
})
|
|
15
|
+
await agentWeather.compile()
|
|
16
|
+
|
|
17
|
+
const agentNews = await agents.newsAgent
|
|
18
|
+
agentNews.tools.newsSearchTool.bind(async (state, input) => {
|
|
19
|
+
return { answer: "Latest news: AI is taking over!" }
|
|
20
|
+
})
|
|
21
|
+
await agentNews.compile()
|
|
22
|
+
|
|
23
|
+
const agentCalculator = await agents.calculatorAgent
|
|
24
|
+
agentCalculator.tools.calculationTool.bind(async (state, input) => {
|
|
25
|
+
return { answer: `The result of ${input.expression} is 42` }
|
|
26
|
+
})
|
|
27
|
+
await agentCalculator.compile()
|
|
28
|
+
|
|
29
|
+
const agentTranslation = await agents.translationAgent
|
|
30
|
+
agentTranslation.tools.translationTool.bind(async (state, input) => {
|
|
31
|
+
return { answer: `'${input.text}' translated to ${input.targetLanguage} is 'Hola Mundo'` }
|
|
32
|
+
})
|
|
33
|
+
await agentTranslation.compile()
|
|
34
|
+
|
|
35
|
+
const agentCalendar = await agents.calendarAgent
|
|
36
|
+
agentCalendar.tools.createEventTool.bind(async (state, input) => {
|
|
37
|
+
return { answer: `Event '${input.title}' created for ${input.startTime}` }
|
|
38
|
+
})
|
|
39
|
+
agentCalendar.tools.listEventsTool.bind(async (state, input) => {
|
|
40
|
+
return { answer: `Events for ${input.startDate}: Meeting at 10 AM` }
|
|
41
|
+
})
|
|
42
|
+
await agentCalendar.compile()
|
|
43
|
+
|
|
44
|
+
const agentStockTicker = await agents.stockTickerAgent
|
|
45
|
+
agentStockTicker.tools.stockPriceTool.bind(async (state, input) => {
|
|
46
|
+
return { answer: `The price of ${input.symbol} is $100` }
|
|
47
|
+
})
|
|
48
|
+
await agentStockTicker.compile()
|
|
49
|
+
|
|
50
|
+
const io = NatsIO({
|
|
51
|
+
servers: ['nats://localhost:4222']
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const agentClient = AgentClient()
|
|
55
|
+
//const res = await agentClient.queryAgent(agentTravelInstance, "Find me a flight from New York to Los Angeles")
|
|
56
|
+
//console.log(res)
|
|
57
|
+
|
|
58
|
+
// Wait for 2 seconds before proceeding
|
|
59
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
60
|
+
|
|
61
|
+
const res = await agentClient.queryIo(io, 'advancedTravelAgent', "Find me a flight from New York to Los Angeles. How is the weather in New York? Give me some news. Also create the birthday event for my friend tomorrow 09-05-2025. RETURN TO ME THE RESULTS")
|
|
62
|
+
console.log(res)
|
|
63
|
+
|
|
64
|
+
//const res = await agent.query("Find me a flight from New York to Los Angeles")
|
|
65
|
+
//console.log(res)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AgentLoaderFile, AgentClient, NatsIO, Bindings, Message, PostgresStore } from "../index.js"
|
|
2
|
+
|
|
3
|
+
// NatsIO instance
|
|
4
|
+
const io = NatsIO({
|
|
5
|
+
servers: ['nats://localhost:4222']
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
// Load the agents from the YAML file
|
|
9
|
+
const agents = await AgentLoaderFile('./src/examples/agents-smartness.yaml', {
|
|
10
|
+
bindings: { [Bindings.NatsIO]: io, [Bindings.Postgres]: PostgresStore() }
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
// Entry point
|
|
14
|
+
const agentSmartness = await agents.smartnessAgent
|
|
15
|
+
await agentSmartness.compile()
|
|
16
|
+
|
|
17
|
+
// Accomodation agent
|
|
18
|
+
const agentAccomodation = await agents.accomodationAgent
|
|
19
|
+
agentAccomodation.tools.getRoomsListTool.bind(async (state, input) => {
|
|
20
|
+
return { answer: "We have Double room with a view of the sea and a single room with a view of the pool, and a suite with a view of the city." }
|
|
21
|
+
})
|
|
22
|
+
agentAccomodation.tools.getRoomDetailTool.bind(async (state, input) => {
|
|
23
|
+
return { answer: "The Double room with a view of the sea has a king size bed, a private balcony, and a view of the sea." }
|
|
24
|
+
})
|
|
25
|
+
agentAccomodation.prompt(async (state, input) => {
|
|
26
|
+
state._accomodationAgent = true
|
|
27
|
+
return input
|
|
28
|
+
})
|
|
29
|
+
await agentAccomodation.compile()
|
|
30
|
+
|
|
31
|
+
// Booking agent
|
|
32
|
+
const agentBooking = await agents.bookingAgent
|
|
33
|
+
agentBooking.tools.bookRoomTool.bind(async (state, input) => {
|
|
34
|
+
return { answer: "The room " + input.roomName + " has been booked for the dates " + input.checkinDate + " to " + input.checkoutDate + "." }
|
|
35
|
+
})
|
|
36
|
+
await agentBooking.compile()
|
|
37
|
+
|
|
38
|
+
// Hotel review agent
|
|
39
|
+
const agentHotelReview = await agents.hotelReviewAgent
|
|
40
|
+
agentHotelReview.tools.getHotelReviewsTool.bind(async (state, input) => {
|
|
41
|
+
return { answer: "The hotel " + input.hotelName + " has a 4.5 star rating and a 9.2 out of 10 guest satisfaction score." }
|
|
42
|
+
})
|
|
43
|
+
await agentHotelReview.compile()
|
|
44
|
+
|
|
45
|
+
// Pricing agent
|
|
46
|
+
const agentPricing = await agents.pricingAgent
|
|
47
|
+
agentPricing.tools.getPricingTool.bind(async (state, input) => {
|
|
48
|
+
return { answer: "The room " + input.roomName + " has a price of 200€ per night." }
|
|
49
|
+
})
|
|
50
|
+
await agentPricing.compile()
|
|
51
|
+
|
|
52
|
+
// Wait for 2 seconds before proceeding in order to allow self discovery
|
|
53
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
54
|
+
|
|
55
|
+
// Agent client
|
|
56
|
+
const agentClient = AgentClient()
|
|
57
|
+
const message = new Message({
|
|
58
|
+
content: "What rooms do you have from 2025-05-25 to 2025-05-30 for 3 guests For the hotel Flora? Give me the review of the hotel Flora",
|
|
59
|
+
session: {
|
|
60
|
+
id: "67a71e42-a7d8-1db2-ad17-64e1c8546b21"
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
const res = await agentClient.queryIo(io, 'smartnessAgent', message)
|
|
64
|
+
console.log("=======\n", res.getContent())
|
|
65
|
+
console.log("=======\n", res.getSession())
|
|
66
|
+
//const res2 = await agentClient.queryIo(io, 'smartnessAgent', "Quanto costa la camera doppia del Flora per il 10-05-2025 per due persone? Prenotala se costa meno di 100€ la camera double con vista mare per il 10-05-2025 al hotel Flora")
|
|
67
|
+
//console.log("=======\n", res2)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { AgentLoaderJSON, Message, Bindings, PostgresStore } from "../index.js"
|
|
2
|
+
|
|
3
|
+
const agentDefinition = {
|
|
4
|
+
"apiVersion": "smartagent.io/v1alpha1",
|
|
5
|
+
"kind": "AgentDefinition",
|
|
6
|
+
"metadata": {
|
|
7
|
+
"name": "accomodationAgent",
|
|
8
|
+
"namespace": "smartchat"
|
|
9
|
+
},
|
|
10
|
+
"spec": {
|
|
11
|
+
"store": {
|
|
12
|
+
"type": "Postgres",
|
|
13
|
+
},
|
|
14
|
+
"llm": {
|
|
15
|
+
"provider": "Gemini",
|
|
16
|
+
"model": "gemini-2.0-flash",
|
|
17
|
+
"systemInstruction": "You are a highly advanced accomodation manager agent. \nPrioritize clarity and helpfulness.\nUse tools effectively to gather information.\n",
|
|
18
|
+
"config": {
|
|
19
|
+
"temperature": 0.5,
|
|
20
|
+
"toolConfig": {
|
|
21
|
+
"functionCallingConfig": {
|
|
22
|
+
"mode": "auto"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"tools": [
|
|
28
|
+
{
|
|
29
|
+
"name": "getRoomsListTool",
|
|
30
|
+
"description": "Retrieves a list of available rooms based on criteria.",
|
|
31
|
+
"parameters": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {
|
|
34
|
+
"checkinDate": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "The check-in date."
|
|
37
|
+
},
|
|
38
|
+
"checkoutDate": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "The check-out date."
|
|
41
|
+
},
|
|
42
|
+
"guests": {
|
|
43
|
+
"type": "integer",
|
|
44
|
+
"description": "Number of guests."
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"required": [
|
|
48
|
+
"checkinDate",
|
|
49
|
+
"checkoutDate"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "getRoomDetailTool",
|
|
55
|
+
"description": "Retrieves detailed information about a specific room.",
|
|
56
|
+
"parameters": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": {
|
|
59
|
+
"roomName": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"description": "The name of the room."
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"required": [
|
|
65
|
+
"roomName"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Load the agent definition
|
|
74
|
+
const agents = await AgentLoaderJSON(agentDefinition, {
|
|
75
|
+
bindings: {
|
|
76
|
+
[Bindings.Postgres]: PostgresStore()
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Add the binding tools to the agent
|
|
81
|
+
agents.accomodationAgent.tools.getRoomsListTool.bind(async (state, input) => {
|
|
82
|
+
return { answer: "We have Double room with a view of the sea and a single room with a view of the pool, and a suite with a view of the city." }
|
|
83
|
+
})
|
|
84
|
+
agents.accomodationAgent.tools.getRoomDetailTool.bind(async (state, input) => {
|
|
85
|
+
return { answer: "The Double room with a view of the sea has a king size bed, a private balcony, and a view of the sea." }
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Compile the agent
|
|
89
|
+
const agentInstance = await agents.accomodationAgent.compile()
|
|
90
|
+
const input = new Message("What rooms do you have from 2025-05-10 to 2025-05-15 for 2 guests?")
|
|
91
|
+
|
|
92
|
+
const input2 = new Message({
|
|
93
|
+
content: "What rooms do you have from 2025-05-10 to 2025-05-15 for 2 guests?",
|
|
94
|
+
session: {
|
|
95
|
+
id: "67a71e42-a7d8-1db2-ad17-64e1c8546b21",
|
|
96
|
+
propertySetId: "123"
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
const result = await agentInstance.query(input2)
|
|
102
|
+
|
|
103
|
+
console.log(result.getContent())
|
package/src/index.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as _Agent from "./agent/agent.js";
|
|
2
|
+
import * as _AgentLoader from "./agent/agent-loader.js";
|
|
3
|
+
import * as _AgentClient from "./agent/client.js";
|
|
4
|
+
import _Gemini from "./llm/gemini.js";
|
|
5
|
+
import _GPT from "./llm/gpt.js";
|
|
6
|
+
import {
|
|
7
|
+
redisStore,
|
|
8
|
+
postgresStore,
|
|
9
|
+
memoryStore,
|
|
10
|
+
session
|
|
11
|
+
} from "./store/store.js";
|
|
12
|
+
|
|
13
|
+
export const AgentLoaderFile = _AgentLoader.AgentLoaderFile
|
|
14
|
+
export const AgentLoaderJSON = _AgentLoader.AgentLoaderJSON
|
|
15
|
+
|
|
16
|
+
export const Agent = _Agent.Agent
|
|
17
|
+
export const AgentClient = _AgentClient.AgentClient
|
|
18
|
+
export const LLMRuntime = {
|
|
19
|
+
GPT: _GPT,
|
|
20
|
+
Gemini: _Gemini
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const Gemini = _Gemini
|
|
24
|
+
export const GPT = _GPT
|
|
25
|
+
|
|
26
|
+
export const PostgresStore = postgresStore
|
|
27
|
+
export const RedisStore = redisStore
|
|
28
|
+
export const MemoryStore = memoryStore
|
|
29
|
+
export const SessionStore = session
|
|
30
|
+
|
|
31
|
+
import { connect } from "@nats-io/transport-node"
|
|
32
|
+
export const NatsIO = (config) => {
|
|
33
|
+
let connected = false
|
|
34
|
+
let nc = null
|
|
35
|
+
return {
|
|
36
|
+
type: 'NatsIO',
|
|
37
|
+
connect: async () => {
|
|
38
|
+
if (connected) {
|
|
39
|
+
return nc
|
|
40
|
+
}
|
|
41
|
+
nc = await connect(config)
|
|
42
|
+
connected = true
|
|
43
|
+
return nc
|
|
44
|
+
},
|
|
45
|
+
query: async (target, message) => {
|
|
46
|
+
const nc = await this.connect()
|
|
47
|
+
return await nc.request(target, message.serialize(), {replyTo: target + '.reply'})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export const Bindings = {
|
|
52
|
+
NatsIO: 'NatsIO',
|
|
53
|
+
Postgres: 'Postgres',
|
|
54
|
+
Redis: 'Redis',
|
|
55
|
+
Memory: 'Memory'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class Message {
|
|
59
|
+
#content
|
|
60
|
+
#session
|
|
61
|
+
constructor(input) {
|
|
62
|
+
if (typeof input === 'string') {
|
|
63
|
+
this.#content = input
|
|
64
|
+
} else {
|
|
65
|
+
this.#content = input.content
|
|
66
|
+
this.#session = input.session || {}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
getContent() {
|
|
70
|
+
return this.#content
|
|
71
|
+
}
|
|
72
|
+
getSessionId() {
|
|
73
|
+
return this.#session.id || null
|
|
74
|
+
}
|
|
75
|
+
getSession() {
|
|
76
|
+
return this.#session
|
|
77
|
+
}
|
|
78
|
+
serialize() {
|
|
79
|
+
return JSON.stringify({
|
|
80
|
+
content: this.#content,
|
|
81
|
+
session: this.#session
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
deserialize(data) {
|
|
85
|
+
const parsed = JSON.parse(data)
|
|
86
|
+
this.#content = parsed.content
|
|
87
|
+
this.#session = parsed.session || {}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class Response {
|
|
92
|
+
#content
|
|
93
|
+
#session
|
|
94
|
+
constructor(output) {
|
|
95
|
+
this.#content = output.content
|
|
96
|
+
this.#session = output.session
|
|
97
|
+
}
|
|
98
|
+
getContent() {
|
|
99
|
+
return this.#content
|
|
100
|
+
}
|
|
101
|
+
getSession() {
|
|
102
|
+
return this.#session
|
|
103
|
+
}
|
|
104
|
+
serialize() {
|
|
105
|
+
return JSON.stringify({
|
|
106
|
+
content: this.#content,
|
|
107
|
+
session: this.#session
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
deserialize(data) {
|
|
111
|
+
const parsed = JSON.parse(data)
|
|
112
|
+
this.#content = parsed.content
|
|
113
|
+
this.#session = parsed.session
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { GoogleGenAI } from '@google/genai'
|
|
2
|
+
import { logger } from '../utils/logger.js'
|
|
3
|
+
import { LLMError } from '../errors/index.js'
|
|
4
|
+
|
|
5
|
+
const type = 'gemini'
|
|
6
|
+
|
|
7
|
+
const getClient = async function () {
|
|
8
|
+
try {
|
|
9
|
+
if (!process.env.GEMINI_API_KEY) {
|
|
10
|
+
throw new LLMError(
|
|
11
|
+
'GEMINI_API_KEY environment variable is not set',
|
|
12
|
+
'gemini'
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
logger.debug('Initializing Gemini client');
|
|
17
|
+
return new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
|
18
|
+
} catch (error) {
|
|
19
|
+
logger.error('Failed to initialize Gemini client', { error });
|
|
20
|
+
throw new LLMError(
|
|
21
|
+
`Failed to initialize Gemini client: ${error.message}`,
|
|
22
|
+
'gemini',
|
|
23
|
+
{ originalError: error }
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const callModel = async function (llmClientConfig, context) {
|
|
29
|
+
const client = context.client;
|
|
30
|
+
const toolsAndHandoffsMap = context.toolsAndHandoffsMap;
|
|
31
|
+
const conversation = context.conversation;
|
|
32
|
+
const input = {};
|
|
33
|
+
|
|
34
|
+
Object.assign(input, llmClientConfig);
|
|
35
|
+
input['contents'] = conversation;
|
|
36
|
+
|
|
37
|
+
if (input.config !== undefined && input.tools !== undefined) {
|
|
38
|
+
input.config.tools = toolsAndHandoffsMap.tools;
|
|
39
|
+
} else if (toolsAndHandoffsMap.tools.length > 0) {
|
|
40
|
+
if (input.config == undefined) {
|
|
41
|
+
input.config = {};
|
|
42
|
+
}
|
|
43
|
+
input.config.tools = [{ functionDeclarations: toolsAndHandoffsMap.tools }];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
logger.debug('Calling Gemini model', {
|
|
47
|
+
model: input.model,
|
|
48
|
+
conversationLength: conversation.length,
|
|
49
|
+
toolsCount: toolsAndHandoffsMap.tools.length
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const res = await client.models.generateContent(input);
|
|
54
|
+
logger.debug('Gemini response received', {
|
|
55
|
+
responseType: res.response?.candidates ? 'candidates' : 'unknown',
|
|
56
|
+
hasContent: !!res.response?.candidates?.[0]?.content
|
|
57
|
+
});
|
|
58
|
+
return res;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
logger.error('Gemini API error', {
|
|
61
|
+
error,
|
|
62
|
+
modelName: input.model
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
throw new LLMError(
|
|
66
|
+
`Gemini API error: ${error.message}`,
|
|
67
|
+
'gemini',
|
|
68
|
+
{
|
|
69
|
+
statusCode: error.status || error.statusCode,
|
|
70
|
+
modelName: input.model
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const onResponse = async function (state, conversation, toolsAndHandoffsMap, response) {
|
|
77
|
+
if (response.text !== undefined) {
|
|
78
|
+
logger.debug('Gemini response contains text, returning directly');
|
|
79
|
+
conversation.push({ role: 'model', parts: [{ text: response.text }] });
|
|
80
|
+
return response.text;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
logger.debug('Gemini response contains function calls', {
|
|
84
|
+
functionCallCount: response.functionCalls?.length || 0
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
for (const toolCall of response.functionCalls) {
|
|
88
|
+
const args = toolCall.args;
|
|
89
|
+
const name = toolCall.name;
|
|
90
|
+
|
|
91
|
+
logger.debug('Executing tool from Gemini', {
|
|
92
|
+
toolName: name,
|
|
93
|
+
argsPreview: JSON.stringify(args).substring(0, 100)
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
if (!toolsAndHandoffsMap[name] || !toolsAndHandoffsMap[name].function) {
|
|
98
|
+
throw new Error(`Tool "${name}" not found or has no function implementation`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let result = await toolsAndHandoffsMap[name].function(conversation, state, args);
|
|
102
|
+
if (toolsAndHandoffsMap[name].type === 'handoff') {
|
|
103
|
+
const resultParsed = JSON.parse(result)
|
|
104
|
+
// Update state with the result
|
|
105
|
+
if (resultParsed.session) {
|
|
106
|
+
for (const key of Object.keys(resultParsed.session)) {
|
|
107
|
+
state[key] = resultParsed.session[key]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const function_response_part = {
|
|
113
|
+
name: name,
|
|
114
|
+
response: typeof result === 'string' ? { answer: result } : result
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Append function call and result of the function execution to contents
|
|
118
|
+
conversation.push({ role: 'model', parts: [{ functionCall: toolCall }] });
|
|
119
|
+
conversation.push({ role: 'user', parts: [{ functionResponse: function_response_part }] });
|
|
120
|
+
|
|
121
|
+
logger.debug('Tool execution successful', { toolName: name });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
logger.error(`Error executing tool "${name}"`, { error });
|
|
124
|
+
// Return error as function response
|
|
125
|
+
const errorResponse = {
|
|
126
|
+
name: name,
|
|
127
|
+
response: { error: error.message }
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
conversation.push({ role: 'model', parts: [{ functionCall: toolCall }] });
|
|
131
|
+
conversation.push({ role: 'user', parts: [{ functionResponse: errorResponse }] });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const prompt = async function (conversation, formattedPrompt) {
|
|
139
|
+
logger.debug('Adding user prompt to conversation', {
|
|
140
|
+
promptPreview: formattedPrompt.substring(0, 100)
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
conversation.push({
|
|
144
|
+
role: 'user',
|
|
145
|
+
parts: [{ text: formattedPrompt}]
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default {
|
|
150
|
+
type,
|
|
151
|
+
getClient,
|
|
152
|
+
prompt,
|
|
153
|
+
callModel,
|
|
154
|
+
onResponse
|
|
155
|
+
}
|