Skip to content

Commit a197b7d

Browse files
Merge pull request #149 from SmythOS/dev
Dev merge
2 parents 1659151 + 23851dc commit a197b7d

File tree

16 files changed

+1372
-82
lines changed

16 files changed

+1372
-82
lines changed

examples/01-agent-code-skill/04.1-chat-planner-coder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Agent, Chat, Component, Model, TAgentMode, TLLMEvent } from '@smythos/sdk';
22
import chalk from 'chalk';
33
import * as readline from 'readline';
4-
import { EmitUnit, PluginBase, TokenLoom } from 'tokenloom';
4+
import { EmitUnit, PluginAPI, PluginBase, TokenLoom } from 'tokenloom';
55

66
//Show the tasks list and status to the user at every step before performing the tasks, and also give a tasks status summary after tasks.
77
//When you display the tasks list to a user show it in a concise way with a summary and checkboxes for each task.

examples/05-VectorDB-with-agent/01-upsert-and-search.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Agent, Doc, Model, Scope } from '@smythos/sdk';
1+
import { Agent, Doc, Model, Scope, TLLMEvent } from '@smythos/sdk';
22
import path from 'path';
33
import { fileURLToPath } from 'url';
44

@@ -16,6 +16,7 @@ const pineconeSettings = {
1616
indexName: 'demo-vec',
1717
apiKey: process.env.PINECONE_API_KEY,
1818
embeddings: Model.OpenAI('text-embedding-3-large'),
19+
//you can also use Model.GoogleAI('gemini-embedding-001', { dimensions: 1024 })
1920
};
2021

2122
async function createAgent() {
@@ -83,14 +84,25 @@ async function indexDataForAgent(agent: Agent) {
8384
await pinecone.insertDoc(parsedDoc.title, parsedDoc, { myEntry: 'My Metadata' });
8485
}
8586

87+
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
88+
8689
async function main() {
8790
const agent = await createAgent();
91+
console.log('Indexing data for agent');
8892
await indexDataForAgent(agent);
8993

94+
console.log('Waiting for 5 seconds before prompting the agent ... sometimes the index is not ready immediately');
95+
await delay(5000);
96+
97+
console.log('Prompting the agent');
98+
9099
//this will prompt the agent and use the agent's LLM to determine which skill to use
91-
const promptResult = await agent.prompt('What is bitcoin Proof-of-Work ?');
100+
const promptStream = await agent.prompt('What is bitcoin Proof-of-Work ?').stream();
92101
//the response comes back in natural language
93-
console.log(promptResult);
102+
console.log('\n');
103+
promptStream.on(TLLMEvent.Content, (content) => {
104+
process.stdout.write(content);
105+
});
94106
}
95107

96108
main();

examples/06-Storage-no-agent/01-localstorage.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@ import { Storage } from '@smythos/sdk';
22

33
async function main() {
44
const localStorage = Storage.LocalStorage();
5-
5+
66
await localStorage.write('test.txt', 'Hello, world!');
77

88
const data = await localStorage.read('test.txt');
99

1010
const dataAsString = data.toString();
1111

1212
console.log(dataAsString);
13-
14-
1513
}
1614

17-
main();
15+
main();
Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,71 @@
11
// Global type declarations for Node.js environment
22
declare global {
3-
namespace NodeJS {
4-
interface ProcessEnv {
5-
PORT?: string;
6-
ZOOM_SECRET_TOKEN?: string;
7-
ZOOM_CLIENT_ID?: string;
8-
ZOOM_CLIENT_SECRET?: string;
9-
WEBHOOK_PATH?: string;
10-
OPENAI_API_KEY?: string;
11-
ANTHROPIC_API_KEY?: string;
12-
PINECONE_API_KEY?: string;
13-
PINECONE_INDEX_NAME?: string;
14-
AWS_ACCESS_KEY_ID?: string;
15-
AWS_SECRET_ACCESS_KEY?: string;
16-
AWS_REGION?: string;
17-
AWS_S3_BUCKET?: string;
18-
LOG_LEVEL?: string;
3+
namespace NodeJS {
4+
interface ProcessEnv {
5+
PORT?: string;
6+
ZOOM_SECRET_TOKEN?: string;
7+
ZOOM_CLIENT_ID?: string;
8+
ZOOM_CLIENT_SECRET?: string;
9+
WEBHOOK_PATH?: string;
10+
OPENAI_API_KEY?: string;
11+
ANTHROPIC_API_KEY?: string;
12+
PINECONE_API_KEY?: string;
13+
PINECONE_INDEX_NAME?: string;
14+
AWS_ACCESS_KEY_ID?: string;
15+
AWS_SECRET_ACCESS_KEY?: string;
16+
AWS_REGION?: string;
17+
AWS_S3_BUCKET?: string;
18+
LOG_LEVEL?: string;
19+
}
1920
}
20-
}
2121

22-
var process: NodeJS.Process;
23-
var console: Console;
24-
var Buffer: BufferConstructor;
25-
}
26-
27-
// Module declarations for packages that might not have types
28-
declare module '@smythos/sdk' {
29-
export class Agent {
30-
constructor(config: any);
31-
addSkill(skill: any): void;
32-
prompt(message: string): Promise<string>;
33-
llm: any;
34-
storage: any;
35-
vectordb: any;
36-
}
37-
38-
export class Model {
39-
static OpenAI(model: string): any;
40-
static Anthropic(model: string): any;
41-
}
22+
var process: NodeJS.Process;
23+
var console: Console;
24+
var Buffer: BufferConstructor;
4225
}
4326

4427
declare module 'crypto' {
45-
export function createHmac(algorithm: string, key: string): any;
28+
export function createHmac(algorithm: string, key: string): any;
4629
}
4730

4831
declare module 'ws' {
49-
export default class WebSocket {
50-
constructor(url: string, options?: any);
51-
on(event: string, callback: Function): void;
52-
send(data: string): void;
53-
close(): void;
54-
}
32+
export default class WebSocket {
33+
constructor(url: string, options?: any);
34+
on(event: string, callback: Function): void;
35+
send(data: string): void;
36+
close(): void;
37+
}
5538
}
5639

5740
declare module 'express' {
58-
export interface Request {
59-
body: any;
60-
}
61-
62-
export interface Response {
63-
json(data: any): void;
64-
sendStatus(code: number): void;
65-
}
66-
67-
interface Express {
68-
use(middleware: any): void;
69-
post(path: string, handler: any): void;
70-
get(path: string, handler: any): void;
71-
listen(port: string | number, callback?: () => void): void;
72-
}
73-
74-
interface ExpressStatic {
75-
(): Express;
76-
json(): any;
77-
}
78-
79-
const express: ExpressStatic;
80-
export default express;
81-
export { Request, Response };
41+
export interface Request {
42+
body: any;
43+
}
44+
45+
export interface Response {
46+
json(data: any): void;
47+
sendStatus(code: number): void;
48+
}
49+
50+
interface Express {
51+
use(middleware: any): void;
52+
post(path: string, handler: any): void;
53+
get(path: string, handler: any): void;
54+
listen(port: string | number, callback?: () => void): void;
55+
}
56+
57+
interface ExpressStatic {
58+
(): Express;
59+
json(): any;
60+
}
61+
62+
const express: ExpressStatic;
63+
export default express;
64+
export { Request, Response };
8265
}
8366

8467
declare module 'dotenv' {
85-
export function config(): void;
68+
export function config(): void;
8669
}
8770

8871
export {};

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@smythos/sre",
3-
"version": "1.5.67",
3+
"version": "1.5.68",
44
"description": "Smyth Runtime Environment",
55
"author": "Alaa-eddine KADDOURI",
66
"license": "MIT",

packages/core/src/helpers/Conversation.helper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ export class Conversation extends EventEmitter {
282282
const reqMethods = this._reqMethods;
283283
const toolsConfig = this._toolsConfig;
284284
//deduplicate tools
285-
toolsConfig.tools = toolsConfig.tools.filter((tool, index, self) => self.findIndex((t) => t.name === tool.name) === index);
285+
toolsConfig.tools = toolsConfig.tools.filter((tool, index, self) => self.findIndex((t) => t.function.name === tool.function.name) === index);
286286
const endpoints = this._endpoints;
287287
const baseUrl = this._baseUrl;
288288
const message_id = 'msg_' + randomUUID();

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export * from './subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class'
166166
export * from './subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class';
167167
export * from './subsystems/IO/VectorDB.service/connectors/RAMVecrtorDB.class';
168168
export * from './subsystems/IO/VectorDB.service/embed/BaseEmbedding';
169+
export * from './subsystems/IO/VectorDB.service/embed/GoogleEmbedding';
169170
export * from './subsystems/IO/VectorDB.service/embed/index';
170171
export * from './subsystems/IO/VectorDB.service/embed/OpenAIEmbedding';
171172
export * from './subsystems/LLMManager/LLM.service/connectors/Anthropic.class';
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { GoogleGenAI } from '@google/genai';
2+
import { BaseEmbedding, TEmbeddings } from './BaseEmbedding';
3+
import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class';
4+
import { getLLMCredentials } from '@sre/LLMManager/LLM.service/LLMCredentials.helper';
5+
import { TLLMCredentials, TLLMModel, BasicCredentials } from '@sre/types/LLM.types';
6+
7+
const DEFAULT_MODEL = 'gemini-embedding-001';
8+
9+
export class GoogleEmbeds extends BaseEmbedding {
10+
protected client: GoogleGenAI;
11+
12+
// Keep in sync with Gemini API supported embedding models
13+
public static models = ['gemini-embedding-001', 'text-embedding-005', 'text-multilingual-embedding-002'];
14+
public canSpecifyDimensions = true;
15+
16+
constructor(private settings?: Partial<TEmbeddings>) {
17+
super({ model: settings?.model ?? DEFAULT_MODEL, ...settings });
18+
}
19+
20+
async embedTexts(texts: string[], candidate: AccessCandidate): Promise<number[][]> {
21+
const batches = this.chunkArr(this.processTexts(texts), this.chunkSize);
22+
23+
const batchRequests = batches.map((batch) => {
24+
return this.embed(batch, candidate);
25+
});
26+
const batchResponses = await Promise.all(batchRequests);
27+
28+
const embeddings: number[][] = [];
29+
for (let i = 0; i < batchResponses.length; i += 1) {
30+
const batch = batches[i];
31+
const batchResponse = batchResponses[i];
32+
for (let j = 0; j < batch.length; j += 1) {
33+
embeddings.push(batchResponse[j]);
34+
}
35+
}
36+
return embeddings;
37+
}
38+
39+
async embedText(text: string, candidate: AccessCandidate): Promise<number[]> {
40+
const processedText = this.processTexts([text])[0];
41+
const embeddings = await this.embed([processedText], candidate);
42+
return embeddings[0];
43+
}
44+
45+
protected async embed(texts: string[], candidate: AccessCandidate): Promise<number[][]> {
46+
let apiKey: string | undefined;
47+
48+
// Try to get from credentials first
49+
try {
50+
const modelInfo: TLLMModel = {
51+
provider: 'GoogleAI',
52+
modelId: this.model,
53+
credentials: this.settings?.credentials as unknown as TLLMCredentials,
54+
};
55+
const credentials = await getLLMCredentials(candidate, modelInfo);
56+
apiKey = (credentials as BasicCredentials)?.apiKey;
57+
} catch (e) {
58+
// If credential system fails, fall back to environment variable
59+
}
60+
61+
// Fall back to environment variable if not found in credentials
62+
if (!apiKey) {
63+
apiKey = process.env.GOOGLE_AI_API_KEY;
64+
}
65+
66+
if (!apiKey) {
67+
throw new Error('Please provide an API key for Google AI embeddings via credentials or GOOGLE_AI_API_KEY environment variable');
68+
}
69+
70+
if (!this.client) {
71+
this.client = new GoogleGenAI({ apiKey });
72+
}
73+
74+
try {
75+
const outputDimensionality = this.dimensions && Number.isFinite(this.dimensions) ? this.dimensions : undefined;
76+
77+
// Batch request using the new SDK
78+
const res = await this.client.models.embedContent({
79+
model: this.model,
80+
contents: texts,
81+
...(outputDimensionality ? { outputDimensionality } : {}),
82+
});
83+
84+
// The SDK can return either { embedding } for single or { embeddings } for batch
85+
const vectors: number[][] = Array.isArray((res as any).embeddings)
86+
? (res as any).embeddings.map((e: any) => e.values as number[])
87+
: [((res as any).embedding?.values as number[]) || []];
88+
89+
// Enforce dimensions and normalization when requested or when non-3072
90+
const targetDim = outputDimensionality;
91+
const processed = vectors.map((v) => this.postProcessEmbedding(v, targetDim));
92+
93+
return processed;
94+
} catch (e) {
95+
throw new Error(`Google Embeddings API error: ${e.message || e}`);
96+
}
97+
}
98+
99+
private postProcessEmbedding(values: number[], targetDim?: number): number[] {
100+
let v = Array.isArray(values) ? values.slice() : [];
101+
if (targetDim && targetDim > 0) {
102+
if (v.length > targetDim) {
103+
// SDK ignored smaller dimension: truncate
104+
v = v.slice(0, targetDim);
105+
} else if (v.length < targetDim) {
106+
// SDK returned shorter vector: pad with zeros
107+
v = v.concat(Array(targetDim - v.length).fill(0));
108+
}
109+
}
110+
// Normalize for non-default 3072 dims (recommended by Google docs)
111+
const needNormalize = (targetDim && targetDim !== 3072) || (!targetDim && v.length !== 3072);
112+
if (needNormalize && v.length > 0) {
113+
const norm = Math.sqrt(v.reduce((acc, x) => acc + x * x, 0));
114+
if (norm > 0) v = v.map((x) => x / norm);
115+
}
116+
return v;
117+
}
118+
}

packages/core/src/subsystems/IO/VectorDB.service/embed/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { OpenAIEmbeds } from './OpenAIEmbedding';
2+
import { GoogleEmbeds } from './GoogleEmbedding';
23
import { TEmbeddings } from './BaseEmbedding';
34

45
// a factory to get the correct embedding provider based on the provider name
@@ -7,6 +8,10 @@ const supportedProviders = {
78
embedder: OpenAIEmbeds,
89
models: OpenAIEmbeds.models,
910
},
11+
GoogleAI: {
12+
embedder: GoogleEmbeds,
13+
models: GoogleEmbeds.models,
14+
},
1015
} as const;
1116

1217
export type SupportedProviders = keyof typeof supportedProviders;

0 commit comments

Comments
 (0)