diff --git a/libs/langchain-ollama/.eslintrc.cjs b/libs/langchain-ollama/.eslintrc.cjs new file mode 100644 index 000000000000..e3033ac0160c --- /dev/null +++ b/libs/langchain-ollama/.eslintrc.cjs @@ -0,0 +1,74 @@ +module.exports = { + extends: [ + "airbnb-base", + "eslint:recommended", + "prettier", + "plugin:@typescript-eslint/recommended", + ], + parserOptions: { + ecmaVersion: 12, + parser: "@typescript-eslint/parser", + project: "./tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint", "no-instanceof"], + ignorePatterns: [ + ".eslintrc.cjs", + "scripts", + "node_modules", + "dist", + "dist-cjs", + "*.js", + "*.cjs", + "*.d.ts", + ], + rules: { + "no-process-env": 2, + "no-instanceof/no-instanceof": 2, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-shadow": 0, + "@typescript-eslint/no-empty-interface": 0, + "@typescript-eslint/no-use-before-define": ["error", "nofunc"], + "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }], + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + camelcase: 0, + "class-methods-use-this": 0, + "import/extensions": [2, "ignorePackages"], + "import/no-extraneous-dependencies": [ + "error", + { devDependencies: ["**/*.test.ts"] }, + ], + "import/no-unresolved": 0, + "import/prefer-default-export": 0, + "keyword-spacing": "error", + "max-classes-per-file": 0, + "max-len": 0, + "no-await-in-loop": 0, + "no-bitwise": 0, + "no-console": 0, + "no-restricted-syntax": 0, + "no-shadow": 0, + "no-continue": 0, + "no-void": 0, + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "no-useless-constructor": 0, + "no-return-await": 0, + "consistent-return": 0, + "no-else-return": 0, + "func-names": 0, + "no-lonely-if": 0, + "prefer-rest-params": 0, + "new-cap": ["error", { properties: false, capIsNew: false }], + }, + overrides: [ + { + files: ["**/*.test.ts"], + rules: { + "@typescript-eslint/no-unused-vars": "off", + }, + }, + ], +}; diff --git a/libs/langchain-ollama/.gitignore b/libs/langchain-ollama/.gitignore new file mode 100644 index 000000000000..ae701c7fe705 --- /dev/null +++ b/libs/langchain-ollama/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.yarn diff --git a/libs/langchain-ollama/.prettierrc b/libs/langchain-ollama/.prettierrc new file mode 100644 index 000000000000..ba08ff04f677 --- /dev/null +++ b/libs/langchain-ollama/.prettierrc @@ -0,0 +1,19 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "always", + "requirePragma": false, + "insertPragma": false, + "proseWrap": "preserve", + "htmlWhitespaceSensitivity": "css", + "vueIndentScriptAndStyle": false, + "endOfLine": "lf" +} diff --git a/libs/langchain-ollama/.release-it.json b/libs/langchain-ollama/.release-it.json new file mode 100644 index 000000000000..522ee6abf705 --- /dev/null +++ b/libs/langchain-ollama/.release-it.json @@ -0,0 +1,10 @@ +{ + "github": { + "release": true, + "autoGenerate": true, + "tokenRef": "GITHUB_TOKEN_RELEASE" + }, + "npm": { + "versionArgs": ["--workspaces-update=false"] + } +} diff --git a/libs/langchain-ollama/LICENSE b/libs/langchain-ollama/LICENSE new file mode 100644 index 000000000000..8cd8f501eb49 --- /dev/null +++ b/libs/langchain-ollama/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2023 LangChain + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/libs/langchain-ollama/README.md b/libs/langchain-ollama/README.md new file mode 100644 index 000000000000..05d28ee656e4 --- /dev/null +++ b/libs/langchain-ollama/README.md @@ -0,0 +1,67 @@ +# @langchain/ollama + +This package contains the LangChain.js integrations for Ollama via the `ollama` TypeScript SDK. + +## Installation + +```bash npm2yarn +npm install @langchain/ollama +``` + +TODO: add setup instructions for ollama locally + +## Chat Models + +```typescript +import { ChatOllama } from "@langchain/ollama"; + +const model = new ChatOllama({ + model: "todo-add-me", // Default value. +}); + +const result = await model.invoke(["human", "Hello, how are you?"]); +``` + +## Development + +To develop the `@langchain/ollama` package, you'll need to follow these instructions: + +### Install dependencies + +```bash +yarn install +``` + +### Build the package + +```bash +yarn build +``` + +Or from the repo root: + +```bash +yarn build --filter=@langchain/ollama +``` + +### Run tests + +Test files should live within a `tests/` file in the `src/` folder. Unit tests should end in `.test.ts` and integration tests should +end in `.int.test.ts`: + +```bash +$ yarn test +$ yarn test:int +``` + +### Lint & Format + +Run the linter & formatter to ensure your code is up to standard: + +```bash +yarn lint && yarn format +``` + +### Adding new entrypoints + +If you add a new file to be exported, either import & re-export from `src/index.ts`, or add it to the `entrypoints` field in the `config` variable located inside `langchain.config.js` and run `yarn build` to generate the new entrypoint. diff --git a/libs/langchain-ollama/jest.config.cjs b/libs/langchain-ollama/jest.config.cjs new file mode 100644 index 000000000000..994826496bc5 --- /dev/null +++ b/libs/langchain-ollama/jest.config.cjs @@ -0,0 +1,21 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: "ts-jest/presets/default-esm", + testEnvironment: "./jest.env.cjs", + modulePathIgnorePatterns: ["dist/", "docs/"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + transform: { + "^.+\\.tsx?$": ["@swc/jest"], + }, + transformIgnorePatterns: [ + "/node_modules/", + "\\.pnp\\.[^\\/]+$", + "./scripts/jest-setup-after-env.js", + ], + setupFiles: ["dotenv/config"], + testTimeout: 20_000, + passWithNoTests: true, + collectCoverageFrom: ["src/**/*.ts"], +}; diff --git a/libs/langchain-ollama/jest.env.cjs b/libs/langchain-ollama/jest.env.cjs new file mode 100644 index 000000000000..2ccedccb8672 --- /dev/null +++ b/libs/langchain-ollama/jest.env.cjs @@ -0,0 +1,12 @@ +const { TestEnvironment } = require("jest-environment-node"); + +class AdjustedTestEnvironmentToSupportFloat32Array extends TestEnvironment { + constructor(config, context) { + // Make `instanceof Float32Array` return true in tests + // to avoid https://github.com/xenova/transformers.js/issues/57 and https://github.com/jestjs/jest/issues/2549 + super(config, context); + this.global.Float32Array = Float32Array; + } +} + +module.exports = AdjustedTestEnvironmentToSupportFloat32Array; diff --git a/libs/langchain-ollama/langchain.config.js b/libs/langchain-ollama/langchain.config.js new file mode 100644 index 000000000000..46b1a2b31264 --- /dev/null +++ b/libs/langchain-ollama/langchain.config.js @@ -0,0 +1,22 @@ +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** + * @param {string} relativePath + * @returns {string} + */ +function abs(relativePath) { + return resolve(dirname(fileURLToPath(import.meta.url)), relativePath); +} + +export const config = { + internals: [/node\:/, /@langchain\/core\//], + entrypoints: { + index: "index", + }, + requiresOptionalDependency: [], + tsConfigPath: resolve("./tsconfig.json"), + cjsSource: "./dist-cjs", + cjsDestination: "./dist", + abs, +}; diff --git a/libs/langchain-ollama/package.json b/libs/langchain-ollama/package.json new file mode 100644 index 000000000000..46a81f9dd5d1 --- /dev/null +++ b/libs/langchain-ollama/package.json @@ -0,0 +1,82 @@ +{ + "name": "langchain-ollama", + "version": "0.0.0", + "description": "Ollama integration for LangChain.js", + "type": "module", + "engines": { + "node": ">=18" + }, + "main": "./index.js", + "types": "./index.d.ts", + "repository": { + "type": "git", + "url": "git@github.com:langchain-ai/langchainjs.git" + }, + "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-ollama/", + "scripts": { + "build": "yarn turbo:command build:internal --filter=@langchain/ollama", + "build:internal": "yarn lc-build:v2 --create-entrypoints --pre --tree-shaking", + "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", + "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", + "lint": "yarn lint:eslint && yarn lint:dpdm", + "lint:fix": "yarn lint:eslint --fix && yarn lint:dpdm", + "clean": "rm -rf .turbo dist/", + "prepack": "yarn build", + "test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts --testTimeout 30000 --maxWorkers=50%", + "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch --testPathIgnorePatterns=\\.int\\.test.ts", + "test:single": "NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.cjs --testTimeout 100000", + "test:int": "NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.int\\.test.ts --testTimeout 100000 --maxWorkers=50%", + "format": "prettier --config .prettierrc --write \"src\"", + "format:check": "prettier --config .prettierrc --check \"src\"", + "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", + "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", + "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + }, + "author": "LangChain", + "license": "MIT", + "dependencies": { + "@langchain/core": ">0.1.0 <0.3.0", + "ollama": "^0.5.2" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@langchain/scripts": "~0.0.14", + "@swc/core": "^1.3.90", + "@swc/jest": "^0.2.29", + "@tsconfig/recommended": "^1.0.3", + "@typescript-eslint/eslint-plugin": "^6.12.0", + "@typescript-eslint/parser": "^6.12.0", + "dotenv": "^16.3.1", + "dpdm": "^3.12.0", + "eslint": "^8.33.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-no-instanceof": "^1.0.1", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.5.0", + "jest-environment-node": "^29.6.4", + "prettier": "^2.8.3", + "release-it": "^15.10.1", + "rollup": "^4.5.2", + "ts-jest": "^29.1.0", + "typescript": "<5.2.0" + }, + "publishConfig": { + "access": "public" + }, + "exports": { + ".": { + "types": "./index.d.ts", + "import": "./index.js", + "require": "./index.cjs" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/", + "index.cjs", + "index.js", + "index.d.ts" + ] +} diff --git a/libs/langchain-ollama/scripts/jest-setup-after-env.js b/libs/langchain-ollama/scripts/jest-setup-after-env.js new file mode 100644 index 000000000000..778cf7437a20 --- /dev/null +++ b/libs/langchain-ollama/scripts/jest-setup-after-env.js @@ -0,0 +1,3 @@ +import { awaitAllCallbacks } from "@langchain/core/callbacks/promises"; + +afterAll(awaitAllCallbacks); diff --git a/libs/langchain-ollama/src/chat_models.ts b/libs/langchain-ollama/src/chat_models.ts new file mode 100644 index 000000000000..7ead127849b1 --- /dev/null +++ b/libs/langchain-ollama/src/chat_models.ts @@ -0,0 +1,105 @@ +import { type BaseMessage } from "@langchain/core/messages"; +import { type BaseLanguageModelCallOptions } from "@langchain/core/language_models/base"; + +import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager"; +import { + type BaseChatModelParams, + SimpleChatModel, +} from "@langchain/core/language_models/chat_models"; + +// Uncomment if implementing streaming + +// import { +// ChatGenerationChunk, +// } from "@langchain/core/outputs"; +// import { +// AIMessageChunk, +// } from "@langchain/core/messages"; + +/** + * Input to chat model class. + */ +export interface ChatIntegrationInput extends BaseChatModelParams {} + +/** + * Integration with a chat model. + */ +export class ChatIntegration< + CallOptions extends BaseLanguageModelCallOptions = BaseLanguageModelCallOptions + > + extends SimpleChatModel + implements ChatIntegrationInput +{ + // Used for tracing, replace with the same name as your class + static lc_name() { + return "ChatIntegration"; + } + + lc_serializable = true; + + /** + * Replace with any secrets this class passes to `super`. + * See {@link ../../langchain-cohere/src/chat_model.ts} for + * an example. + */ + get lc_secrets(): { [key: string]: string } | undefined { + return { + apiKey: "API_KEY_NAME", + }; + } + + get lc_aliases(): { [key: string]: string } | undefined { + return { + apiKey: "API_KEY_NAME", + }; + } + + constructor(fields?: ChatIntegrationInput) { + super(fields ?? {}); + } + + // Replace + _llmType() { + return "chat_integration"; + } + + /** + * For some given input messages and options, return a string output. + */ + _call( + _messages: BaseMessage[], + _options: this["ParsedCallOptions"], + _runManager?: CallbackManagerForLLMRun + ): Promise { + throw new Error("Not implemented."); + } + + /** + * Implement to support streaming. + * Should yield chunks iteratively. + */ + // async *_streamResponseChunks( + // messages: BaseMessage[], + // options: this["ParsedCallOptions"], + // runManager?: CallbackManagerForLLMRun + // ): AsyncGenerator { + // // All models have a built in `this.caller` property for retries + // const stream = await this.caller.call(async () => + // createStreamMethod() + // ); + // for await (const chunk of stream) { + // if (!chunk.done) { + // yield new ChatGenerationChunk({ + // text: chunk.response, + // message: new AIMessageChunk({ content: chunk.response }), + // }); + // await runManager?.handleLLMNewToken(chunk.response ?? ""); + // } + // } + // } + + /** @ignore */ + _combineLLMOutput() { + return []; + } +} diff --git a/libs/langchain-ollama/src/index.ts b/libs/langchain-ollama/src/index.ts new file mode 100644 index 000000000000..38c7cea7f478 --- /dev/null +++ b/libs/langchain-ollama/src/index.ts @@ -0,0 +1 @@ +export * from "./chat_models.js"; diff --git a/libs/langchain-ollama/src/tests/chat_models.int.test.ts b/libs/langchain-ollama/src/tests/chat_models.int.test.ts new file mode 100644 index 000000000000..5d609f496501 --- /dev/null +++ b/libs/langchain-ollama/src/tests/chat_models.int.test.ts @@ -0,0 +1,5 @@ +import { test } from "@jest/globals"; + +test("Test chat model", async () => { + // Your test here +}); diff --git a/libs/langchain-ollama/src/tests/chat_models.test.ts b/libs/langchain-ollama/src/tests/chat_models.test.ts new file mode 100644 index 000000000000..5d609f496501 --- /dev/null +++ b/libs/langchain-ollama/src/tests/chat_models.test.ts @@ -0,0 +1,5 @@ +import { test } from "@jest/globals"; + +test("Test chat model", async () => { + // Your test here +}); diff --git a/libs/langchain-ollama/tsconfig.cjs.json b/libs/langchain-ollama/tsconfig.cjs.json new file mode 100644 index 000000000000..3b7026ea406c --- /dev/null +++ b/libs/langchain-ollama/tsconfig.cjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "declaration": false + }, + "exclude": ["node_modules", "dist", "docs", "**/tests"] +} diff --git a/libs/langchain-ollama/tsconfig.json b/libs/langchain-ollama/tsconfig.json new file mode 100644 index 000000000000..bc85d83b6229 --- /dev/null +++ b/libs/langchain-ollama/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "@tsconfig/recommended", + "compilerOptions": { + "outDir": "../dist", + "rootDir": "./src", + "target": "ES2021", + "lib": ["ES2021", "ES2022.Object", "DOM"], + "module": "ES2020", + "moduleResolution": "nodenext", + "esModuleInterop": true, + "declaration": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useDefineForClassFields": true, + "strictPropertyInitialization": false, + "allowJs": true, + "strict": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "docs"] +} diff --git a/libs/langchain-ollama/turbo.json b/libs/langchain-ollama/turbo.json new file mode 100644 index 000000000000..d024cee15c81 --- /dev/null +++ b/libs/langchain-ollama/turbo.json @@ -0,0 +1,11 @@ +{ + "extends": ["//"], + "pipeline": { + "build": { + "outputs": ["**/dist/**"] + }, + "build:internal": { + "dependsOn": ["^build:internal"] + } + } +} diff --git a/yarn.lock b/yarn.lock index b82e3b5d8ff1..633c5eae894a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29870,6 +29870,37 @@ __metadata: languageName: node linkType: hard +"langchain-ollama@workspace:libs/langchain-ollama": + version: 0.0.0-use.local + resolution: "langchain-ollama@workspace:libs/langchain-ollama" + dependencies: + "@jest/globals": ^29.5.0 + "@langchain/core": ">0.1.0 <0.3.0" + "@langchain/scripts": ~0.0.14 + "@swc/core": ^1.3.90 + "@swc/jest": ^0.2.29 + "@tsconfig/recommended": ^1.0.3 + "@typescript-eslint/eslint-plugin": ^6.12.0 + "@typescript-eslint/parser": ^6.12.0 + dotenv: ^16.3.1 + dpdm: ^3.12.0 + eslint: ^8.33.0 + eslint-config-airbnb-base: ^15.0.0 + eslint-config-prettier: ^8.6.0 + eslint-plugin-import: ^2.27.5 + eslint-plugin-no-instanceof: ^1.0.1 + eslint-plugin-prettier: ^4.2.1 + jest: ^29.5.0 + jest-environment-node: ^29.6.4 + ollama: ^0.5.2 + prettier: ^2.8.3 + release-it: ^15.10.1 + rollup: ^4.5.2 + ts-jest: ^29.1.0 + typescript: <5.2.0 + languageName: unknown + linkType: soft + "langchain@npm:0.2.3": version: 0.2.3 resolution: "langchain@npm:0.2.3" @@ -32985,6 +33016,15 @@ __metadata: languageName: node linkType: hard +"ollama@npm:^0.5.2": + version: 0.5.2 + resolution: "ollama@npm:0.5.2" + dependencies: + whatwg-fetch: ^3.6.20 + checksum: d824825fcf52dba24e4c99eb7ec1f95075dc2e3c89d89991af7d633a67f4484f43386dd83e70b757ab1d695d7a15c27f0ed87c0ac91d47509836fbec138db85a + languageName: node + linkType: hard + "on-finished@npm:2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -40358,6 +40398,13 @@ __metadata: languageName: node linkType: hard +"whatwg-fetch@npm:^3.6.20": + version: 3.6.20 + resolution: "whatwg-fetch@npm:3.6.20" + checksum: c58851ea2c4efe5c2235f13450f426824cf0253c1d45da28f45900290ae602a20aff2ab43346f16ec58917d5562e159cd691efa368354b2e82918c2146a519c5 + languageName: node + linkType: hard + "whatwg-mimetype@npm:^3.0.0": version: 3.0.0 resolution: "whatwg-mimetype@npm:3.0.0"