diff --git a/backend/.env.development b/backend/.env.development index 236e5076..0e67ee63 100644 --- a/backend/.env.development +++ b/backend/.env.development @@ -37,9 +37,14 @@ YORKIE_PROJECT_NAME=default YORKIE_PROJECT_SECRET_KEY="" # YORKIE_INTELLIGENCE: Whether to enable Yorkie Intelligence for collaborative editing. -# Set to true if Yorkie Intelligence is required. +# Set to ollama modelname if Yorkie Intelligence is required +# you can find llm model in https://ollama.com/library # If set to false, OPENAI_API_KEY is not required. -YORKIE_INTELLIGENCE=false +YORKIE_INTELLIGENCE="ollama:gemma2:2b" + +# OLLAMA_HOST_URL: yorkie-intelligence ollama url +OLLAMA_HOST_URL=http://localhost:11434 + # OPENAI_API_KEY: API key for using the gpt-3.5-turbo model by Yorkie Intelligence. # To obtain an API key, visit OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-api-key OPENAI_API_KEY=your_openai_api_key_here diff --git a/backend/docker/docker-compose-full.yml b/backend/docker/docker-compose-full.yml index 5bec6f56..2fddd53f 100644 --- a/backend/docker/docker-compose-full.yml +++ b/backend/docker/docker-compose-full.yml @@ -40,6 +40,12 @@ services: - "8080:8080" - "8081:8081" + yorkie-intelligence: + image: "ollama/ollama:latest" + restart: always + expose: + - "11434" + mongo: build: context: ./mongodb_replica diff --git a/backend/docker/docker-compose.yml b/backend/docker/docker-compose.yml index 7b191618..a4845797 100644 --- a/backend/docker/docker-compose.yml +++ b/backend/docker/docker-compose.yml @@ -9,6 +9,12 @@ services: - "8080:8080" - "8081:8081" + yorkie-intelligence: + image: "ollama/ollama:latest" + restart: always + expose: + - "11434" + mongo: build: context: ./mongodb_replica diff --git a/backend/package-lock.json b/backend/package-lock.json index dc508fe5..0ea001df 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,6 +13,7 @@ "@aws-sdk/s3-request-presigner": "^3.509.0", "@langchain/community": "^0.0.21", "@langchain/core": "^0.1.18", + "@langchain/ollama": "^0.0.4", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.0.0", @@ -2999,6 +3000,107 @@ "node": ">=18" } }, + "node_modules/@langchain/ollama": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@langchain/ollama/-/ollama-0.0.4.tgz", + "integrity": "sha512-laYaYFZsbu0Mjhm40CypXPV5gYvQjd16oBEoGnFFjmCUgCCu6gjETg3tgvgifo7w+dhmNPLUyqnjXA/BpWEH2Q==", + "license": "MIT", + "dependencies": { + "@langchain/core": ">=0.2.21 <0.3.0", + "ollama": "^0.5.6", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/ollama/node_modules/@langchain/core": { + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.23.tgz", + "integrity": "sha512-elPg6WpAkxWEIGC9u38F2anbzqfYYEy32lJdsd9dtChcHSFmFLlXqa+SnpO3R772gUuJmcu+Pd+fCvmRFy029w==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "~0.1.39", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/ollama/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@langchain/ollama/node_modules/langsmith": { + "version": "0.1.41", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.41.tgz", + "integrity": "sha512-8R7s/225Pxmv0ipMfd6sqmWVsfHLQivYlQZ0vx5K+ReoknummTenQlVK8gapk3kqRMnzkrouuRHMhWjMR6RgUA==", + "license": "MIT", + "dependencies": { + "@types/uuid": "^9.0.1", + "commander": "^10.0.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^9.0.0" + }, + "peerDependencies": { + "@langchain/core": "*", + "langchain": "*", + "openai": "*" + }, + "peerDependenciesMeta": { + "@langchain/core": { + "optional": true + }, + "langchain": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, + "node_modules/@langchain/ollama/node_modules/langsmith/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/ollama/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@langchain/openai": { "version": "0.0.13", "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.0.13.tgz", @@ -9293,9 +9395,10 @@ } }, "node_modules/js-tiktoken": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.10.tgz", - "integrity": "sha512-ZoSxbGjvGyMT13x6ACo9ebhDha/0FHdKA+OsQcMOWcm1Zs7r90Rhk5lhERLzji+3rA7EKpXCgwXcM5fF3DMpdA==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.12.tgz", + "integrity": "sha512-L7wURW1fH9Qaext0VzaUDpFGVQgjkdE3Dgsy9/+yXyGEpBKnylTd0mU0bfbNkKDlXRb6TEsZkwuflu1B8uQbJQ==", + "license": "MIT", "dependencies": { "base64-js": "^1.5.1" } @@ -10387,6 +10490,15 @@ "node": ">= 6.0.0" } }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -10542,6 +10654,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ollama": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.8.tgz", + "integrity": "sha512-frBGdfSV34i7JybLZUeyCYDx0CMyDiG4On8xOK+cNRWM04HImhoWgIMpF4p7vTkQumadbSxOteR7SZyKqNmOXg==", + "license": "MIT", + "dependencies": { + "whatwg-fetch": "^3.6.20" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -11731,12 +11852,10 @@ "dev": true }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -11744,22 +11863,6 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -13178,6 +13281,12 @@ "node": ">=4.0" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 46263e02..785d0e68 100644 --- a/backend/package.json +++ b/backend/package.json @@ -32,6 +32,7 @@ "@aws-sdk/s3-request-presigner": "^3.509.0", "@langchain/community": "^0.0.21", "@langchain/core": "^0.1.18", + "@langchain/ollama": "^0.0.4", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.0.0", diff --git a/backend/src/langchain/langchain.module.ts b/backend/src/langchain/langchain.module.ts index 73771480..1a9e49b0 100644 --- a/backend/src/langchain/langchain.module.ts +++ b/backend/src/langchain/langchain.module.ts @@ -1,10 +1,54 @@ import { Module } from "@nestjs/common"; import { ChatOpenAI } from "@langchain/openai"; +import { ChatOllama } from "@langchain/ollama"; import { BaseChatModel } from "@langchain/core/language_models/chat_models"; +type ModelList = { + [key: string]: string[]; +}; + +const modelList: ModelList = { + ollama: [ + "lamma3.1", + "gemma2", + "gemma2:2b", + "phi3", + "mistral", + "neural-chat", + "starling-lm", + "solar", + ], + openai: ["gpt-3.5-turbo", "gpt-4o-mini"], +}; + const chatModelFactory = { provide: "ChatModel", - useFactory: () => new ChatOpenAI({ modelName: "gpt-4o-mini" }) as BaseChatModel, + useFactory: () => { + const modelType = process.env.YORKIE_INTELLIGENCE; + try { + const [provider, model] = modelType.split(":", 2); + let chatModel: BaseChatModel | ChatOllama; + + if (modelList[provider]?.includes(model)) { + if (provider === "ollama") { + chatModel = new ChatOllama({ + model: model, + baseUrl: process.env.OLLAMA_HOST_URL, + checkOrPullModel: true, + streaming: true, + }); + } else if (provider === "openai") { + chatModel = new ChatOpenAI({ modelName: model }); + } + } + + if (!chatModel) throw new Error(); + + return chatModel; + } catch { + throw new Error(`${modelType} is not found. please check your model name`); + } + }, }; @Module({ diff --git a/backend/src/settings/settings.service.ts b/backend/src/settings/settings.service.ts index a2532705..c387c3a7 100644 --- a/backend/src/settings/settings.service.ts +++ b/backend/src/settings/settings.service.ts @@ -10,7 +10,7 @@ export class SettingsService { async getSettings(): Promise { return { yorkieIntelligence: { - enable: this.configService.get("YORKIE_INTELLIGENCE") === "true", + enable: this.configService.get("YORKIE_INTELLIGENCE") !== "false", config: { features: generateFeatureList(this.configService), },