diff --git a/src/packages/frontend/components/language-model-icon.tsx b/src/packages/frontend/components/language-model-icon.tsx
index 1c84bbf1964..bef2211d432 100644
--- a/src/packages/frontend/components/language-model-icon.tsx
+++ b/src/packages/frontend/components/language-model-icon.tsx
@@ -5,6 +5,7 @@ import { unreachable } from "@cocalc/util/misc";
import AIAvatar from "./ai-avatar";
import GoogleGeminiLogo from "./google-gemini-avatar";
import GooglePalmLogo from "./google-palm-avatar";
+import OllamaAvatar from "./ollama-avatar";
import OpenAIAvatar from "./openai-avatar";
export function LanguageModelVendorAvatar(
@@ -40,6 +41,9 @@ export function LanguageModelVendorAvatar(
return fallback();
}
}
+ case "ollama":
+ return ;
+
default:
unreachable(vendor);
return fallback();
diff --git a/src/packages/frontend/components/ollama-avatar.tsx b/src/packages/frontend/components/ollama-avatar.tsx
new file mode 100644
index 00000000000..c9c33f93c1e
--- /dev/null
+++ b/src/packages/frontend/components/ollama-avatar.tsx
@@ -0,0 +1,33 @@
+import { CSS } from "../app-framework";
+import ollamaPng from "./ollama.png";
+
+export default function OllamaAvatar({
+ size = 64,
+ style,
+}: {
+ size: number;
+ style?: CSS;
+}) {
+ // render the ollamaPng (a square png image with transparent background) with the given size and background color
+
+ return (
+
+
+
+ );
+}
diff --git a/src/packages/frontend/components/ollama.png b/src/packages/frontend/components/ollama.png
new file mode 100644
index 00000000000..1f142c8d534
Binary files /dev/null and b/src/packages/frontend/components/ollama.png differ
diff --git a/src/packages/pnpm-lock.yaml b/src/packages/pnpm-lock.yaml
index c6a1b7496ff..d975732d90e 100644
--- a/src/packages/pnpm-lock.yaml
+++ b/src/packages/pnpm-lock.yaml
@@ -1339,6 +1339,9 @@ importers:
'@isaacs/ttlcache':
specifier: ^1.2.1
version: 1.2.1
+ '@langchain/community':
+ specifier: ^0.0.32
+ version: 0.0.32(@google-ai/generativelanguage@1.1.0)(encoding@0.1.13)(google-auth-library@9.4.1)(lodash@4.17.21)
'@node-saml/passport-saml':
specifier: ^4.0.4
version: 4.0.4
@@ -3979,6 +3982,307 @@ packages:
- crypto
dev: false
+ /@langchain/community@0.0.32(@google-ai/generativelanguage@1.1.0)(encoding@0.1.13)(google-auth-library@9.4.1)(lodash@4.17.21):
+ resolution: {integrity: sha512-jN4BxGKAmLbA87hqXH5Mx1IRMMVOgcn1TY1MLOVyBcBa12EvHFx8suogtXgA2ekfc8U8nIryVb1ftSupwUBv/A==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@aws-crypto/sha256-js': ^5.0.0
+ '@aws-sdk/client-bedrock-agent-runtime': ^3.485.0
+ '@aws-sdk/client-bedrock-runtime': ^3.422.0
+ '@aws-sdk/client-dynamodb': ^3.310.0
+ '@aws-sdk/client-kendra': ^3.352.0
+ '@aws-sdk/client-lambda': ^3.310.0
+ '@aws-sdk/client-sagemaker-runtime': ^3.310.0
+ '@aws-sdk/client-sfn': ^3.310.0
+ '@aws-sdk/credential-provider-node': ^3.388.0
+ '@azure/search-documents': ^12.0.0
+ '@clickhouse/client': ^0.2.5
+ '@cloudflare/ai': '*'
+ '@datastax/astra-db-ts': ^0.1.4
+ '@elastic/elasticsearch': ^8.4.0
+ '@getmetal/metal-sdk': '*'
+ '@getzep/zep-js': ^0.9.0
+ '@gomomento/sdk': ^1.51.1
+ '@gomomento/sdk-core': ^1.51.1
+ '@google-ai/generativelanguage': ^0.2.1
+ '@gradientai/nodejs-sdk': ^1.2.0
+ '@huggingface/inference': ^2.6.4
+ '@mozilla/readability': '*'
+ '@opensearch-project/opensearch': '*'
+ '@pinecone-database/pinecone': '*'
+ '@planetscale/database': ^1.8.0
+ '@qdrant/js-client-rest': ^1.2.0
+ '@raycast/api': ^1.55.2
+ '@rockset/client': ^0.9.1
+ '@smithy/eventstream-codec': ^2.0.5
+ '@smithy/protocol-http': ^3.0.6
+ '@smithy/signature-v4': ^2.0.10
+ '@smithy/util-utf8': ^2.0.0
+ '@supabase/postgrest-js': ^1.1.1
+ '@supabase/supabase-js': ^2.10.0
+ '@tensorflow-models/universal-sentence-encoder': '*'
+ '@tensorflow/tfjs-converter': '*'
+ '@tensorflow/tfjs-core': '*'
+ '@upstash/redis': ^1.20.6
+ '@upstash/vector': ^1.0.2
+ '@vercel/kv': ^0.2.3
+ '@vercel/postgres': ^0.5.0
+ '@writerai/writer-sdk': ^0.40.2
+ '@xata.io/client': ^0.28.0
+ '@xenova/transformers': ^2.5.4
+ '@zilliz/milvus2-sdk-node': '>=2.2.7'
+ better-sqlite3: ^9.4.0
+ cassandra-driver: ^4.7.2
+ chromadb: '*'
+ closevector-common: 0.1.3
+ closevector-node: 0.1.6
+ closevector-web: 0.1.6
+ cohere-ai: '*'
+ convex: ^1.3.1
+ discord.js: ^14.14.1
+ dria: ^0.0.3
+ faiss-node: ^0.5.1
+ firebase-admin: ^11.9.0 || ^12.0.0
+ google-auth-library: ^8.9.0
+ googleapis: ^126.0.1
+ hnswlib-node: ^1.4.2
+ html-to-text: ^9.0.5
+ ioredis: ^5.3.2
+ jsdom: '*'
+ llmonitor: ^0.5.9
+ lodash: ^4.17.21
+ lunary: ^0.6.11
+ mongodb: '>=5.2.0'
+ mysql2: ^3.3.3
+ neo4j-driver: '*'
+ node-llama-cpp: '*'
+ pg: ^8.11.0
+ pg-copy-streams: ^6.0.5
+ pickleparser: ^0.2.1
+ portkey-ai: ^0.1.11
+ redis: '*'
+ replicate: ^0.18.0
+ typeorm: ^0.3.12
+ typesense: ^1.5.3
+ usearch: ^1.1.1
+ vectordb: ^0.1.4
+ voy-search: 0.6.2
+ weaviate-ts-client: '*'
+ web-auth-library: ^1.0.3
+ ws: ^8.14.2
+ peerDependenciesMeta:
+ '@aws-crypto/sha256-js':
+ optional: true
+ '@aws-sdk/client-bedrock-agent-runtime':
+ optional: true
+ '@aws-sdk/client-bedrock-runtime':
+ optional: true
+ '@aws-sdk/client-dynamodb':
+ optional: true
+ '@aws-sdk/client-kendra':
+ optional: true
+ '@aws-sdk/client-lambda':
+ optional: true
+ '@aws-sdk/client-sagemaker-runtime':
+ optional: true
+ '@aws-sdk/client-sfn':
+ optional: true
+ '@aws-sdk/credential-provider-node':
+ optional: true
+ '@azure/search-documents':
+ optional: true
+ '@clickhouse/client':
+ optional: true
+ '@cloudflare/ai':
+ optional: true
+ '@datastax/astra-db-ts':
+ optional: true
+ '@elastic/elasticsearch':
+ optional: true
+ '@getmetal/metal-sdk':
+ optional: true
+ '@getzep/zep-js':
+ optional: true
+ '@gomomento/sdk':
+ optional: true
+ '@gomomento/sdk-core':
+ optional: true
+ '@google-ai/generativelanguage':
+ optional: true
+ '@gradientai/nodejs-sdk':
+ optional: true
+ '@huggingface/inference':
+ optional: true
+ '@mozilla/readability':
+ optional: true
+ '@opensearch-project/opensearch':
+ optional: true
+ '@pinecone-database/pinecone':
+ optional: true
+ '@planetscale/database':
+ optional: true
+ '@qdrant/js-client-rest':
+ optional: true
+ '@raycast/api':
+ optional: true
+ '@rockset/client':
+ optional: true
+ '@smithy/eventstream-codec':
+ optional: true
+ '@smithy/protocol-http':
+ optional: true
+ '@smithy/signature-v4':
+ optional: true
+ '@smithy/util-utf8':
+ optional: true
+ '@supabase/postgrest-js':
+ optional: true
+ '@supabase/supabase-js':
+ optional: true
+ '@tensorflow-models/universal-sentence-encoder':
+ optional: true
+ '@tensorflow/tfjs-converter':
+ optional: true
+ '@tensorflow/tfjs-core':
+ optional: true
+ '@upstash/redis':
+ optional: true
+ '@upstash/vector':
+ optional: true
+ '@vercel/kv':
+ optional: true
+ '@vercel/postgres':
+ optional: true
+ '@writerai/writer-sdk':
+ optional: true
+ '@xata.io/client':
+ optional: true
+ '@xenova/transformers':
+ optional: true
+ '@zilliz/milvus2-sdk-node':
+ optional: true
+ better-sqlite3:
+ optional: true
+ cassandra-driver:
+ optional: true
+ chromadb:
+ optional: true
+ closevector-common:
+ optional: true
+ closevector-node:
+ optional: true
+ closevector-web:
+ optional: true
+ cohere-ai:
+ optional: true
+ convex:
+ optional: true
+ discord.js:
+ optional: true
+ dria:
+ optional: true
+ faiss-node:
+ optional: true
+ firebase-admin:
+ optional: true
+ google-auth-library:
+ optional: true
+ googleapis:
+ optional: true
+ hnswlib-node:
+ optional: true
+ html-to-text:
+ optional: true
+ ioredis:
+ optional: true
+ jsdom:
+ optional: true
+ llmonitor:
+ optional: true
+ lodash:
+ optional: true
+ lunary:
+ optional: true
+ mongodb:
+ optional: true
+ mysql2:
+ optional: true
+ neo4j-driver:
+ optional: true
+ node-llama-cpp:
+ optional: true
+ pg:
+ optional: true
+ pg-copy-streams:
+ optional: true
+ pickleparser:
+ optional: true
+ portkey-ai:
+ optional: true
+ redis:
+ optional: true
+ replicate:
+ optional: true
+ typeorm:
+ optional: true
+ typesense:
+ optional: true
+ usearch:
+ optional: true
+ vectordb:
+ optional: true
+ voy-search:
+ optional: true
+ weaviate-ts-client:
+ optional: true
+ web-auth-library:
+ optional: true
+ ws:
+ optional: true
+ dependencies:
+ '@google-ai/generativelanguage': 1.1.0(encoding@0.1.13)
+ '@langchain/core': 0.1.32
+ '@langchain/openai': 0.0.14(encoding@0.1.13)
+ flat: 5.0.2
+ google-auth-library: 9.4.1(encoding@0.1.13)
+ langsmith: 0.1.3
+ lodash: 4.17.21
+ uuid: 9.0.1
+ zod: 3.22.4
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
+ /@langchain/core@0.1.32:
+ resolution: {integrity: sha512-7b8wBQMej2QxaDDS0fCQa3/zrA2raTh1RBe2h1som7QxFpWJkHSxwVwdvGUotX9SopmsY99TK54sK0amfDvBBA==}
+ engines: {node: '>=18'}
+ dependencies:
+ ansi-styles: 5.2.0
+ camelcase: 6.3.0
+ decamelize: 1.2.0
+ js-tiktoken: 1.0.10
+ langsmith: 0.1.3
+ ml-distance: 4.0.1
+ p-queue: 6.6.2
+ p-retry: 4.6.2
+ uuid: 9.0.1
+ zod: 3.22.4
+ zod-to-json-schema: 3.22.4(zod@3.22.4)
+ dev: false
+
+ /@langchain/openai@0.0.14(encoding@0.1.13):
+ resolution: {integrity: sha512-co6nRylPrLGY/C3JYxhHt6cxLq07P086O7K3QaZH7SFFErIN9wSzJonpvhZR07DEUq6eK6wKgh2ORxA/NcjSRQ==}
+ engines: {node: '>=18'}
+ dependencies:
+ '@langchain/core': 0.1.32
+ js-tiktoken: 1.0.10
+ openai: 4.27.0(encoding@0.1.13)
+ zod: 3.22.4
+ zod-to-json-schema: 3.22.4(zod@3.22.4)
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
/@lumino/algorithm@1.9.2:
resolution: {integrity: sha512-Z06lp/yuhz8CtIir3PNTGnuk7909eXt4ukJsCzChsGuot2l5Fbs96RJ/FOHgwCedaX74CtxPjXHXoszFbUA+4A==}
dev: false
@@ -5498,6 +5802,10 @@ packages:
'@types/node': 18.18.13
dev: false
+ /@types/retry@0.12.0:
+ resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
+ dev: false
+
/@types/sanitize-html@2.8.0:
resolution: {integrity: sha512-Uih6caOm3DsBYnVGOYn0A9NoTNe1c4aPStmHC/YA2JrpP9kx//jzaRcIklFvSpvVQEcpl/ZCr4DgISSf/YxTvg==}
dependencies:
@@ -5552,6 +5860,10 @@ packages:
/@types/uuid@8.3.4:
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
+ /@types/uuid@9.0.8:
+ resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
+ dev: false
+
/@types/xml-crypto@1.4.3:
resolution: {integrity: sha512-pnvKYb7vUsUIMc+C6JM/j779YWQgOMcwjnqHJ9cdaWXwWEBE1hAqthzeszRx62V5RWMvS+XS9w9tXMOYyUc8zg==}
dependencies:
@@ -6143,7 +6455,6 @@ packages:
/ansi-styles@5.2.0:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
- dev: true
/ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
@@ -6751,6 +7062,10 @@ packages:
resolution: {integrity: sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==}
dev: false
+ /binary-search@1.3.6:
+ resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==}
+ dev: false
+
/bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
dependencies:
@@ -7018,7 +7333,6 @@ packages:
/camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
- dev: true
/caniuse-lite@1.0.30001564:
resolution: {integrity: sha512-DqAOf+rhof+6GVx1y+xzbFPeOumfQnhYzVnZD6LAXijR77yPtm9mfOcqOnT3mpnJiZVT+kwLAFnRlZcIz+c6bg==}
@@ -7488,7 +7802,6 @@ packages:
/commander@10.0.1:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'}
- dev: true
/commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -7698,7 +8011,6 @@ packages:
loose-envify: 1.4.0
object-assign: 4.1.1
dev: false
- bundledDependencies: false
/create-server@1.0.2:
resolution: {integrity: sha512-hie+Kyero+jxt6dwKhLKtN23qSNiMn8mNIEjTjwzaZwH2y4tr4nYloeFrpadqV+ZqV9jQ15t3AKotaK8dOo45w==}
@@ -9408,7 +9720,6 @@ packages:
/flat@5.0.2:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
- dev: true
/flatted@3.2.7:
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
@@ -10726,6 +11037,10 @@ packages:
is-decimal: 2.0.1
dev: false
+ /is-any-array@2.0.1:
+ resolution: {integrity: sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==}
+ dev: false
+
/is-arguments@1.1.1:
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
engines: {node: '>= 0.4'}
@@ -12238,6 +12553,12 @@ packages:
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
dev: false
+ /js-tiktoken@1.0.10:
+ resolution: {integrity: sha512-ZoSxbGjvGyMT13x6ACo9ebhDha/0FHdKA+OsQcMOWcm1Zs7r90Rhk5lhERLzji+3rA7EKpXCgwXcM5fF3DMpdA==}
+ dependencies:
+ base64-js: 1.5.1
+ dev: false
+
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -12509,6 +12830,17 @@ packages:
resolution: {integrity: sha512-v4pxOBEQVN1WBTfB1crhTtxzNLZU9HPWgadlwzWKISJtt6Ku/CnpBrwVy+jFv8StjxsPfwPFzO0CMwdZLJ0/BA==}
dev: false
+ /langsmith@0.1.3:
+ resolution: {integrity: sha512-kQMS3QySeU0Qt9A71d9trUXbeKn33HfxpRc7hRjSB967zcdTAngh66NcqYqBflD3nOL4FK6LKmvfb3vbNDEoPg==}
+ hasBin: true
+ dependencies:
+ '@types/uuid': 9.0.8
+ commander: 10.0.1
+ p-queue: 6.6.2
+ p-retry: 4.6.2
+ uuid: 9.0.1
+ dev: false
+
/ldap-filter@0.3.3:
resolution: {integrity: sha512-/tFkx5WIn4HuO+6w9lsfxq4FN3O+fDZeO9Mek8dCD8rTUpqzRa766BOBO7BcGkn3X86m5+cBm1/2S/Shzz7gMg==}
engines: {node: '>=0.8'}
@@ -13186,6 +13518,37 @@ packages:
resolution: {integrity: sha512-bauHShmaxVQiEvlrAPWxSPn8spSL8gDVRl11r8vLT4r/KdnknLqtqwQbToZ2Oa8sJkExYY1z6/d+X7pNiqo4yg==}
dev: true
+ /ml-array-mean@1.1.6:
+ resolution: {integrity: sha512-MIdf7Zc8HznwIisyiJGRH9tRigg3Yf4FldW8DxKxpCCv/g5CafTw0RRu51nojVEOXuCQC7DRVVu5c7XXO/5joQ==}
+ dependencies:
+ ml-array-sum: 1.1.6
+ dev: false
+
+ /ml-array-sum@1.1.6:
+ resolution: {integrity: sha512-29mAh2GwH7ZmiRnup4UyibQZB9+ZLyMShvt4cH4eTK+cL2oEMIZFnSyB3SS8MlsTh6q/w/yh48KmqLxmovN4Dw==}
+ dependencies:
+ is-any-array: 2.0.1
+ dev: false
+
+ /ml-distance-euclidean@2.0.0:
+ resolution: {integrity: sha512-yC9/2o8QF0A3m/0IXqCTXCzz2pNEzvmcE/9HFKOZGnTjatvBbsn4lWYJkxENkA4Ug2fnYl7PXQxnPi21sgMy/Q==}
+ dev: false
+
+ /ml-distance@4.0.1:
+ resolution: {integrity: sha512-feZ5ziXs01zhyFUUUeZV5hwc0f5JW0Sh0ckU1koZe/wdVkJdGxcP06KNQuF0WBTj8FttQUzcvQcpcrOp/XrlEw==}
+ dependencies:
+ ml-array-mean: 1.1.6
+ ml-distance-euclidean: 2.0.0
+ ml-tree-similarity: 1.0.0
+ dev: false
+
+ /ml-tree-similarity@1.0.0:
+ resolution: {integrity: sha512-XJUyYqjSuUQkNQHMscr6tcjldsOoAekxADTplt40QKfwW6nd++1wHWV9AArl0Zvw/TIHgNaZZNvr8QGvE8wLRg==}
+ dependencies:
+ binary-search: 1.3.6
+ num-sort: 2.1.0
+ dev: false
+
/mocha@10.2.0:
resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==}
engines: {node: '>= 14.0.0'}
@@ -13693,6 +14056,11 @@ packages:
dependencies:
boolbase: 1.0.0
+ /num-sort@2.1.0:
+ resolution: {integrity: sha512-1MQz1Ed8z2yckoBeSfkQHHO9K1yDRxxtotKSJ9yvcTUUxSvfvzEq5GwBrjjHEpMlq/k5gvXdmJ1SbYxWtpNoVg==}
+ engines: {node: '>=8'}
+ dev: false
+
/number-is-integer@1.0.1:
resolution: {integrity: sha512-Dq3iuiFBkrbmuQjGFFF3zckXNCQoSD37/SdSbgcBailUx6knDvDwb5CympBgcoWHy36sfS12u74MHYkXyHq6bg==}
engines: {node: '>=0.10.0'}
@@ -13943,6 +14311,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /p-finally@1.0.0:
+ resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
+ engines: {node: '>=4'}
+ dev: false
+
/p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
@@ -13980,6 +14353,29 @@ packages:
dependencies:
aggregate-error: 3.1.0
+ /p-queue@6.6.2:
+ resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ eventemitter3: 4.0.7
+ p-timeout: 3.2.0
+ dev: false
+
+ /p-retry@4.6.2:
+ resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@types/retry': 0.12.0
+ retry: 0.13.1
+ dev: false
+
+ /p-timeout@3.2.0:
+ resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
+ engines: {node: '>=8'}
+ dependencies:
+ p-finally: 1.0.0
+ dev: false
+
/p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
@@ -16302,6 +16698,11 @@ packages:
- supports-color
dev: false
+ /retry@0.13.1:
+ resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
+ engines: {node: '>= 4'}
+ dev: false
+
/reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -19079,10 +19480,22 @@ packages:
resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==}
dev: false
+ /zod-to-json-schema@3.22.4(zod@3.22.4):
+ resolution: {integrity: sha512-2Ed5dJ+n/O3cU383xSY28cuVi0BCQhF8nYqWU5paEpl7fVdqdAmiLdqLyfblbNdfOFwFfi/mqU4O1pwc60iBhQ==}
+ peerDependencies:
+ zod: ^3.22.4
+ dependencies:
+ zod: 3.22.4
+ dev: false
+
/zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
dev: false
+ /zod@3.22.4:
+ resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
+ dev: false
+
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: false
diff --git a/src/packages/server/llm/client.ts b/src/packages/server/llm/client.ts
index d6f2ba86c03..1f50dab0c0f 100644
--- a/src/packages/server/llm/client.ts
+++ b/src/packages/server/llm/client.ts
@@ -11,12 +11,13 @@ import { getServerSettings } from "@cocalc/database/settings/server-settings";
import { LanguageModel, model2vendor } from "@cocalc/util/db-schema/openai";
import { unreachable } from "@cocalc/util/misc";
import { VertexAIClient } from "./vertex-ai-client";
+import { Ollama } from "@langchain/community/llms/ollama";
const log = getLogger("llm:client");
const clientCache: { [key: string]: OpenAI | VertexAIClient } = {};
-export default async function getClient(
+export async function getClient(
model?: LanguageModel,
): Promise {
const vendor = model == null ? "openai" : model2vendor(model);
@@ -56,8 +57,40 @@ export default async function getClient(
clientCache[key] = vai;
return vai;
+ case "ollama":
+ throw new Error("Use the getOllama function instead");
+
default:
unreachable(vendor);
throw new Error(`unknown vendor: ${vendor}`);
}
}
+
+const ollamaCache: { [key: string]: Ollama } = {};
+
+export async function getOllama(model: string) {
+ // model is the unique key in the ServerSettings.ollama_configuration mapping
+ if (ollamaCache[model]) {
+ return ollamaCache[model];
+ }
+
+ const settings = await getServerSettings();
+ const config = settings.ollama_configuration?.[model];
+ if (!config) {
+ throw new Error(
+ `Ollama model ${model} not configured – you have to create an entry {${model}: {url: "https://...", ...}} in the "Ollama Configuration" entry of the server settings`,
+ );
+ }
+
+ const baseUrl = config.url;
+
+ if (!baseUrl) {
+ throw new Error(`The url of the Ollama model ${model} is not configured`);
+ }
+
+ const keepAlive = config.keepAlive ?? -1;
+
+ const client = new Ollama({ baseUrl, model, keepAlive });
+ ollamaCache[model] = client;
+ return client;
+}
diff --git a/src/packages/server/llm/index.ts b/src/packages/server/llm/index.ts
index 6708288c99c..8f2800d53e2 100644
--- a/src/packages/server/llm/index.ts
+++ b/src/packages/server/llm/index.ts
@@ -31,9 +31,10 @@ import {
import { ChatOptions, ChatOutput, History } from "@cocalc/util/types/llm";
import { checkForAbuse } from "./abuse";
import { callChatGPTAPI } from "./call-chatgpt";
-import getClient from "./client";
+import { getClient } from "./client";
import { saveResponse } from "./save-response";
import { VertexAIClient } from "./vertex-ai-client";
+import { evaluateOllama } from "./ollama";
const log = getLogger("llm");
@@ -59,38 +60,6 @@ export async function evaluate(opts: ChatOptions): Promise {
}
}
-async function evaluteCall({
- system,
- history,
- input,
- client,
- model,
- maxTokens,
- stream,
-}) {
- if (client instanceof VertexAIClient) {
- return await evaluateVertexAI({
- system,
- history,
- input,
- client,
- maxTokens,
- model,
- stream,
- });
- }
-
- return await evaluateOpenAI({
- system,
- history,
- input,
- client,
- model,
- maxTokens,
- stream,
- });
-}
-
async function evaluateImpl({
input,
system,
@@ -104,7 +73,7 @@ async function evaluateImpl({
stream,
maxTokens,
}: ChatOptions): Promise {
- log.debug("evaluate", {
+ log.debug("evaluateImpl", {
input,
history,
system,
@@ -124,15 +93,28 @@ async function evaluateImpl({
const client = await getClient(model);
const { output, total_tokens, prompt_tokens, completion_tokens } =
- await evaluteCall({
- system,
- history,
- input,
- client,
- model,
- maxTokens,
- stream,
- });
+ await (async () => {
+ if (model.startsWith("ollama-")) {
+ return await evaluateOllama({
+ system,
+ history,
+ input,
+ model,
+ maxTokens,
+ stream,
+ });
+ } else {
+ return await evaluteCall({
+ system,
+ history,
+ input,
+ client,
+ model,
+ maxTokens,
+ stream,
+ });
+ }
+ })();
log.debug("response: ", { output, total_tokens, prompt_tokens });
const total_time_s = (Date.now() - start) / 1000;
@@ -192,6 +174,38 @@ async function evaluateImpl({
return output;
}
+async function evaluteCall({
+ system,
+ history,
+ input,
+ client,
+ model,
+ maxTokens,
+ stream,
+}) {
+ if (client instanceof VertexAIClient) {
+ return await evaluateVertexAI({
+ system,
+ history,
+ input,
+ client,
+ maxTokens,
+ model,
+ stream,
+ });
+ }
+
+ return await evaluateOpenAI({
+ system,
+ history,
+ input,
+ client,
+ model,
+ maxTokens,
+ stream,
+ });
+}
+
interface EvalVertexAIProps {
client: VertexAIClient;
system?: string;
diff --git a/src/packages/server/llm/ollama.ts b/src/packages/server/llm/ollama.ts
new file mode 100644
index 00000000000..91ad6317f2e
--- /dev/null
+++ b/src/packages/server/llm/ollama.ts
@@ -0,0 +1,56 @@
+import getLogger from "@cocalc/backend/logger";
+import { ChatOutput, History } from "@cocalc/util/types/llm";
+import { getOllama } from "./client";
+
+const log = getLogger("llm:ollama");
+
+// subset of ChatOptions, but model is a string
+interface OllamaOpts {
+ input: string; // new input that user types
+ system?: string; // extra setup that we add for relevance and context
+ history?: History;
+ model: string; // this must be ollama-[model]
+ stream?: (output?: string) => void;
+ maxTokens?: number;
+}
+
+export async function evaluateOllama(
+ opts: Readonly,
+): Promise {
+ if (!opts.model.startsWith("ollama-")) {
+ throw new Error(`model ${opts.model} not supported`);
+ }
+ const model = opts.model.slice("ollama-".length);
+ const { system, history, input, maxTokens, stream } = opts;
+ log.debug("evaluateOllama", {
+ input,
+ history,
+ system,
+ model,
+ stream: stream != null,
+ maxTokens,
+ });
+
+ const ollama = await getOllama(model);
+
+ const chunks = await ollama.stream(input);
+
+ let output = "";
+ for await (const chunk of chunks) {
+ output += chunk;
+ opts.stream?.(chunk);
+ }
+
+ // and an empty call when done
+ opts.stream?.();
+
+ const prompt_tokens = 10;
+ const completion_tokens = 10;
+
+ return {
+ output,
+ total_tokens: prompt_tokens + completion_tokens,
+ completion_tokens,
+ prompt_tokens,
+ };
+}
diff --git a/src/packages/server/package.json b/src/packages/server/package.json
index 9f8d42b723f..a167fa941aa 100644
--- a/src/packages/server/package.json
+++ b/src/packages/server/package.json
@@ -46,6 +46,7 @@
"@google-cloud/monitoring": "^4.0.0",
"@google/generative-ai": "^0.1.3",
"@isaacs/ttlcache": "^1.2.1",
+ "@langchain/community": "^0.0.32",
"@node-saml/passport-saml": "^4.0.4",
"@passport-js/passport-twitter": "^1.0.8",
"@passport-next/passport-google-oauth2": "^1.0.0",
diff --git a/src/packages/util/db-schema/openai.ts b/src/packages/util/db-schema/openai.ts
index 83181e061aa..372f47be07b 100644
--- a/src/packages/util/db-schema/openai.ts
+++ b/src/packages/util/db-schema/openai.ts
@@ -72,7 +72,7 @@ export type LanguageService =
| "google-embedding-gecko-001"
| "google-gemini-pro";
-const LANGUAGE_MODEL_VENDORS = ["openai", "google"] as const;
+const LANGUAGE_MODEL_VENDORS = ["openai", "google", "ollama"] as const;
export type Vendor = (typeof LANGUAGE_MODEL_VENDORS)[number];
// used e.g. for checking "account-id={string}" and other things like that
@@ -122,6 +122,8 @@ export const DEFAULT_MODEL: LanguageModel = "gpt-3.5-turbo";
export function model2vendor(model: LanguageModel): Vendor {
if (model.startsWith("gpt-")) {
return "openai";
+ } else if (model.startsWith("ollama-")) {
+ return "ollama";
} else {
return "google";
}
@@ -193,6 +195,8 @@ export function getVendorStatusCheckMD(vendor: Vendor): string {
return `OpenAI [status](https://status.openai.com) and [downdetector](https://downdetector.com/status/openai).`;
case "google":
return `Google [status](https://status.cloud.google.com) and [downdetector](https://downdetector.com/status/google-cloud).`;
+ case "ollama":
+ return `No status information for Ollama available – you have to check with the particular backend for the model.`;
default:
unreachable(vendor);
}
@@ -266,8 +270,10 @@ const LLM_COST: { [name in LanguageModel]: Cost } = {
},
} as const;
-export function isValidModel(model?: Model) {
- return model != null && LLM_COST[model ?? ""] != null;
+export function isValidModel(model?: string): boolean {
+ if (model == null) return false;
+ if (model.startsWith("ollama-")) return true;
+ return LLM_COST[model ?? ""] != null;
}
export function getMaxTokens(model?: Model): number {
diff --git a/src/packages/util/db-schema/site-defaults.ts b/src/packages/util/db-schema/site-defaults.ts
index 57a196bd730..b976ef9464b 100644
--- a/src/packages/util/db-schema/site-defaults.ts
+++ b/src/packages/util/db-schema/site-defaults.ts
@@ -26,6 +26,7 @@ export type SiteSettingsKeys =
| "policies"
| "openai_enabled"
| "google_vertexai_enabled"
+ | "ollama_enabled"
| "neural_search_enabled"
| "jupyter_api_enabled"
| "organization_name"
@@ -595,6 +596,13 @@ export const site_settings_conf: SiteSettings = {
valid: only_booleans,
to_val: to_bool,
},
+ ollama_enabled: {
+ name: "Ollama LLM UI",
+ desc: "Controls visibility of UI elements related to Ollama integration. To make this actually work, configure the list of API/model endpoints in the Ollama configuration.",
+ default: "no",
+ valid: only_booleans,
+ to_val: to_bool,
+ },
neural_search_enabled: {
name: "OpenAI Neural Search UI",
desc: "Controls visibility of UI elements related to Neural Search integration. You must **also set your OpenAI API key** below and fully configure the **Qdrant vector database** for neural search to work.",
diff --git a/src/packages/util/db-schema/site-settings-extras.ts b/src/packages/util/db-schema/site-settings-extras.ts
index 692c0942c13..b338ae55074 100644
--- a/src/packages/util/db-schema/site-settings-extras.ts
+++ b/src/packages/util/db-schema/site-settings-extras.ts
@@ -67,8 +67,9 @@ const pii_retention_display = (retention: string) => {
const openai_enabled = (conf: SiteSettings) => to_bool(conf.openai_enabled);
const vertexai_enabled = (conf: SiteSettings) =>
to_bool(conf.google_vertexai_enabled);
+const ollama_enabled = (conf: SiteSettings) => to_bool(conf.ollama_enabled);
const any_llm_enabled = (conf: SiteSettings) =>
- openai_enabled(conf) || vertexai_enabled(conf);
+ openai_enabled(conf) || vertexai_enabled(conf) || ollama_enabled(conf);
const compute_servers_enabled = (conf: SiteSettings) =>
to_bool(conf.compute_servers_enabled);
@@ -104,6 +105,7 @@ export type SiteSettingsExtrasKeys =
| "openai_section"
| "openai_api_key"
| "google_vertexai_key"
+ | "ollama_configuration"
| "qdrant_section"
| "qdrant_api_key"
| "qdrant_cluster_url"
@@ -180,6 +182,15 @@ export const EXTRAS: SettingsExtras = {
password: true,
show: vertexai_enabled,
},
+ ollama_configuration: {
+ name: "Ollama Configuration",
+ desc: "This is the configuration for the Ollama LLM API endpoints.",
+ default: "",
+ multiline: 5,
+ show: ollama_enabled,
+ to_val: from_json,
+ valid: parsableJson,
+ },
qdrant_section: {
name: "Qdrant Configuration",
desc: "",