mirror of
https://github.com/Canner/WrenAI.git
synced 2026-09-24 23:29:49 +08:00
minor update for supporting 3rd party Open AI APIs + add Ollama (#376)
This commit is contained in:
@@ -52,7 +52,7 @@ jobs:
|
||||
LLM_PROVIDER: openai
|
||||
DOCUMENT_STORE_PROVIDER: qdrant
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_GENERATION_MODEL: gpt-3.5-turbo
|
||||
GENERATION_MODEL: gpt-3.5-turbo
|
||||
WREN_ENGINE_ENDPOINT: http://localhost:8080
|
||||
WREN_UI_ENDPOINT: http://localhost:3000
|
||||
QDRANT_HOST: localhost
|
||||
|
||||
@@ -72,6 +72,7 @@ yarn-error.log*
|
||||
|
||||
## local env files
|
||||
.env*.local
|
||||
.env.ai
|
||||
|
||||
## vercel
|
||||
.vercel
|
||||
|
||||
@@ -19,7 +19,7 @@ data:
|
||||
WREN_UI_VERSION: "0.8.0"
|
||||
|
||||
# OpenAI
|
||||
OPENAI_GENERATION_MODEL: "gpt-3.5-turbo"
|
||||
GENERATION_MODEL: "gpt-3.5-turbo"
|
||||
|
||||
# Telemetry
|
||||
POSTHOG_HOST: "https://app.posthog.com"
|
||||
|
||||
@@ -36,11 +36,11 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: wren-config
|
||||
key: OPENAI_API_BASE
|
||||
- name: OPENAI_GENERATION_MODEL
|
||||
valueFrom:
|
||||
- name: GENERATION_MODEL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: wren-config
|
||||
key: OPENAI_GENERATION_MODEL
|
||||
key: GENERATION_MODEL
|
||||
- name: QDRANT_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
||||
@@ -44,11 +44,11 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: wren-config
|
||||
key: WREN_AI_ENDPOINT
|
||||
- name: OPENAI_GENERATION_MODEL
|
||||
- name: GENERATION_MODEL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: wren-config
|
||||
key: OPENAI_GENERATION_MODEL
|
||||
key: GENERATION_MODEL
|
||||
- name: PG_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
WREN_UI_VERSION: "0.8.0"
|
||||
|
||||
# OpenAI
|
||||
OPENAI_GENERATION_MODEL: "gpt-3.5-turbo"
|
||||
GENERATION_MODEL: "gpt-3.5-turbo"
|
||||
|
||||
# Telemetry
|
||||
POSTHOG_HOST: "https://app.posthog.com"
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
## LLM
|
||||
LLM_PROVIDER= # openai, azure-openai, ollama
|
||||
|
||||
# openai or openai-api-compatible llm
|
||||
OPENAI_API_KEY=
|
||||
OPENAI_API_BASE=
|
||||
|
||||
# azure-openai
|
||||
AZURE_CHAT_BASE=
|
||||
AZURE_CHAT_KEY=
|
||||
AZURE_CHAT_VERSION=
|
||||
|
||||
AZURE_EMBED_BASE=
|
||||
AZURE_EMBED_KEY=
|
||||
AZURE_EMBED_VERSION=
|
||||
|
||||
# ollama
|
||||
OLLAMA_URL=http://host.docker.internal:11434
|
||||
|
||||
GENERATION_MODEL=
|
||||
EMBEDDING_MODEL=
|
||||
EMBEDDING_MODEL_DIMENSION=
|
||||
|
||||
|
||||
## DOCUMENT_STORE
|
||||
DOCUMENT_STORE_PROVIDER=qdrant
|
||||
|
||||
QDRANT_HOST=qdrant
|
||||
+4
-17
@@ -11,6 +11,10 @@ IBIS_SERVER_PORT=8000
|
||||
# service endpoint (for docker-compose-dev.yaml file)
|
||||
WREN_UI_ENDPOINT=http://docker.for.mac.localhost:3000
|
||||
|
||||
# LLM
|
||||
OPENAI_API_KEY=
|
||||
GENERATION_MODEL=gpt-3.5-turbo # gpt-3.5-turbo, gpt-4o, gpt-4-turbo
|
||||
|
||||
# version
|
||||
# CHANGE THIS TO THE LATEST VERSION
|
||||
WREN_PRODUCT_VERSION=0.5.0
|
||||
@@ -20,23 +24,6 @@ WREN_UI_VERSION=0.8.0
|
||||
IBIS_SERVER_VERSION=0.5.1
|
||||
WREN_BOOTSTRAP_VERSION=0.1.4
|
||||
|
||||
# keys
|
||||
LLM_PROVIDER=openai
|
||||
DOCUMENT_STORE_PROVIDER=qdrant
|
||||
# CHANGE THIS TO YOUR OPENAI API KEY
|
||||
OPENAI_API_KEY=sk-1234567890
|
||||
OPENAI_API_BASE=https://api.openai.com/v1
|
||||
OPENAI_GENERATION_MODEL=gpt-3.5-turbo
|
||||
|
||||
# Azure env
|
||||
AZURE_CHAT_BASE=
|
||||
AZURE_CHAT_KEY=
|
||||
AZURE_CHAT_VERSION=
|
||||
|
||||
AZURE_EMBED_BASE=
|
||||
AZURE_EMBED_KEY=
|
||||
AZURE_EMBED_VERSION=
|
||||
|
||||
# AI service related env variables
|
||||
AI_SERVICE_ENABLE_TIMER=
|
||||
AI_SERVICE_LOGGING_LEVEL=INFO
|
||||
|
||||
+8
-2
@@ -20,5 +20,11 @@ Path structure as following:
|
||||
|
||||
## How to start
|
||||
1. copy `.env.example` to `.env.local` and modify the OpenAI API key.
|
||||
1. (optional) if your port 3000 is occupied, you can modify the `HOST_PORT` in `.env.local`.
|
||||
1. run `docker-compose --env-file .env.local up` to start all services.
|
||||
2. (optional) copy `.env.ai.example` to `.env.ai` and fill in necessary information if you would like to use custom LLM.
|
||||
3. (optional) if your port 3000 is occupied, you can modify the `HOST_PORT` in `.env.example`.
|
||||
4. start all services:
|
||||
- using OpenAI: `docker-compose --env-file .env.local up -d`
|
||||
- using custom LLM: `docker-compose --env-file .env.local --env-file .env.ai up -d`
|
||||
5. stop all services:
|
||||
- using OpenAI: `docker-compose --env-file .env.local down`
|
||||
- using custom LLM: `docker-compose --env-file .env.local --env-file .env.ai down`
|
||||
@@ -42,13 +42,9 @@ services:
|
||||
ports:
|
||||
- ${AI_SERVICE_FORWARD_PORT}:${WREN_AI_SERVICE_PORT}
|
||||
environment:
|
||||
WREN_AI_SERVICE_PORT: ${WREN_AI_SERVICE_PORT}
|
||||
LLM_PROVIDER: ${LLM_PROVIDER}
|
||||
DOCUMENT_STORE_PROVIDER: ${DOCUMENT_STORE_PROVIDER}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
OPENAI_GENERATION_MODEL: ${OPENAI_GENERATION_MODEL}
|
||||
QDRANT_HOST: qdrant
|
||||
WREN_UI_ENDPOINT: ${WREN_UI_ENDPOINT}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
GENERATION_MODEL: ${GENERATION_MODEL}
|
||||
ENABLE_TIMER: ${AI_SERVICE_ENABLE_TIMER}
|
||||
LOGGING_LEVEL: ${AI_SERVICE_LOGGING_LEVEL}
|
||||
# sometimes the console won't show print messages,
|
||||
|
||||
@@ -55,13 +55,9 @@ services:
|
||||
- ${AI_SERVICE_FORWARD_PORT}:${WREN_AI_SERVICE_PORT}
|
||||
environment:
|
||||
WREN_AI_SERVICE_PORT: ${WREN_AI_SERVICE_PORT}
|
||||
LLM_PROVIDER: ${LLM_PROVIDER}
|
||||
DOCUMENT_STORE_PROVIDER: ${DOCUMENT_STORE_PROVIDER}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
OPENAI_API_BASE: ${OPENAI_API_BASE}
|
||||
OPENAI_GENERATION_MODEL: ${OPENAI_GENERATION_MODEL}
|
||||
QDRANT_HOST: qdrant
|
||||
WREN_UI_ENDPOINT: http://wren-ui:${WREN_UI_PORT}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
GENERATION_MODEL: ${GENERATION_MODEL}
|
||||
ENABLE_TIMER: ${AI_SERVICE_ENABLE_TIMER}
|
||||
LOGGING_LEVEL: ${AI_SERVICE_LOGGING_LEVEL}
|
||||
# sometimes the console won't show print messages,
|
||||
@@ -95,7 +91,9 @@ services:
|
||||
WREN_ENGINE_ENDPOINT: http://wren-engine:${WREN_ENGINE_PORT}
|
||||
WREN_AI_ENDPOINT: http://wren-ai-service:${WREN_AI_SERVICE_PORT}
|
||||
IBIS_SERVER_ENDPOINT: http://ibis-server:${IBIS_SERVER_PORT}
|
||||
OPENAI_GENERATION_MODEL: ${OPENAI_GENERATION_MODEL}
|
||||
EMBEDDING_MODEL: ${EMBEDDING_MODEL}
|
||||
EMBEDDING_MODEL_DIM: ${EMBEDDING_MODEL_DIM}
|
||||
GENERATION_MODEL: ${GENERATION_MODEL}
|
||||
PG_USERNAME: ${PG_USERNAME}
|
||||
PG_PASSWORD: ${PG_PASSWORD}
|
||||
# telemetry
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
# fastapi related
|
||||
# app related
|
||||
WREN_AI_SERVICE_HOST=127.0.0.1
|
||||
WREN_AI_SERVICE_PORT=5556
|
||||
WREN_ENGINE_ENDPOINT=http://localhost:8080
|
||||
WREN_UI_ENDPOINT=http://localhost:3000
|
||||
|
||||
# app related
|
||||
# LLM Provider name should be mapped to Haystack's supported LLM providers: https://docs.haystack.deepset.ai/v2.0/docs/generators
|
||||
LLM_PROVIDER=openai # azure_openai as well
|
||||
## LLM
|
||||
LLM_PROVIDER=openai # openai, azure-openai, ollama
|
||||
|
||||
|
||||
# Document Store Provider name should be mapped to Haystack's supported Document Store providers: see the haystack documentation's left sidebar
|
||||
DOCUMENT_STORE_PROVIDER=qdrant
|
||||
|
||||
# llm provider specific env variables names must be in the format of [LLM_PROVIDER]_[ENV_VARIABLE_NAME] and must be in uppercase
|
||||
OPENAI_API_KEY=
|
||||
# openai or openai-api-compatible llm
|
||||
OPENAI_API_KEY=sk-1234567890
|
||||
OPENAI_API_BASE=https://api.openai.com/v1
|
||||
OPENAI_GENERATION_MODEL=gpt-3.5-turbo # gpt-4o, gpt-4-turbo, gpt-3.5-turbo
|
||||
|
||||
#Azure openai env
|
||||
# azure-openai
|
||||
AZURE_CHAT_BASE=
|
||||
AZURE_CHAT_KEY=
|
||||
AZURE_CHAT_VERSION=
|
||||
@@ -24,8 +20,17 @@ AZURE_EMBED_BASE=
|
||||
AZURE_EMBED_KEY=
|
||||
AZURE_EMBED_VERSION=
|
||||
|
||||
# document store provider specific env variables names must be in the format of [DOCUMENT_STORE_PROVIDER]_[ENV_VARIABLE_NAME] and must be in uppercase
|
||||
QDRANT_HOST=localhost
|
||||
# ollama
|
||||
OLLAMA_URL=http://localhost:11434
|
||||
|
||||
GENERATION_MODEL=gpt-3.5-turbo
|
||||
EMBEDDING_MODEL=text-embedding-3-large
|
||||
EMBEDDING_MODEL_DIMENSION=3072
|
||||
|
||||
## DOCUMENT_STORE
|
||||
DOCUMENT_STORE_PROVIDER=qdrant
|
||||
|
||||
QDRANT_HOST=http://localhost:6333
|
||||
|
||||
ENGINE=wren-ui
|
||||
|
||||
@@ -43,3 +48,8 @@ DATASET_NAME=book_2
|
||||
|
||||
ENABLE_TIMER=
|
||||
LOGGING_LEVEL=INFO
|
||||
|
||||
LANGFUSE_ENABLE=
|
||||
LANGFUSE_SECRET_KEY=
|
||||
LANGFUSE_PUBLIC_KEY=
|
||||
LANGFUSE_HOST=https://cloud.langfuse.com
|
||||
@@ -20,7 +20,9 @@ DOCUMENT_STORE_PROVIDER=qdrant
|
||||
# llm provider specific env variables names must be in the format of [LLM_PROVIDER]_[ENV_VARIABLE_NAME] and must be in uppercase
|
||||
OPENAI_API_KEY=
|
||||
OPENAI_API_BASE=https://api.openai.com/v1
|
||||
OPENAI_GENERATION_MODEL=gpt-3.5-turbo # gpt-4o, gpt-4-turbo, gpt-3.5-turbo
|
||||
EMBEDDING_MODEL=text-embedding-3-large
|
||||
EMBEDDING_MODEL_DIMENSION=3072
|
||||
GENERATION_MODEL=gpt-3.5-turbo # gpt-4o, gpt-4-turbo, gpt-3.5-turbo
|
||||
|
||||
#Azure openai env
|
||||
AZURE_CHAT_BASE=
|
||||
|
||||
@@ -14,7 +14,7 @@ services:
|
||||
WREN_AI_SERVICE_PORT: ${WREN_AI_SERVICE_PORT}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
OPENAI_API_BASE: ${OPENAI_API_BASE}
|
||||
OPENAI_GENERATION_MODEL: ${OPENAI_GENERATION_MODEL}
|
||||
GENERATION_MODEL: ${GENERATION_MODEL}
|
||||
QDRANT_HOST: ${QDRANT_HOST}
|
||||
WREN_UI_ENDPOINT: ${WREN_UI_ENDPOINT}
|
||||
ENABLE_TIMER: ${ENABLE_TIMER}
|
||||
|
||||
Generated
+100
-84
@@ -1751,22 +1751,22 @@ tenacity = ">=8.1.0,<9.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.2.7"
|
||||
version = "0.2.9"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langchain_core-0.2.7-py3-none-any.whl", hash = "sha256:fd02e153c898486dd728d634684ffc64bc257ff2ba443dc7e53d017ac0bf4658"},
|
||||
{file = "langchain_core-0.2.7.tar.gz", hash = "sha256:b0b1b6dfbdedb39426fcb8bd3f07e40eec7964856e3fc384c420ca6dba61b34e"},
|
||||
{file = "langchain_core-0.2.9-py3-none-any.whl", hash = "sha256:426a5a4fea95a5db995ba5ab560b76edd4998fb6fe52ccc28ac987092a4cbfcd"},
|
||||
{file = "langchain_core-0.2.9.tar.gz", hash = "sha256:f1c59082642921727844e1cd0eb36d451edd1872c20e193aa3142aac03495986"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
jsonpatch = ">=1.33,<2.0"
|
||||
langsmith = ">=0.1.75,<0.2.0"
|
||||
packaging = ">=23.2,<25"
|
||||
pydantic = ">=1,<3"
|
||||
pydantic = {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}
|
||||
PyYAML = ">=5.3"
|
||||
tenacity = ">=8.1.0,<9.0.0"
|
||||
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-openai"
|
||||
@@ -1803,13 +1803,13 @@ extended-testing = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "lxml (>=4.9.3,<6.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "langsmith"
|
||||
version = "0.1.77"
|
||||
version = "0.1.80"
|
||||
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langsmith-0.1.77-py3-none-any.whl", hash = "sha256:2202cc21b1ed7e7b9e5d2af2694be28898afa048c09fdf09f620cbd9301755ae"},
|
||||
{file = "langsmith-0.1.77.tar.gz", hash = "sha256:4ace09077a9a4e412afeb4b517ca68e7de7b07f36e4792dc8236ac5207c0c0c7"},
|
||||
{file = "langsmith-0.1.80-py3-none-any.whl", hash = "sha256:951fc29576b52afd8378d41f6db343090fea863e3620f0ca97e83b221f93c94d"},
|
||||
{file = "langsmith-0.1.80.tar.gz", hash = "sha256:a29b1dde27612308beee424f1388ad844c8e7e375bf2ac8bdf4da174013f279d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2287,6 +2287,21 @@ files = [
|
||||
{file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ollama-haystack"
|
||||
version = "0.0.6"
|
||||
description = "An integration between the Ollama LLM framework and Haystack"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "ollama_haystack-0.0.6-py3-none-any.whl", hash = "sha256:a1bc20367e7da76485a9997c53d1898176819505038633a4ef38344c116f13c0"},
|
||||
{file = "ollama_haystack-0.0.6.tar.gz", hash = "sha256:12609538bc0c61d624a8314390c717c00c6c07373714e0f09420b97b1e91e23c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
haystack-ai = "*"
|
||||
requests = "*"
|
||||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "1.30.1"
|
||||
@@ -2644,27 +2659,28 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "5.9.8"
|
||||
version = "6.0.0"
|
||||
description = "Cross-platform lib for process and system monitoring in Python."
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
|
||||
files = [
|
||||
{file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"},
|
||||
{file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"},
|
||||
{file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"},
|
||||
{file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"},
|
||||
{file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"},
|
||||
{file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"},
|
||||
{file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"},
|
||||
{file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"},
|
||||
{file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"},
|
||||
{file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"},
|
||||
{file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"},
|
||||
{file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"},
|
||||
{file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"},
|
||||
{file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"},
|
||||
{file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"},
|
||||
{file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"},
|
||||
{file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"},
|
||||
{file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"},
|
||||
{file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"},
|
||||
{file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"},
|
||||
{file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"},
|
||||
{file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"},
|
||||
{file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"},
|
||||
{file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"},
|
||||
{file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"},
|
||||
{file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"},
|
||||
{file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"},
|
||||
{file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"},
|
||||
{file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"},
|
||||
{file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"},
|
||||
{file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"},
|
||||
{file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"},
|
||||
{file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -3677,64 +3693,64 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.30"
|
||||
version = "2.0.31"
|
||||
description = "Database Abstraction Library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc"},
|
||||
{file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2"},
|
||||
{file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7"},
|
||||
{file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797"},
|
||||
{file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af"},
|
||||
{file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5"},
|
||||
{file = "SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8"},
|
||||
{file = "SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb"},
|
||||
{file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0"},
|
||||
{file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46"},
|
||||
{file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa"},
|
||||
{file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13"},
|
||||
{file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9"},
|
||||
{file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0"},
|
||||
{file = "SQLAlchemy-2.0.30-cp311-cp311-win32.whl", hash = "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49"},
|
||||
{file = "SQLAlchemy-2.0.30-cp311-cp311-win_amd64.whl", hash = "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260"},
|
||||
{file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"},
|
||||
{file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"},
|
||||
{file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"},
|
||||
{file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"},
|
||||
{file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"},
|
||||
{file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"},
|
||||
{file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"},
|
||||
{file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"},
|
||||
{file = "SQLAlchemy-2.0.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57"},
|
||||
{file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0"},
|
||||
{file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad"},
|
||||
{file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf"},
|
||||
{file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b"},
|
||||
{file = "SQLAlchemy-2.0.30-cp37-cp37m-win32.whl", hash = "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584"},
|
||||
{file = "SQLAlchemy-2.0.30-cp37-cp37m-win_amd64.whl", hash = "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e"},
|
||||
{file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3"},
|
||||
{file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7"},
|
||||
{file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd"},
|
||||
{file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9"},
|
||||
{file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621"},
|
||||
{file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb"},
|
||||
{file = "SQLAlchemy-2.0.30-cp38-cp38-win32.whl", hash = "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6"},
|
||||
{file = "SQLAlchemy-2.0.30-cp38-cp38-win_amd64.whl", hash = "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e"},
|
||||
{file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a"},
|
||||
{file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b"},
|
||||
{file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6"},
|
||||
{file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7"},
|
||||
{file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953"},
|
||||
{file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c"},
|
||||
{file = "SQLAlchemy-2.0.30-cp39-cp39-win32.whl", hash = "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513"},
|
||||
{file = "SQLAlchemy-2.0.30-cp39-cp39-win_amd64.whl", hash = "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221"},
|
||||
{file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"},
|
||||
{file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"},
|
||||
{file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"},
|
||||
{file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"},
|
||||
{file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"},
|
||||
{file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"},
|
||||
{file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"},
|
||||
{file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"},
|
||||
{file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"},
|
||||
{file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"},
|
||||
{file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"},
|
||||
{file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"},
|
||||
{file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"},
|
||||
{file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"},
|
||||
{file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"},
|
||||
{file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"},
|
||||
{file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"},
|
||||
{file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"},
|
||||
{file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"},
|
||||
{file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"},
|
||||
{file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"},
|
||||
{file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"},
|
||||
{file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"},
|
||||
{file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"},
|
||||
{file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"},
|
||||
{file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"},
|
||||
{file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"},
|
||||
{file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"},
|
||||
{file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"},
|
||||
{file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"},
|
||||
{file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"},
|
||||
{file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"},
|
||||
{file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"},
|
||||
{file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"},
|
||||
{file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"},
|
||||
{file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"},
|
||||
{file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"},
|
||||
{file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"},
|
||||
{file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"},
|
||||
{file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"},
|
||||
{file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"},
|
||||
{file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"},
|
||||
{file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"},
|
||||
{file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"},
|
||||
{file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"},
|
||||
{file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"},
|
||||
{file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"},
|
||||
{file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"},
|
||||
{file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"},
|
||||
{file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"},
|
||||
{file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
|
||||
greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
|
||||
typing-extensions = ">=4.6.0"
|
||||
|
||||
[package.extras]
|
||||
@@ -3846,13 +3862,13 @@ snowflake = ["snowflake-connector-python (>=2.8.0)", "snowflake-snowpark-python
|
||||
|
||||
[[package]]
|
||||
name = "tenacity"
|
||||
version = "8.3.0"
|
||||
version = "8.4.1"
|
||||
description = "Retry code until it succeeds"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tenacity-8.3.0-py3-none-any.whl", hash = "sha256:3649f6443dbc0d9b01b9d8020a9c4ec7a1ff5f6f3c6c8a036ef371f573fe9185"},
|
||||
{file = "tenacity-8.3.0.tar.gz", hash = "sha256:953d4e6ad24357bceffbc9707bc74349aca9d245f68eb65419cf0c249a1949a2"},
|
||||
{file = "tenacity-8.4.1-py3-none-any.whl", hash = "sha256:28522e692eda3e1b8f5e99c51464efcc0b9fc86933da92415168bc1c4e2308fa"},
|
||||
{file = "tenacity-8.4.1.tar.gz", hash = "sha256:54b1412b878ddf7e1f1577cd49527bad8cdef32421bd599beac0c6c3f10582fd"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -4012,13 +4028,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
|
||||
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
|
||||
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
|
||||
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -4636,4 +4652,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "==3.12.0"
|
||||
content-hash = "db6214bf0c8af641ee4b6a66c5e9c44f20dab8578b6686c68a46f6bd3ce4f979"
|
||||
content-hash = "77cf58d792c26a1fb35a19ad82e5b390f30bbaa150832100518189c5207f059b"
|
||||
|
||||
@@ -22,6 +22,7 @@ sqlglot = "==22.5.0"
|
||||
orjson = "==3.10.3"
|
||||
sf-hamilton = {version = "==1.63.0", extras = ["visualization"]}
|
||||
aiohttp = "==3.9.5"
|
||||
ollama-haystack = "==0.0.6"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "==8.2.0"
|
||||
|
||||
@@ -45,7 +45,7 @@ class GenerationPostProcessor:
|
||||
"invalid_generation_results": invalid_generation_results,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error in GenerationPostProcessor: {e}")
|
||||
logger.exception(f"Error in GenerationPostProcessor: {e}")
|
||||
|
||||
return {
|
||||
"valid_generation_results": [],
|
||||
|
||||
@@ -42,7 +42,7 @@ class QueryUnderstandingPostProcessor:
|
||||
)
|
||||
def run(self, replies: List[str]):
|
||||
try:
|
||||
result = orjson.loads(replies[0])["result"]
|
||||
result = orjson.loads(replies[0])["result"].lower()
|
||||
|
||||
if result == "yes":
|
||||
return {
|
||||
|
||||
@@ -17,8 +17,7 @@ from haystack_integrations.document_stores.qdrant.filters import (
|
||||
from qdrant_client.http import models as rest
|
||||
|
||||
from src.core.provider import DocumentStoreProvider
|
||||
from src.providers.llm.openai import EMBEDDING_MODEL_DIMENSION
|
||||
from src.providers.loader import provider
|
||||
from src.providers.loader import get_default_embedding_model_dim, provider
|
||||
|
||||
|
||||
class AsyncQdrantDocumentStore(QdrantDocumentStore):
|
||||
@@ -199,7 +198,12 @@ class QdrantProvider(DocumentStoreProvider):
|
||||
|
||||
def get_store(
|
||||
self,
|
||||
embedding_model_dim: int = EMBEDDING_MODEL_DIMENSION,
|
||||
embedding_model_dim: int = (
|
||||
int(os.getenv("EMBEDDING_MODEL_DIMENSION"))
|
||||
if os.getenv("EMBEDDING_MODEL_DIMENSION")
|
||||
else 0
|
||||
)
|
||||
or get_default_embedding_model_dim(os.getenv("LLM_PROVIDER", "opeani")),
|
||||
dataset_name: Optional[str] = None,
|
||||
recreate_index: bool = False,
|
||||
):
|
||||
|
||||
@@ -21,7 +21,7 @@ from src.providers.loader import provider
|
||||
|
||||
EMBEDDING_MODEL_NAME = "text-embedding-3-small"
|
||||
EMBEDDING_MODEL_DIMENSION = 1536
|
||||
logger = logging.getLogger("azure-openai")
|
||||
logger = logging.getLogger("wren-ai-service")
|
||||
AZURE_GENERATION_MODEL = "gpt-4-turbo"
|
||||
AZURE_GENERATION_MODEL_KWARGS = {
|
||||
"temperature": 0,
|
||||
@@ -299,16 +299,25 @@ class AzureOpenAILLMProvider(LLMProvider):
|
||||
embed_api_version: str = os.getenv("AZURE_EMBED_VERSION"),
|
||||
generation_model: str = os.getenv("AZURE_GENERATION_MODEL")
|
||||
or AZURE_GENERATION_MODEL,
|
||||
embedding_model: str = os.getenv("EMBEDDING_MODEL") or EMBEDDING_MODEL_NAME,
|
||||
embedding_model_dim: int = (
|
||||
int(os.getenv("EMBEDDING_MODEL_DIMENSION"))
|
||||
if os.getenv("EMBEDDING_MODEL_DIMENSION")
|
||||
else 0
|
||||
)
|
||||
or EMBEDDING_MODEL_DIMENSION,
|
||||
):
|
||||
logger.info(f"Using Azure OpenAI Generation Model: {generation_model}")
|
||||
self.chat_api_key = chat_api_key
|
||||
self.chat_api_base = chat_api_base
|
||||
self.chat_api_version = chat_api_version
|
||||
|
||||
self.embed_api_base = embed_api_base
|
||||
self.embed_api_key = embed_api_key
|
||||
self.embed_api_version = embed_api_version
|
||||
self.generation_model = generation_model
|
||||
self._generation_api_key = chat_api_key
|
||||
self._generation_api_base = chat_api_base
|
||||
self._generation_api_version = chat_api_version
|
||||
self._generation_model = generation_model
|
||||
self._embedding_api_base = embed_api_base
|
||||
self._embedding_api_key = embed_api_key
|
||||
self._embedding_api_version = embed_api_version
|
||||
self._embedding_model = embedding_model
|
||||
self._embedding_model_dim = embedding_model_dim
|
||||
|
||||
def get_generator(
|
||||
self,
|
||||
@@ -316,36 +325,28 @@ class AzureOpenAILLMProvider(LLMProvider):
|
||||
system_prompt: Optional[str] = None,
|
||||
):
|
||||
return AsyncAzureGenerator(
|
||||
api_key=self.chat_api_key,
|
||||
model=self.generation_model,
|
||||
api_base=self.chat_api_base,
|
||||
api_version=self.chat_api_version,
|
||||
api_key=self._generation_api_key,
|
||||
model=self._generation_model,
|
||||
api_base=self._generation_api_base,
|
||||
api_version=self._generation_api_version,
|
||||
system_prompt=system_prompt,
|
||||
generation_kwargs=model_kwargs,
|
||||
)
|
||||
|
||||
def get_text_embedder(
|
||||
self,
|
||||
model_name: str = EMBEDDING_MODEL_NAME,
|
||||
model_dim: int = EMBEDDING_MODEL_DIMENSION,
|
||||
):
|
||||
def get_text_embedder(self):
|
||||
return AsyncAzureTextEmbedder(
|
||||
api_key=self.embed_api_key,
|
||||
model=model_name,
|
||||
dimensions=model_dim,
|
||||
api_base_url=self.embed_api_base,
|
||||
api_version=self.embed_api_version,
|
||||
api_key=self._embedding_api_key,
|
||||
model=self._embedding_model,
|
||||
dimensions=self._embedding_model_dim,
|
||||
api_base_url=self._embedding_api_base,
|
||||
api_version=self._embedding_api_version,
|
||||
)
|
||||
|
||||
def get_document_embedder(
|
||||
self,
|
||||
model_name: str = EMBEDDING_MODEL_NAME,
|
||||
model_dim: int = EMBEDDING_MODEL_DIMENSION,
|
||||
):
|
||||
def get_document_embedder(self):
|
||||
return AsyncAzureDocumentEmbedder(
|
||||
api_key=self.embed_api_key,
|
||||
model=model_name,
|
||||
dimensions=model_dim,
|
||||
api_base_url=self.embed_api_base,
|
||||
api_version=self.embed_api_version,
|
||||
api_key=self._embedding_api_key,
|
||||
model=self._embedding_model,
|
||||
dimensions=self._embedding_model_dim,
|
||||
api_base_url=self._embedding_api_base,
|
||||
api_version=self._embedding_api_version,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
from haystack import Document, component
|
||||
from haystack.dataclasses import StreamingChunk
|
||||
from haystack_integrations.components.embedders.ollama import (
|
||||
OllamaDocumentEmbedder,
|
||||
OllamaTextEmbedder,
|
||||
)
|
||||
from haystack_integrations.components.generators.ollama import OllamaGenerator
|
||||
from tqdm import tqdm
|
||||
|
||||
from src.core.provider import LLMProvider
|
||||
from src.providers.loader import provider
|
||||
|
||||
logger = logging.getLogger("wren-ai-service")
|
||||
|
||||
OLLAMA_URL = "http://localhost:11434"
|
||||
GENERATION_MODEL_NAME = "llama3:8b"
|
||||
GENERATION_MODEL_KWARGS = {
|
||||
"temperature": 0,
|
||||
}
|
||||
EMBEDDING_MODEL_NAME = "nomic-embed-text"
|
||||
EMBEDDING_MODEL_DIMENSION = 768 # https://huggingface.co/nomic-ai/nomic-embed-text-v1.5
|
||||
|
||||
|
||||
@component
|
||||
class AsyncGenerator(OllamaGenerator):
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "orca-mini",
|
||||
url: str = "http://localhost:11434/api/generate",
|
||||
generation_kwargs: Optional[Dict[str, Any]] = None,
|
||||
system_prompt: Optional[str] = None,
|
||||
template: Optional[str] = None,
|
||||
raw: bool = False,
|
||||
timeout: int = 120,
|
||||
streaming_callback: Optional[Callable[[StreamingChunk], None]] = None,
|
||||
):
|
||||
super(AsyncGenerator, self).__init__(
|
||||
model=model,
|
||||
url=url,
|
||||
generation_kwargs=generation_kwargs,
|
||||
system_prompt=system_prompt,
|
||||
template=template,
|
||||
raw=raw,
|
||||
timeout=timeout,
|
||||
streaming_callback=streaming_callback,
|
||||
)
|
||||
|
||||
async def _handle_streaming_response(self, response) -> List[StreamingChunk]:
|
||||
"""
|
||||
Handles Streaming response cases
|
||||
"""
|
||||
chunks: List[StreamingChunk] = []
|
||||
for chunk in await response.iter_lines():
|
||||
chunk_delta: StreamingChunk = self._build_chunk(chunk)
|
||||
chunks.append(chunk_delta)
|
||||
if self.streaming_callback is not None:
|
||||
self.streaming_callback(chunk_delta)
|
||||
return chunks
|
||||
|
||||
async def _convert_to_response(
|
||||
self, ollama_response: aiohttp.ClientResponse
|
||||
) -> Dict[str, List[Any]]:
|
||||
"""
|
||||
Converts a response from the Ollama API to the required Haystack format.
|
||||
"""
|
||||
|
||||
resp_dict = await ollama_response.json()
|
||||
|
||||
replies = [resp_dict["response"]]
|
||||
meta = {key: value for key, value in resp_dict.items() if key != "response"}
|
||||
|
||||
return {"replies": replies, "meta": [meta]}
|
||||
|
||||
def _create_json_payload(
|
||||
self, prompt: str, stream: bool, generation_kwargs=None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Returns a dictionary of JSON arguments for a POST request to an Ollama service.
|
||||
"""
|
||||
generation_kwargs = generation_kwargs or {}
|
||||
return {
|
||||
"prompt": prompt,
|
||||
"model": self.model,
|
||||
"stream": stream,
|
||||
"raw": self.raw,
|
||||
"format": "json", # https://github.com/ollama/ollama/blob/main/docs/api.md#request-json-mode
|
||||
"template": self.template,
|
||||
"system": self.system_prompt,
|
||||
"options": generation_kwargs,
|
||||
}
|
||||
|
||||
@component.output_types(replies=List[str], meta=List[Dict[str, Any]])
|
||||
async def run(
|
||||
self,
|
||||
prompt: str,
|
||||
generation_kwargs: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
logger.debug(f"Running Ollama generator with prompt: {prompt}")
|
||||
|
||||
generation_kwargs = {**self.generation_kwargs, **(generation_kwargs or {})}
|
||||
|
||||
stream = self.streaming_callback is not None
|
||||
|
||||
json_payload = self._create_json_payload(prompt, stream, generation_kwargs)
|
||||
|
||||
async with aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(self.timeout)
|
||||
) as session:
|
||||
response = await session.post(
|
||||
self.url,
|
||||
json=json_payload,
|
||||
)
|
||||
|
||||
if stream:
|
||||
chunks: List[StreamingChunk] = await self._handle_streaming_response(
|
||||
response
|
||||
)
|
||||
return self._convert_to_streaming_response(chunks)
|
||||
|
||||
return await self._convert_to_response(response)
|
||||
|
||||
|
||||
@component
|
||||
class AsyncTextEmbedder(OllamaTextEmbedder):
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "nomic-embed-text",
|
||||
url: str = "http://localhost:11434/api/embeddings",
|
||||
generation_kwargs: Optional[Dict[str, Any]] = None,
|
||||
timeout: int = 120,
|
||||
):
|
||||
super(AsyncTextEmbedder, self).__init__(
|
||||
model=model,
|
||||
url=url,
|
||||
generation_kwargs=generation_kwargs,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
@component.output_types(embedding=List[float], meta=Dict[str, Any])
|
||||
async def run(
|
||||
self,
|
||||
text: str,
|
||||
generation_kwargs: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
logger.debug(f"Running Ollama text embedder with text: {text}")
|
||||
|
||||
payload = self._create_json_payload(text, generation_kwargs)
|
||||
|
||||
start = time.perf_counter()
|
||||
async with aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(self.timeout)
|
||||
) as session:
|
||||
async with session.post(
|
||||
self.url,
|
||||
json=payload,
|
||||
) as response:
|
||||
elapsed = time.perf_counter() - start
|
||||
result = await response.json()
|
||||
|
||||
result["meta"] = {"model": self.model, "duration": elapsed}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@component
|
||||
class AsyncDocumentEmbedder(OllamaDocumentEmbedder):
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "nomic-embed-text",
|
||||
url: str = "http://localhost:11434/api/embeddings",
|
||||
generation_kwargs: Optional[Dict[str, Any]] = None,
|
||||
timeout: int = 120,
|
||||
prefix: str = "",
|
||||
suffix: str = "",
|
||||
progress_bar: bool = True,
|
||||
meta_fields_to_embed: Optional[List[str]] = None,
|
||||
embedding_separator: str = "\n",
|
||||
):
|
||||
super(AsyncDocumentEmbedder, self).__init__(
|
||||
model=model,
|
||||
url=url,
|
||||
generation_kwargs=generation_kwargs,
|
||||
timeout=timeout,
|
||||
prefix=prefix,
|
||||
suffix=suffix,
|
||||
progress_bar=progress_bar,
|
||||
meta_fields_to_embed=meta_fields_to_embed,
|
||||
embedding_separator=embedding_separator,
|
||||
)
|
||||
|
||||
async def _embed_batch(
|
||||
self,
|
||||
texts_to_embed: List[str],
|
||||
batch_size: int,
|
||||
generation_kwargs: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
"""
|
||||
Ollama Embedding only allows single uploads, not batching. Currently the batch size is set to 1.
|
||||
If this changes in the future, line 86 (the first line within the for loop), can contain:
|
||||
batch = texts_to_embed[i + i + batch_size]
|
||||
"""
|
||||
|
||||
all_embeddings = []
|
||||
meta: Dict[str, Any] = {"model": self.model}
|
||||
|
||||
async with aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(self.timeout)
|
||||
) as session:
|
||||
for i in tqdm(
|
||||
range(0, len(texts_to_embed), batch_size),
|
||||
disable=not self.progress_bar,
|
||||
desc="Calculating embeddings",
|
||||
):
|
||||
batch = texts_to_embed[i] # Single batch only
|
||||
payload = self._create_json_payload(batch, generation_kwargs)
|
||||
|
||||
async with session.post(
|
||||
self.url,
|
||||
json=payload,
|
||||
) as response:
|
||||
result = await response.json()
|
||||
all_embeddings.append(result["embedding"])
|
||||
|
||||
return all_embeddings, meta
|
||||
|
||||
@component.output_types(embedding=List[float], meta=Dict[str, Any])
|
||||
async def run(
|
||||
self,
|
||||
documents: List[str],
|
||||
generation_kwargs: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
logger.debug(f"Running Ollama document embedder with documents: {documents}")
|
||||
|
||||
if (
|
||||
not isinstance(documents, list)
|
||||
or documents
|
||||
and not isinstance(documents[0], Document)
|
||||
):
|
||||
msg = (
|
||||
"OllamaDocumentEmbedder expects a list of Documents as input."
|
||||
"In case you want to embed a list of strings, please use the OllamaTextEmbedder."
|
||||
)
|
||||
raise TypeError(msg)
|
||||
|
||||
texts_to_embed = self._prepare_texts_to_embed(documents=documents)
|
||||
embeddings, meta = await self._embed_batch(
|
||||
texts_to_embed=texts_to_embed,
|
||||
batch_size=self.batch_size,
|
||||
generation_kwargs=generation_kwargs,
|
||||
)
|
||||
|
||||
for doc, emb in zip(documents, embeddings):
|
||||
doc.embedding = emb
|
||||
|
||||
return {"documents": documents, "meta": meta}
|
||||
|
||||
|
||||
@provider("ollama")
|
||||
class OllamaLLMProvider(LLMProvider):
|
||||
def __init__(
|
||||
self,
|
||||
url: str = os.getenv("OLLAMA_URL") or OLLAMA_URL,
|
||||
generation_model: str = os.getenv("GENERATION_MODEL") or GENERATION_MODEL_NAME,
|
||||
embedding_model: str = os.getenv("EMBEDDING_MODEL") or EMBEDDING_MODEL_NAME,
|
||||
):
|
||||
logger.info(f"Using Ollama Generation Model: {generation_model}")
|
||||
self._url = url
|
||||
self._generation_model = generation_model
|
||||
self._embedding_model = embedding_model
|
||||
|
||||
def get_generator(
|
||||
self,
|
||||
model_kwargs: Optional[Dict[str, Any]] = GENERATION_MODEL_KWARGS,
|
||||
system_prompt: Optional[str] = None,
|
||||
):
|
||||
return AsyncGenerator(
|
||||
model=self._generation_model,
|
||||
url=f"{self._url}/api/generate",
|
||||
generation_kwargs=model_kwargs,
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
|
||||
def get_text_embedder(
|
||||
self,
|
||||
model_kwargs: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
return AsyncTextEmbedder(
|
||||
model=self._embedding_model,
|
||||
url=f"{self._url}/api/embeddings",
|
||||
generation_kwargs=model_kwargs,
|
||||
)
|
||||
|
||||
def get_document_embedder(
|
||||
self,
|
||||
model_kwargs: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
return AsyncDocumentEmbedder(
|
||||
model=self._embedding_model,
|
||||
url=f"{self._url}/api/embeddings",
|
||||
generation_kwargs=model_kwargs,
|
||||
)
|
||||
@@ -276,8 +276,14 @@ class OpenAILLMProvider(LLMProvider):
|
||||
self,
|
||||
api_key: Secret = Secret.from_env_var("OPENAI_API_KEY"),
|
||||
api_base: str = os.getenv("OPENAI_API_BASE") or OPENAI_API_BASE,
|
||||
generation_model: str = os.getenv("OPENAI_GENERATION_MODEL")
|
||||
or GENERATION_MODEL_NAME,
|
||||
embedding_model: str = os.getenv("EMBEDDING_MODEL") or EMBEDDING_MODEL_NAME,
|
||||
embedding_model_dim: int = (
|
||||
int(os.getenv("EMBEDDING_MODEL_DIMENSION"))
|
||||
if os.getenv("EMBEDDING_MODEL_DIMENSION")
|
||||
else 0
|
||||
)
|
||||
or EMBEDDING_MODEL_DIMENSION,
|
||||
generation_model: str = os.getenv("GENERATION_MODEL") or GENERATION_MODEL_NAME,
|
||||
):
|
||||
def _verify_api_key(api_key: str, api_base: str) -> None:
|
||||
"""
|
||||
@@ -285,54 +291,45 @@ class OpenAILLMProvider(LLMProvider):
|
||||
"""
|
||||
OpenAI(api_key=api_key, base_url=api_base).models.list()
|
||||
|
||||
_verify_api_key(api_key.resolve_value(), api_base)
|
||||
logger.info(f"Using OpenAI Generation Model: {generation_model}")
|
||||
# TODO: currently only OpenAI api key can be verified
|
||||
if api_base == OPENAI_API_BASE:
|
||||
_verify_api_key(api_key.resolve_value(), api_base)
|
||||
logger.info(f"Using OpenAI Generation Model: {generation_model}")
|
||||
else:
|
||||
logger.info(
|
||||
f"Using OpenAI API-compatible Generation Model: {generation_model}"
|
||||
)
|
||||
self._api_key = api_key
|
||||
self._api_base = api_base
|
||||
self._embedding_model = embedding_model
|
||||
self._embedding_model_dim = embedding_model_dim
|
||||
self._generation_model = generation_model
|
||||
|
||||
def get_generator(
|
||||
self,
|
||||
model_kwargs: Optional[Dict[str, Any]] = None,
|
||||
model_kwargs: Optional[Dict[str, Any]] = GENERATION_MODEL_KWARGS,
|
||||
system_prompt: Optional[str] = None,
|
||||
):
|
||||
def _get_generation_kwargs(
|
||||
api_base: str,
|
||||
model_kwargs: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
if api_base == OPENAI_API_BASE:
|
||||
return GENERATION_MODEL_KWARGS
|
||||
|
||||
return model_kwargs
|
||||
|
||||
return AsyncGenerator(
|
||||
api_key=self._api_key,
|
||||
api_base_url=self._api_base,
|
||||
model=self._generation_model,
|
||||
system_prompt=system_prompt,
|
||||
generation_kwargs=_get_generation_kwargs(self._api_base, model_kwargs),
|
||||
generation_kwargs=model_kwargs,
|
||||
)
|
||||
|
||||
def get_text_embedder(
|
||||
self,
|
||||
model_name: str = EMBEDDING_MODEL_NAME,
|
||||
model_dim: int = EMBEDDING_MODEL_DIMENSION,
|
||||
):
|
||||
def get_text_embedder(self):
|
||||
return AsyncTextEmbedder(
|
||||
api_key=self._api_key,
|
||||
api_base_url=self._api_base,
|
||||
model=model_name,
|
||||
dimensions=model_dim,
|
||||
model=self._embedding_model,
|
||||
dimensions=self._embedding_model_dim,
|
||||
)
|
||||
|
||||
def get_document_embedder(
|
||||
self,
|
||||
model_name: str = EMBEDDING_MODEL_NAME,
|
||||
model_dim: int = EMBEDDING_MODEL_DIMENSION,
|
||||
):
|
||||
def get_document_embedder(self):
|
||||
return AsyncDocumentEmbedder(
|
||||
api_key=self._api_key,
|
||||
api_base_url=self._api_base,
|
||||
model=model_name,
|
||||
dimensions=model_dim,
|
||||
model=self._embedding_model,
|
||||
dimensions=self._embedding_model_dim,
|
||||
)
|
||||
|
||||
@@ -91,3 +91,9 @@ def get_provider(name: str):
|
||||
logger.debug(f"Getting provider: {name} from {PROVIDERS}")
|
||||
|
||||
return PROVIDERS[name]
|
||||
|
||||
|
||||
def get_default_embedding_model_dim(llm_provider: str):
|
||||
return importlib.import_module(
|
||||
f"src.providers.llm.{llm_provider}"
|
||||
).EMBEDDING_MODEL_DIMENSION
|
||||
|
||||
@@ -127,7 +127,7 @@ class AskService:
|
||||
status="finished",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"ask pipeline - Failed to prepare semantics: {e}")
|
||||
logger.exception(f"ask pipeline - Failed to prepare semantics: {e}")
|
||||
|
||||
self._prepare_semantics_statuses[
|
||||
prepare_semantics_request.id
|
||||
@@ -343,7 +343,8 @@ class AskService:
|
||||
response=results,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"ask pipeline - OTHERS: {e}")
|
||||
logger.exception(f"ask pipeline - OTHERS: {e}")
|
||||
|
||||
self._ask_results[query_id] = AskResultResponse(
|
||||
status="failed",
|
||||
error=AskResultResponse.AskError(
|
||||
|
||||
@@ -106,7 +106,8 @@ class AskDetailsService:
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"ask-details pipeline - OTHERS: {e}")
|
||||
logger.exception(f"ask-details pipeline - OTHERS: {e}")
|
||||
|
||||
self._ask_details_results[query_id] = AskDetailsResultResponse(
|
||||
status="failed",
|
||||
error=AskDetailsResultResponse.AskDetailsError(
|
||||
|
||||
@@ -26,7 +26,8 @@ with open(f"./outputs/locust/{filename}.json", "r") as f:
|
||||
|
||||
formatted = {
|
||||
"llm provider": os.getenv("LLM_PROVIDER"),
|
||||
"generation model": os.getenv("OPENAI_GENERATION_MODEL"),
|
||||
"generation model": os.getenv("GENERATION_MODEL"),
|
||||
"embedding model": os.getenv("EMBEDDING_MODEL"),
|
||||
"locustfile": "tests/locust/locustfile.py",
|
||||
"test results": test_results,
|
||||
}
|
||||
|
||||
@@ -3,21 +3,27 @@ from src.providers import loader
|
||||
|
||||
def test_import_mods():
|
||||
loader.import_mods("src.providers")
|
||||
assert len(loader.PROVIDERS) == 5
|
||||
assert len(loader.PROVIDERS) == 6
|
||||
|
||||
|
||||
def test_get_provider():
|
||||
loader.import_mods("src.providers")
|
||||
|
||||
# llm provider
|
||||
provider = loader.get_provider("openai")
|
||||
assert provider.__name__ == "OpenAILLMProvider"
|
||||
|
||||
provider = loader.get_provider("qdrant")
|
||||
assert provider.__name__ == "QdrantProvider"
|
||||
|
||||
provider = loader.get_provider("azure_openai")
|
||||
assert provider.__name__ == "AzureOpenAILLMProvider"
|
||||
|
||||
provider = loader.get_provider("ollama")
|
||||
assert provider.__name__ == "OllamaLLMProvider"
|
||||
|
||||
# document store provider
|
||||
provider = loader.get_provider("qdrant")
|
||||
assert provider.__name__ == "QdrantProvider"
|
||||
|
||||
# engine provider
|
||||
provider = loader.get_provider("wren-ui")
|
||||
assert provider.__name__ == "WrenUI"
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ services:
|
||||
WREN_ENGINE_ENDPOINT: http://engine:${WREN_ENGINE_PORT}
|
||||
WREN_AI_ENDPOINT: http://host.docker.internal:${WREN_AI_SERVICE_PORT}
|
||||
IBIS_SERVER_ENDPOINT: http://ibis:${IBIS_SERVER_PORT}
|
||||
OPENAI_GENERATION_MODEL: ${OPENAI_GENERATION_MODEL}
|
||||
GENERATION_MODEL: ${GENERATION_MODEL}
|
||||
# telemetry
|
||||
WREN_ENGINE_PORT: ${WREN_ENGINE_PORT}
|
||||
WREN_AI_SERVICE_VERSION: ${WREN_AI_SERVICE_VERSION}
|
||||
|
||||
@@ -43,6 +43,26 @@ func evaluateTelemetryPreferences() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func askForLLMProvider() (string, error) {
|
||||
// let users know we're asking for a LLM provider
|
||||
fmt.Println("Please provide the LLM provider you want to use")
|
||||
fmt.Println("You can learn more about how to set up custom LLMs at https://docs.getwren.ai/installation/custom_llm#running-wren-ai-with-your-custom-llm-or-document-store")
|
||||
|
||||
prompt := promptui.Select{
|
||||
Label: "Select an LLM provider",
|
||||
Items: []string{"OpenAI", "Custom"},
|
||||
}
|
||||
|
||||
_, result, err := prompt.Run()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Prompt failed %v\n", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func askForAPIKey() (string, error) {
|
||||
// let users know we're asking for an API key
|
||||
fmt.Println("Please provide your OpenAI API key")
|
||||
@@ -93,6 +113,19 @@ func askForGenerationModel() (string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func isEnvFileValidForCustomLLM(projectDir string) error {
|
||||
// validate if .env.ai file exists in ~/.wrenai
|
||||
envFilePath := path.Join(projectDir, ".env.ai")
|
||||
|
||||
if _, err := os.Stat(envFilePath); os.IsNotExist(err) {
|
||||
errMessage := fmt.Sprintf("Please create a .env.ai file in %s first, more details at https://docs.getwren.ai/installation/custom_llm#running-wren-ai-with-your-custom-llm-or-document-store", projectDir)
|
||||
return errors.New(errMessage)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func Launch() {
|
||||
// recover from panic
|
||||
defer func() {
|
||||
@@ -108,13 +141,30 @@ func Launch() {
|
||||
myFigure.Print()
|
||||
fmt.Println(strings.Repeat("=", 55))
|
||||
|
||||
// ask for OpenAI API key
|
||||
pterm.Print("\n")
|
||||
apiKey, err := askForAPIKey()
|
||||
// prepare a project directory
|
||||
pterm.Info.Println("Preparing project directory")
|
||||
projectDir := prepareProjectDir()
|
||||
|
||||
// ask for OpenAI generation model
|
||||
// ask for LLM provider
|
||||
pterm.Print("\n")
|
||||
generationModel, err := askForGenerationModel()
|
||||
llmProvider, err := askForLLMProvider()
|
||||
openaiApiKey := ""
|
||||
openaiGenerationModel := ""
|
||||
if llmProvider == "OpenAI" {
|
||||
// ask for OpenAI API key
|
||||
pterm.Print("\n")
|
||||
openaiApiKey, _ = askForAPIKey()
|
||||
|
||||
// ask for OpenAI generation model
|
||||
pterm.Print("\n")
|
||||
openaiGenerationModel, _ = askForGenerationModel()
|
||||
} else {
|
||||
// check if .env.ai file exists
|
||||
err = isEnvFileValidForCustomLLM(projectDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ask for telemetry consent
|
||||
pterm.Print("\n")
|
||||
@@ -142,17 +192,13 @@ func Launch() {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
// prepare a project directory
|
||||
pterm.Info.Println("Preparing project directory")
|
||||
projectDir := prepareProjectDir()
|
||||
|
||||
// download docker-compose file and env file template for Wren AI
|
||||
pterm.Info.Println("Downloading docker-compose file and env file")
|
||||
// find an available port
|
||||
uiPort := utils.FindAvailablePort(3000)
|
||||
aiPort := utils.FindAvailablePort(5555)
|
||||
|
||||
err = utils.PrepareDockerFiles(apiKey, generationModel, uiPort, aiPort, projectDir, telemetryEnabled)
|
||||
err = utils.PrepareDockerFiles(openaiApiKey, openaiGenerationModel, uiPort, aiPort, projectDir, telemetryEnabled)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -160,7 +206,7 @@ func Launch() {
|
||||
// launch Wren AI
|
||||
pterm.Info.Println("Launching Wren AI")
|
||||
const projectName string = "wrenai"
|
||||
err = utils.RunDockerCompose(projectName, projectDir)
|
||||
err = utils.RunDockerCompose(projectName, projectDir, llmProvider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -32,14 +32,14 @@ const (
|
||||
PG_USERNAME string = "wren-user"
|
||||
)
|
||||
|
||||
func replaceEnvFileContent(content string, OpenaiApiKey string, OpenaiGenerationModel string, hostPort int, aiPort int, pg_password string, userUUID string, telemetryEnabled bool) string {
|
||||
func replaceEnvFileContent(content string, openaiApiKey string, openAIGenerationModel string, hostPort int, aiPort int, pg_password string, userUUID string, telemetryEnabled bool) string {
|
||||
// replace OPENAI_API_KEY
|
||||
reg := regexp.MustCompile(`OPENAI_API_KEY=sk-(.*)`)
|
||||
str := reg.ReplaceAllString(content, "OPENAI_API_KEY="+OpenaiApiKey)
|
||||
str := reg.ReplaceAllString(content, "OPENAI_API_KEY="+openaiApiKey)
|
||||
|
||||
// replace OPENAI_GENERATION_MODEL
|
||||
reg = regexp.MustCompile(`OPENAI_GENERATION_MODEL=(.*)`)
|
||||
str = reg.ReplaceAllString(str, "OPENAI_GENERATION_MODEL="+OpenaiGenerationModel)
|
||||
// replace GENERATION_MODEL
|
||||
reg = regexp.MustCompile(`GENERATION_MODEL=(.*)`)
|
||||
str = reg.ReplaceAllString(str, "GENERATION_MODEL="+openAIGenerationModel)
|
||||
|
||||
// replace USER_UUID
|
||||
reg = regexp.MustCompile(`USER_UUID=(.*)`)
|
||||
@@ -185,8 +185,18 @@ func PrepareDockerFiles(openaiApiKey string, openaiGenerationModel string, hostP
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// replace the content with regex
|
||||
envFileContent := replaceEnvFileContent(string(envExampleFileContent), openaiApiKey, openaiGenerationModel, hostPort, aiPort, pg_pwd, userUUID, telemetryEnabled)
|
||||
envFileContent := replaceEnvFileContent(
|
||||
string(envExampleFileContent),
|
||||
openaiApiKey,
|
||||
openaiGenerationModel,
|
||||
hostPort,
|
||||
aiPort,
|
||||
pg_pwd,
|
||||
userUUID,
|
||||
telemetryEnabled,
|
||||
)
|
||||
newEnvFile := getEnvFilePath(projectDir)
|
||||
// write the file
|
||||
err = os.WriteFile(newEnvFile, []byte(envFileContent), 0644)
|
||||
@@ -207,10 +217,16 @@ func getEnvFilePath(projectDir string) string {
|
||||
return path.Join(projectDir, ".env")
|
||||
}
|
||||
|
||||
func RunDockerCompose(projectName string, projectDir string) error {
|
||||
func RunDockerCompose(projectName string, projectDir string, llmProvider string) error {
|
||||
ctx := context.Background()
|
||||
composeFilePath := path.Join(projectDir, "docker-compose.yaml")
|
||||
envFile := path.Join(projectDir, ".env")
|
||||
envFiles := []string{envFile}
|
||||
|
||||
if llmProvider == "Custom" {
|
||||
customEnvFile := path.Join(projectDir, ".env.ai")
|
||||
envFiles = append(envFiles, customEnvFile)
|
||||
}
|
||||
|
||||
// docker-compose up
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
@@ -237,7 +253,7 @@ func RunDockerCompose(projectName string, projectDir string) error {
|
||||
ProjectName: projectName,
|
||||
ConfigPaths: []string{composeFilePath},
|
||||
WorkDir: projectDir,
|
||||
EnvFiles: []string{envFile},
|
||||
EnvFiles: envFiles,
|
||||
}
|
||||
|
||||
// Turn projectOptions into a project with default values
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface IConfig {
|
||||
|
||||
// wren AI
|
||||
wrenAIEndpoint: string;
|
||||
openaiGenerationModel?: string;
|
||||
generationModel?: string;
|
||||
|
||||
// ibis server
|
||||
ibisServerEndpoint: string;
|
||||
@@ -102,7 +102,7 @@ const config = {
|
||||
|
||||
// wren AI
|
||||
wrenAIEndpoint: process.env.WREN_AI_ENDPOINT,
|
||||
openaiGenerationModel: process.env.OPENAI_GENERATION_MODEL,
|
||||
generationModel: process.env.GENERATION_MODEL,
|
||||
|
||||
// ibis server
|
||||
ibisServerEndpoint: process.env.IBIS_SERVER_ENDPOINT,
|
||||
|
||||
@@ -12,7 +12,7 @@ const {
|
||||
userUUID,
|
||||
telemetryEnabled,
|
||||
wrenAIVersion,
|
||||
openaiGenerationModel,
|
||||
generationModel,
|
||||
wrenEngineVersion,
|
||||
wrenUIVersion,
|
||||
posthogApiKey,
|
||||
@@ -67,7 +67,7 @@ export class Telemetry {
|
||||
'wren-ai-service-version': wrenAIVersion || null,
|
||||
|
||||
// collect AI model info
|
||||
'openai-generation-model': openaiGenerationModel || null,
|
||||
'generation-model': generationModel || null,
|
||||
|
||||
// collect some system info from process module
|
||||
node_version: process.version,
|
||||
|
||||
Reference in New Issue
Block a user