diff --git a/Dockerfile b/Dockerfile index 935e869f..81b8eb4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,4 +65,5 @@ ENV NODE_ENV=production ENV PORT=8080 EXPOSE 8080 -CMD ["node", "-r", "ts-node/register", "--env-file=variables/.env", "src/index.ts"] +# Build will have performed type checking. Use esbuild-register for efficiency +CMD ["node", "-r", "esbuild-register", "--env-file=variables/.env", "src/index.ts"] diff --git a/shared/files/fileSystemService.ts b/shared/files/fileSystemService.ts index 5612ffcf..b20bdf14 100644 --- a/shared/files/fileSystemService.ts +++ b/shared/files/fileSystemService.ts @@ -154,6 +154,10 @@ export interface IFileSystemService { */ writeFile(filePath: string, contents: string): Promise; + /** + * Deletes a file. + * @param filePath The file path (either full filesystem path or relative to current working directory) + */ deleteFile(filePath: string): Promise; /** diff --git a/src/cli/llmAliases.ts b/src/cli/llmAliases.ts index 60355406..c4782e58 100644 --- a/src/cli/llmAliases.ts +++ b/src/cli/llmAliases.ts @@ -1,4 +1,5 @@ import { FastMediumLLM } from '#llm/multi-agent/fastMedium'; +import { openAIFlexGPT5Mini } from '#llm/multi-agent/openaiFlex'; import { MAD_Balanced, MAD_Fast, MAD_SOTA } from '#llm/multi-agent/reasoning-debate'; import { Claude4_1_Opus_Vertex } from '#llm/services/anthropic-vertex'; import { cerebrasQwen3_235b_Thinking, cerebrasQwen3_Coder } from '#llm/services/cerebras'; @@ -18,7 +19,7 @@ export const LLM_CLI_ALIAS: Record LLM> = { cc: cerebrasQwen3_Coder, g5: openaiGPT5, g5p: openaiGPT5priority, - g5f: openaiGPT5flex, + g5mf: openAIFlexGPT5Mini, gpt5: openaiGPT5, g5m: openaiGPT5mini, g5n: openaiGPT5nano, diff --git a/src/functions/cloud/google/bigquery.ts b/src/functions/cloud/google/bigquery.ts index 2d8b8e5c..c5931b90 100644 --- a/src/functions/cloud/google/bigquery.ts +++ b/src/functions/cloud/google/bigquery.ts @@ -4,7 +4,6 @@ import { humanInTheLoop } from '#agent/autonomous/humanInTheLoop'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; import { execCmd, execCommand, failOnError } from '#utils/exec'; -const Table = require('table'); // Should use either bq or the node library in all functions @funcClass(__filename) @@ -16,14 +15,10 @@ export class BigQuery { * @param projectId The Google Cloud project id to run the query from. Defaults to the GCLOUD_PROJECT environment variable */ @func() - async query(sqlQuery: string, location: string, projectId: string | undefined): Promise { + async query(sqlQuery: string, location: string, projectId: string | undefined): Promise { projectId ??= process.env.GCLOUD_PROJECT; if (!projectId) throw new Error('GCLOUD_PROJECT environment variable not set'); - const result = await new BigQueryDriver(projectId, location).query(sqlQuery); - if (result.length > 5001) { - return `${result.substring(0, 5000)}\n`; - } - return result; + return await new BigQueryDriver(projectId, location).query(sqlQuery); } /** @@ -39,7 +34,7 @@ export class BigQuery { } } -class BigQueryDriver { +export class BigQueryDriver { private bigqueryClient: BigQueryClient; constructor( @@ -49,10 +44,11 @@ class BigQueryDriver { this.bigqueryClient = new BigQueryClient({ projectId }); } - async query(query: string): Promise { + async query(query: string, queryParameters?: Record): Promise { const [dryRun] = await this.bigqueryClient.createQueryJob({ query, location: this.defaultLocation, + params: queryParameters, dryRun: true, }); @@ -63,6 +59,7 @@ class BigQueryDriver { const [job] = await this.bigqueryClient.createQueryJob({ query, location: this.defaultLocation, + params: queryParameters, }); // Wait for the query to finish @@ -78,9 +75,6 @@ class BigQueryDriver { const headers = Object.keys(rows[0]); tableData.unshift(headers); - // Create and print the table - return Table.table(tableData, { - columns: headers.map((header) => ({ alignment: 'left', width: 20 })), - }); + return tableData; } } diff --git a/src/functions/cloud/google/composerAirflow.ts b/src/functions/cloud/google/composerAirflow.ts new file mode 100644 index 00000000..c9eedae5 --- /dev/null +++ b/src/functions/cloud/google/composerAirflow.ts @@ -0,0 +1,226 @@ +import { existsSync, readFile, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import axios, { AxiosInstance, AxiosResponse } from 'axios'; +import { GoogleAuth } from 'google-auth-library'; +import { func, funcClass } from '#functionSchema/functionDecorators'; + +const AUTH_SCOPE = 'https://www.googleapis.com/auth/cloud-platform'; + +interface DagConfig { + [key: string]: any; +} + +interface DagRun { + conf: any; + dag_id: string; + dag_run_id: string; + data_interval_end: string; + data_interval_start: string; + end_date: string; + execution_date: string; + external_trigger: boolean; + last_scheduling_decision: string; + logical_date: string; + note: string; + run_type: string; + start_date: string; + state: string; +} + +interface TaskInstance { + dag_id: string; + dag_run_id: string; + duration: number; + end_date: string; + execution_date: string; + executor: string; + executor_config: string; + hostname: string; + map_index: number; + max_tries: number; + note: string; + operator: string; + pid: number; + pool: string; + pool_slots: number; + priority_weight: number; + queue: string; + queued_when: string; + rendered_fields: any; + rendered_map_index: number; + sla_miss: any | null; + start_date: string; + state: string; + task_display_name: string; + task_id: string; + trigger: any | null; + triggerer_job: any | null; + try_number: number; + unixname: string; +} + +let airflowMapping: Record | undefined; + +/** + * Required the file airflow.json to be present in the root of the project. + * The file should contain a JSON object with the following format: + * { + * "gcpProjectId": "https://airflow.example.com" + * } + */ +@funcClass(__filename) +export class ComposerAirflowClient { + private auth: GoogleAuth; + private httpClient: AxiosInstance; + + constructor() { + // Initialize GoogleAuth client using Application Default Credentials (ADC) + this.auth = new GoogleAuth({ scopes: [AUTH_SCOPE] }); + this.httpClient = axios.create({ timeout: 90000 }); + } + + /** + * Helper function to determine the Composer Airflow Web Server URL based on Google Cloud project ID. + */ + private getWebServerUrl(gcpProjectId: string): string { + if (!airflowMapping) { + const airflowFilePath = join(process.cwd(), 'airflow.json'); + if (!existsSync(airflowFilePath)) throw new Error(`Airflow config file not found at: ${airflowFilePath}`); + airflowMapping = JSON.parse(readFileSync(airflowFilePath).toString()); + if (!airflowMapping) throw new Error('Invalid Airflow config'); + } + if (!airflowMapping[gcpProjectId]) { + throw new Error(`No Airflow config found for project ID: ${gcpProjectId} Valid project IDs: ${Object.keys(airflowMapping).join(', ')}`); + } + return airflowMapping[gcpProjectId]; + } + + /** + * Fetches DAG runs for the given DAG ID and Google Cloud Project. + * + * @param gcpProjectId The Google Cloud Project ID where the Composer environment lives. + * @param dagId The ID of the DAG to fetch runs for. + * @param limit The maximum number of runs to fetch. (Defaults to 20) + */ + @func() + public async fetchDagRuns(gcpProjectId: string, dagId: string, limit = 20): Promise { + const airflowWebServerUrl = this.getWebServerUrl(gcpProjectId); + const token = await this.getAuthToken(); + + const url = `${airflowWebServerUrl}/api/v1/dags/${dagId}/dagRuns?limit=${limit}`; + const response = await this.makeRequest(url, 'GET', token); + + return response.data.dag_runs; + } + + /** + * Fetches all task instances for a specific DAG run. + * @param gcpProjectId The Google Cloud Project ID. + * @param dagId The ID of the DAG. + * @param dagRunId The ID of the specific DAG run. + * @returns A promise that resolves to an array of task instance objects. + */ + @func() + public async fetchTaskInstances(gcpProjectId: string, dagId: string, dagRunId: string): Promise { + const airflowWebServerUrl = this.getWebServerUrl(gcpProjectId); + const token = await this.getAuthToken(); + + const url = `${airflowWebServerUrl}/api/v1/dags/${dagId}/dagRuns/${dagRunId}/taskInstances`; + const response = await this.makeRequest(url, 'GET', token); + + return response.data.task_instances; + } + + /** + * Fetches the raw log for a specific task attempt. + * @param gcpProjectId The Google Cloud Project ID. + * @param dagId The ID of the DAG. + * @param dagRunId The ID of the DAG run. + * @param taskId The ID of the task. + * @param tryNumber The attempt number of the task. + * @returns A promise that resolves to the raw log content as a string. + */ + @func() + public async fetchTaskLog(gcpProjectId: string, dagId: string, dagRunId: string, taskId: string, tryNumber: number): Promise { + const airflowWebServerUrl = this.getWebServerUrl(gcpProjectId); + const token = await this.getAuthToken(); + + const url = `${airflowWebServerUrl}/api/v1/dags/${dagId}/dagRuns/${dagRunId}/taskInstances/${taskId}/logs/${tryNumber}`; + const response = await this.makeRequest(url, 'GET', token); + + return response.data; + } + + /** + * Fetches detailed metadata for a specific DAG. + * @param gcpProjectId The Google Cloud Project ID. + * @param dagId The ID of the DAG. + * @returns A promise that resolves to the DAG detail object. + */ + @func() + public async fetchDagDetails(gcpProjectId: string, dagId: string): Promise { + const airflowWebServerUrl = this.getWebServerUrl(gcpProjectId); + const token = await this.getAuthToken(); + const url = `${airflowWebServerUrl}/api/v1/dags/${dagId}`; + const response = await this.makeRequest(url, 'GET', token); + return response.data; + } + + /** + * Fetches the current Airflow configuration (airflow.cfg). + * @param gcpProjectId The Google Cloud Project ID. + * @returns A promise that resolves to the Airflow configuration object. + */ + @func() + public async fetchAirflowConfig(gcpProjectId: string): Promise { + const airflowWebServerUrl = this.getWebServerUrl(gcpProjectId); + const token = await this.getAuthToken(); + + const url = `${airflowWebServerUrl}/api/v1/config`; + const response = await this.makeRequest(url, 'GET', token); + + return response.data; + } + + /** + * Fetches a short-lived access token needed for authorization. + * This method supports the manual token handling approach seen in fetchDagRuns. + * @returns The access token string. + */ + private async getAuthToken(): Promise { + const token = await this.auth.getAccessToken(); + if (!token || typeof token !== 'string' || token.length === 0) throw new Error('Failed to retrieve access token.'); + return token; + } + + /** + * Generic request handler that uses the retrieved token for authorization. + * @param url The full URL to fetch. + * @param method The HTTP method ('GET', 'POST', etc.). + * @param token The Bearer token for Authorization. + * @param data Optional payload data for POST/PUT requests. + * @returns The Axios response object. + */ + private async makeRequest(url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE', token: string, data?: object): Promise { + try { + console.debug(`Making ${method} request to: ${url}`); + const response = await this.httpClient({ + method, + url, + data: data, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + return response; + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + const status = error.response.status; + if (status === 403) throw new Error(`403 Forbidden: Check Airflow RBAC roles for your account. Details: ${JSON.stringify(error.response.data)}`); + throw new Error(`Request failed with status ${status}: ${error.response.statusText}. ` + `Response data: ${JSON.stringify(error.response.data)}`); + } + throw error; + } + } +} diff --git a/src/functions/cloud/google/google-cloud.ts b/src/functions/cloud/google/google-cloud.ts index ce279b4a..d98f648f 100644 --- a/src/functions/cloud/google/google-cloud.ts +++ b/src/functions/cloud/google/google-cloud.ts @@ -17,14 +17,16 @@ export class GoogleCloud { * @param {string} [options.dateFromIso] - The date/time to get the logs from. Optional. * @param {string} [options.dateToIso] - The date/time to get the logs to. Optional. * @param {string} [options.freshness] - The freshness of the logs (eg. 10m, 1h). Optional. + * @param {string} [options.format] - Format of the response. Defaults to 'yaml' which is more compact and token efficient than 'json'. If you need to parse the results into JSON for programatic use, set this to 'json'. * @param {'asc'|'desc'} [options.order] - The order of the logs (asc or desc. defaults to desc). Optional. + * @param {number} [options.limit] - The limit of the logs. Optional. Defaults to 200. Maximum of 500. * @returns {Promise} */ @func() async getCloudLoggingLogs( projectId: string, filter: string, - options: { dateFromIso?: string; dateToIso?: string; freshness?: string; order?: 'asc' | 'desc' }, + options: { dateFromIso?: string; dateToIso?: string; freshness?: string; order?: 'asc' | 'desc'; limit?: number; format?: 'json' | 'yaml' }, ): Promise { let logFiler = filter; if (options.dateFromIso) logFiler += ` AND timestamp>="${options.dateFromIso}"`; @@ -34,13 +36,16 @@ export class GoogleCloud { let cmd = `gcloud logging read '${filter}' -q --project=${projectId} --format="json"`; if (options.freshness) cmd += ` --freshness=${options.freshness}`; if (options.order) cmd += ` --order=${options.order}`; - cmd += ' --limit=1000'; + if (options?.limit && options.limit > 500) options.limit = 500; + cmd += ` --limit=${options.limit ?? 200}`; const result = await execCommand(cmd); try { const json = JSON.parse(result.stdout); + if (options.format === 'json') return result.stdout; + if (!Array.isArray(json) || json.length === 0) return yamlStringify(json); // Logs for a single resource type will have common properties. Extract them out to reduce tokens returned. @@ -79,6 +84,44 @@ export class GoogleCloud { return trace ? JSON.stringify((trace as any).spans) : 'Trace Id not found'; } + /** + * Runs the BigQuery bq command line tool + * Example command: + * bq ls -j --max_results=50 --format=prettyjson --project_id=test_project_id --filter="labels.costcode:daily-data-aggregation" + * If you are having issues wih the arguments, either call this function with `bq help [COMMAND]` or read https://cloud.google.com/bigquery/docs/reference/bq-cli-reference + * The only supported commands are: ls, query, show, get-iam-policy, head + * @param bqCommand The bq command to execute + * @returns the console output if the exit code is 0, else throws the console output + */ + @func() + async executeBqCommand(bqCommand: string): Promise { + const args = bqCommand.split(' '); + if (args[0] !== 'bq') throw new Error('When calling executeBqCommand the bqCommand parameter must start with "bq"'); + + const cmd = args[1]; + const supportedCommands = new Set(['ls', 'query', 'show', 'get-iam-policy', 'head']); + if (!supportedCommands.has(cmd)) { + throw new Error( + `Command "${bqCommand}" does not appear to be a read operation. Only list, describe, get-iam-policy and read operations are allowed. If you feel that this is a mistake, please request for the gcloud command whitelisting to be updated`, + ); + } + + if (cmd === 'query') { + const _100gb_in_bytes = 100 * 1024 * 1024 * 1024; + // if(bqCommand.includes('--maximum_bytes_billed=')) { + // // extract the value + // const maximumBytesBilled = parseInt(bqCommand.split('--maximum_bytes_billed=')[1]); + // if(maximumBytesBilled < _100gb_in_bytes) throw new Error('The maximum_bytes_billed value must be at least 100GB'); + // } + + bqCommand += ` --maximum_bytes_billed=${_100gb_in_bytes}`; + } + + const result = await execCommand(bqCommand); + if (result.exitCode > 0) throw new Error(`Error running ${bqCommand}. ${result.stdout}${result.stderr}`); + return result.stdout; + } + /** * Query resource information by executing the gcloud command line tool. This must ONLY be used for querying information, and MUST NOT update or modify resources. * If the command supports the --project= argument then it MUST be included. @@ -87,10 +130,12 @@ export class GoogleCloud { */ @func() async executeGcloudCommandQuery(gcloudQueryCommand: string): Promise { - if (!gcloudQueryCommand.includes('--project=')) - throw new Error('When calling executeGcloudCommandQuery the gcloudQueryCommand parameter must include the --project= argument'); + // if (!gcloudQueryCommand.includes('--project=')) + // throw new Error('When calling executeGcloudCommandQuery the gcloudQueryCommand parameter must include the --project= argument'); + if (gcloudQueryCommand.split(' ')[0] !== 'gcloud') + throw new Error('When calling executeGcloudCommandQuery the gcloudQueryCommand parameter must start with "gcloud"'); - // Whitelist list, describe and get-iam-policy commands, otherwise require human-in-the-loop approval + // Whitelist list, describe and get-iam-policy commands, otherwise throw an error const args = gcloudQueryCommand.split(' '); if (args[1] === 'alpha' || args[1] === 'beta') { args.splice(1, 1); @@ -102,11 +147,13 @@ export class GoogleCloud { if (args[i].startsWith('list') || args[i] === 'describe' || args[i] === 'get-iam-policy' || args[i] === 'read') isQuery = true; } - if (!isQuery) { - await humanInTheLoop(agentContext()!, `Agent "${agentContext()!.name}" is requesting to run the command ${gcloudQueryCommand}`); - } + if (!isQuery) + throw new Error( + `Command "${gcloudQueryCommand}" does not appear to be a read operation. Only list, describe, get-iam-policy and read operations are allowed. If you feel that this is a mistake, please request for the gcloud command whitelisting to be updated`, + ); const result = await execCommand(gcloudQueryCommand); + if (result.exitCode > 0) throw new Error(`Error running ${gcloudQueryCommand}. ${result.stdout}${result.stderr}`); return result.stdout; } @@ -117,7 +164,7 @@ export class GoogleCloud { * @param gcloudModifyCommand The gcloud command to execute (incuding --project= if allowed) * @returns the console output if the exit code is 0, else throws the console output or human review rejection reason */ - @func() + // @func() async executeGcloudCommandModification(gcloudModifyCommand: string): Promise { if (!gcloudModifyCommand.includes('--project=')) throw new Error('When calling executeGcloudCommandQuery the gcloudQueryCommand parameter must include the --project= argument'); @@ -127,13 +174,4 @@ export class GoogleCloud { failOnError('Error running gcloudModifyCommand', result); return result.stdout; } - - // /** - // * Returns the open alert incidents across all the production projects - // * @returns {string[]} the open alert incidents - // */ - // @func() - // getOpenProductionIncidents(gcpProjectId: string): Promise { - // return Promise.resolve([]); - // } } diff --git a/src/llm/llmFactory.ts b/src/llm/llmFactory.ts index 5e2b2dee..fab3234b 100644 --- a/src/llm/llmFactory.ts +++ b/src/llm/llmFactory.ts @@ -37,7 +37,7 @@ export const LLM_FACTORY: Record LLM> = { ...deepinfraLLMRegistry(), ...cerebrasLLMRegistry(), ...perplexityLLMRegistry(), - ...xaiLLMRegistry(), + // ...xaiLLMRegistry(), ...nebiusLLMRegistry(), ...sambanovaLLMRegistry(), ...ollamaLLMRegistry(), diff --git a/src/llm/multi-agent/fastEasy.ts b/src/llm/multi-agent/fastEasy.ts index d41040ee..73f9a2c8 100644 --- a/src/llm/multi-agent/fastEasy.ts +++ b/src/llm/multi-agent/fastEasy.ts @@ -66,19 +66,29 @@ export class FastEasyLLM extends BaseLLM { } text += `${msgText}\n`; } - if (nonTextContent) { - if (this.gemini.isConfigured()) return await this.gemini.generateMessage(messages, opts); - if (this.openaiGPT5nano.isConfigured()) return await this.openaiGPT5nano.generateMessage(messages, opts); - throw new Error('No configured LLMs for non-text content'); - } - const tokens = await countTokens(text); + const textOnlyTokens = await countTokens(text); - if (this.cerebrasGptOss_120b.isConfigured() && tokens < this.cerebrasGptOss_120b.getMaxInputTokens()) - return await this.cerebrasGptOss_120b.generateMessage(messages, opts); - if (this.groqScout.isConfigured() && tokens < this.groqScout.getMaxInputTokens()) return await this.groqScout.generateMessage(messages, opts); + if (!nonTextContent) { + if (this.cerebrasGptOss_120b.isConfigured() && textOnlyTokens < this.cerebrasGptOss_120b.getMaxInputTokens()) { + try { + return await this.cerebrasGptOss_120b.generateMessage(messages, opts); + } catch (error) { + logger.error(`Error with ${this.cerebrasGptOss_120b.getDisplayName()}: ${error.message}. Trying next provider.`); + } + } + if (this.groqScout.isConfigured() && textOnlyTokens < this.groqScout.getMaxInputTokens()) { + try { + return await this.groqScout.generateMessage(messages, opts); + } catch (error) { + logger.error(`Error with ${this.groqScout.getDisplayName()}: ${error.message}. Trying next provider.`); + } + } + } + if (this.gemini.isConfigured()) return await this.gemini.generateMessage(messages, opts); + if (this.openaiGPT5nano.isConfigured()) return await this.openaiGPT5nano.generateMessage(messages, opts); - throw new Error('No configured LLMs for text content'); + throw new Error('No configured LLMs for Fast Easy'); // for (const llm of this.providers) { // if (!llm.isConfigured()) { diff --git a/src/llm/multi-agent/fastMedium.ts b/src/llm/multi-agent/fastMedium.ts index 471259cd..392461ba 100644 --- a/src/llm/multi-agent/fastMedium.ts +++ b/src/llm/multi-agent/fastMedium.ts @@ -1,7 +1,7 @@ import { anthropicClaude4_5_Haiku } from '#llm/services/anthropic'; import { Claude4_5_Haiku_Vertex } from '#llm/services/anthropic-vertex'; -import { cerebrasQwen3_235b_Thinking, cerebrasQwen3_Coder } from '#llm/services/cerebras'; -import { groqKimiK2, groqQwen3_32b } from '#llm/services/groq'; +import { cerebrasQwen3_Coder } from '#llm/services/cerebras'; +import { groqKimiK2 } from '#llm/services/groq'; import { openaiGPT5mini } from '#llm/services/openai'; import { vertexGemini_2_5_Flash } from '#llm/services/vertexai'; import { countTokens } from '#llm/tokens'; @@ -16,7 +16,7 @@ import { BaseLLM } from '../base-llm'; export class FastMediumLLM extends BaseLLM { private readonly providers: LLM[]; private readonly cerebras = cerebrasQwen3_Coder(); - private readonly groq = groqQwen3_32b(); + private readonly groq = groqKimiK2(); private readonly openai = openaiGPT5mini(); private readonly gemini = vertexGemini_2_5_Flash({ thinking: 'high' }); private readonly haiku = anthropicClaude4_5_Haiku(); @@ -68,15 +68,23 @@ export class FastMediumLLM extends BaseLLM { override async _generateMessage(messages: ReadonlyArray, opts?: GenerateTextOptions): Promise { opts ??= {}; opts.thinking = 'high'; - const tokens = await this.textTokens(messages); + const textOnlyTokens = await this.textTokens(messages); + + try { + if (textOnlyTokens && this.groq.isConfigured() && textOnlyTokens < this.groq.getMaxInputTokens() * 0.9) + return await this.groq.generateMessage(messages, opts); + } catch (e) { + logger.warn(`Error calling ${this.groq.getId()} with ${textOnlyTokens} tokens: ${e.message}`); + } + try { - if (tokens && this.cerebras.isConfigured() && tokens < this.cerebras.getMaxInputTokens() * 0.4) + if (textOnlyTokens && this.cerebras.isConfigured() && textOnlyTokens < this.cerebras.getMaxInputTokens() * 0.4) return await this.cerebras.generateMessage(messages, opts); } catch (e) { - logger.warn(e, `Error calling ${this.cerebras.getId()} with ${tokens} tokens: ${e.message}`); + logger.warn(`Error calling ${this.cerebras.getId()} with ${textOnlyTokens} tokens: ${e.message}`); } + if (this.gemini.isConfigured()) return await this.gemini.generateMessage(messages, opts); if (this.openai.isConfigured()) return await this.openai.generateMessage(messages, opts); - - return await this.gemini.generateMessage(messages, opts); + throw new Error('No configured LLMs for fastMedium'); } } diff --git a/src/llm/services/vertexai.ts b/src/llm/services/vertexai.ts index 715d2648..73174d6f 100644 --- a/src/llm/services/vertexai.ts +++ b/src/llm/services/vertexai.ts @@ -41,7 +41,7 @@ export function vertexGemini_2_5_Flash(defaultOpts?: GenerateTextOptions): LLM { // https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-flash-lite export function vertexGemini_2_5_Flash_Lite(): LLM { - return new VertexLLM('Gemini 2.5 Flash Lite', 'gemini-2.5-flash-lite-preview-09-2025', 1_000_000, costPerMilTokens(0.01, 0.4), [ + return new VertexLLM('Gemini 2.5 Flash Lite', 'gemini-2.5-flash-lite-preview-09-2025', 1_000_000, costPerMilTokens(0.1, 0.4), [ 'gemini-2.5-flash-lite', 'gemini-2.0-flash-lite-preview-02-05', 'gemini-2.5-flash-lite-preview-06-17', diff --git a/src/routes/chat/getChatByIdRoute.ts b/src/routes/chat/getChatByIdRoute.ts index 38a8629b..42ee7777 100644 --- a/src/routes/chat/getChatByIdRoute.ts +++ b/src/routes/chat/getChatByIdRoute.ts @@ -12,21 +12,11 @@ export async function getChatByIdRoute(fastify: AppFastifyInstance): Promise