diff --git a/.github/workflows/docker-chatbot-rag-app.yml b/.github/workflows/docker-chatbot-rag-app.yml index f4ac68a6..9a5c2752 100644 --- a/.github/workflows/docker-chatbot-rag-app.yml +++ b/.github/workflows/docker-chatbot-rag-app.yml @@ -12,7 +12,9 @@ on: branches: - main paths: - # Verify changes to the Dockerfile on PRs + # Verify changes to the Dockerfile on PRs, tainted when we update ES. + - docker/docker-compose-elastic.yml + - example-apps/chatbot-rag-app/docker-compose.yml - example-apps/chatbot-rag-app/Dockerfile - .github/workflows/docker-chatbot-rag-app.yml - '!**/*.md' @@ -42,13 +44,27 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + # This builds the image and pushes its digest if a multi-architecture + # image will be made later (event_name == 'push'). If PR, the image is + # loaded into docker for testing. - uses: docker/build-push-action@v6 id: build with: context: example-apps/chatbot-rag-app - outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=${{ github.event_name == 'push' && 'true' || 'false' }} + outputs: type=${{ github.event_name == 'pull_request' && 'docker' || 'image' }},name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=${{ github.event_name == 'push' && 'true' || 'false' }} cache-from: type=gha cache-to: type=gha,mode=max + - name: start elasticsearch + if: github.event_name == 'pull_request' + run: docker compose -f docker/docker-compose-elastic.yml up --quiet-pull -d --wait --wait-timeout 120 elasticsearch + - name: test image + if: github.event_name == 'pull_request' + working-directory: example-apps/chatbot-rag-app + run: | # This tests ELSER is working, which doesn't require an LLM. + cp env.example .env + # same as `docker compose run --rm -T create-index`, except pull never + docker run --rm --name create-index --env-file .env --pull never \ + --add-host "localhost:host-gateway" ${{ env.IMAGE }} flask create-index - name: export digest if: github.event_name == 'push' run: | diff --git a/docker/README.md b/docker/README.md index 0652c80a..75062ab5 100644 --- a/docker/README.md +++ b/docker/README.md @@ -12,7 +12,7 @@ wget https://raw.githubusercontent.com/elastic/elasticsearch-labs/refs/heads/mai Use docker compose to run Elastic stack in the background: ```bash -docker compose -f docker-compose-elastic.yml up --force-recreate -d +docker compose -f docker-compose-elastic.yml up --force-recreate --wait -d ``` Then, you can view Kibana at http://localhost:5601/app/home#/ diff --git a/docker/docker-compose-elastic.yml b/docker/docker-compose-elastic.yml index 6d2b0b8b..0f544dac 100644 --- a/docker/docker-compose-elastic.yml +++ b/docker/docker-compose-elastic.yml @@ -2,7 +2,7 @@ name: elastic-stack services: elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0 + image: docker.elastic.co/elasticsearch/elasticsearch:8.17.2 container_name: elasticsearch ports: - 9200:9200 @@ -16,21 +16,29 @@ services: - xpack.security.http.ssl.enabled=false - xpack.security.transport.ssl.enabled=false - xpack.license.self_generated.type=trial - - ES_JAVA_OPTS=-Xmx8g + # Note that ELSER is recommended to have 2GB, but it is JNI (PyTorch). + # So, ELSER's memory is in addition to the heap and other overhead. + - ES_JAVA_OPTS=-Xms2g -Xmx2g ulimits: memlock: soft: -1 hard: -1 healthcheck: - test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health?wait_for_status=yellow&timeout=500ms"] - retries: 300 + test: # readiness probe taken from kbn-health-gateway-server script + [ + "CMD-SHELL", + "curl -s http://localhost:9200 | grep -q 'missing authentication credentials'", + ] + start_period: 10s interval: 1s + timeout: 10s + retries: 120 elasticsearch_settings: depends_on: elasticsearch: condition: service_healthy - image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0 + image: docker.elastic.co/elasticsearch/elasticsearch:8.17.2 container_name: elasticsearch_settings restart: 'no' command: > @@ -42,7 +50,7 @@ services: ' kibana: - image: docker.elastic.co/kibana/kibana:8.17.0 + image: docker.elastic.co/kibana/kibana:8.17.2 container_name: kibana depends_on: elasticsearch_settings: @@ -66,7 +74,7 @@ services: interval: 1s apm-server: - image: docker.elastic.co/apm/apm-server:8.17.0 + image: docker.elastic.co/apm/apm-server:8.17.2 container_name: apm-server depends_on: elasticsearch: diff --git a/example-apps/chatbot-rag-app/README.md b/example-apps/chatbot-rag-app/README.md index bf10b9ec..e6f7013e 100644 --- a/example-apps/chatbot-rag-app/README.md +++ b/example-apps/chatbot-rag-app/README.md @@ -45,14 +45,17 @@ and configure its templated connection settings: ## Running the App +This application contains two services: +* create-index: Installs ELSER and ingests data into elasticsearch +* api-frontend: Hosts the chatbot-rag-app application on http://localhost:4000 + There are two ways to run the app: via Docker or locally. Docker is advised for ease while locally is advised if you are making changes to the application. ### Run with docker -Docker compose is the easiest way, as you get one-step to: -* ingest data into elasticsearch -* run the app, which listens on http://localhost:4000 +Docker compose is the easiest way to get started, as you don't need to have a +working Python environment. **Double-check you have a `.env` file with all your variables set first!** @@ -60,8 +63,7 @@ Docker compose is the easiest way, as you get one-step to: docker compose up --pull always --force-recreate ``` -*Note*: First time creating the index can fail on timeout. Wait a few minutes -and retry. +*Note*: The first run may take several minutes to become available. Clean up when finished, like this: @@ -69,10 +71,10 @@ Clean up when finished, like this: docker compose down ``` -### Run locally +### Run with Python -If you want to run this example with Python and Node.js, you need to do a few -things listed in the [Dockerfile](Dockerfile). The below uses the same +If you want to run this example with Python, you need to do a few things listed +in the [Dockerfile](Dockerfile) to build it first. The below uses the same production mode as used in Docker to avoid problems in debug mode. **Double-check you have a `.env` file with all your variables set first!** @@ -89,7 +91,7 @@ nvm use --lts (cd frontend; yarn install; REACT_APP_API_HOST=/api yarn build) ``` -#### Configure your python environment +#### Configure your Python environment Before we can run the app, we need a working Python environment with the correct packages installed: @@ -102,17 +104,16 @@ pip install "python-dotenv[cli]" pip install -r requirements.txt ``` -#### Run the ingest command +#### Create your Elasticsearch index First, ingest the data into elasticsearch: ```bash -FLASK_APP=api/app.py dotenv run -- flask create-index +dotenv run -- flask create-index ``` -*Note*: First time creating the index can fail on timeout. Wait a few minutes -and retry. +*Note*: This may take several minutes to complete -#### Run the app +#### Run the application Now, run the app, which listens on http://localhost:4000 ```bash @@ -185,10 +186,10 @@ passages. Modify this script to index your own data. See [Langchain documentation][loader-docs] for more ways to load documents. -### Building from source with docker +### Running from source with Docker -To build the app from source instead of using published images, pass the `--build` -flag to Docker Compose. +To build the app from source instead of using published images, pass the +`--build` flag to Docker Compose instead of `--pull always` ```bash docker compose up --build --force-recreate diff --git a/example-apps/chatbot-rag-app/api/chat.py b/example-apps/chatbot-rag-app/api/chat.py index c5916783..376df5d0 100644 --- a/example-apps/chatbot-rag-app/api/chat.py +++ b/example-apps/chatbot-rag-app/api/chat.py @@ -6,6 +6,7 @@ get_elasticsearch_chat_message_history, ) from flask import current_app, render_template, stream_with_context +from functools import cache from langchain_elasticsearch import ( ElasticsearchStore, SparseVectorStrategy, @@ -27,11 +28,16 @@ strategy=SparseVectorStrategy(model_id=ELSER_MODEL), ) -llm = get_llm() + +@cache +def get_lazy_llm(): + return get_llm() @stream_with_context def ask_question(question, session_id): + llm = get_lazy_llm() + yield f"data: {SESSION_ID_TAG} {session_id}\n\n" current_app.logger.debug("Chat session ID: %s", session_id) diff --git a/example-apps/chatbot-rag-app/data/index_data.py b/example-apps/chatbot-rag-app/data/index_data.py index 0ac09b59..9a2a8655 100644 --- a/example-apps/chatbot-rag-app/data/index_data.py +++ b/example-apps/chatbot-rag-app/data/index_data.py @@ -1,8 +1,18 @@ import json import os +from sys import stdout import time +from halo import Halo +from warnings import warn + +from elasticsearch import ( + ApiError, + Elasticsearch, + NotFoundError, + BadRequestError, +) +from elastic_transport._exceptions import ConnectionTimeout -from elasticsearch import Elasticsearch, NotFoundError from langchain.docstore.document import Document from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_elasticsearch import ElasticsearchStore @@ -18,14 +28,12 @@ ELSER_MODEL = os.getenv("ELSER_MODEL", ".elser_model_2") if ELASTICSEARCH_USER: - elasticsearch_client = Elasticsearch( + es = Elasticsearch( hosts=[ELASTICSEARCH_URL], basic_auth=(ELASTICSEARCH_USER, ELASTICSEARCH_PASSWORD), ) elif ELASTICSEARCH_API_KEY: - elasticsearch_client = Elasticsearch( - hosts=[ELASTICSEARCH_URL], api_key=ELASTICSEARCH_API_KEY - ) + es = Elasticsearch(hosts=[ELASTICSEARCH_URL], api_key=ELASTICSEARCH_API_KEY) else: raise ValueError( "Please provide either ELASTICSEARCH_USER or ELASTICSEARCH_API_KEY" @@ -33,27 +41,45 @@ def install_elser(): + # This script is re-entered on ctrl-c or someone just running it twice. + # Hence, both steps need to be careful about being potentially redundant. + + # Step 1: Ensure ELSER_MODEL is defined try: - elasticsearch_client.ml.get_trained_models(model_id=ELSER_MODEL) - print(f'"{ELSER_MODEL}" model is available') + es.ml.get_trained_models(model_id=ELSER_MODEL) except NotFoundError: print(f'"{ELSER_MODEL}" model not available, downloading it now') - elasticsearch_client.ml.put_trained_model( + es.ml.put_trained_model( model_id=ELSER_MODEL, input={"field_names": ["text_field"]} ) - while True: - status = elasticsearch_client.ml.get_trained_models( - model_id=ELSER_MODEL, include="definition_status" - ) - if status["trained_model_configs"][0]["fully_defined"]: - # model is ready - break - time.sleep(1) - - print("Model downloaded, starting deployment") - elasticsearch_client.ml.start_trained_model_deployment( - model_id=ELSER_MODEL, wait_for="fully_allocated" + + while True: + status = es.ml.get_trained_models( + model_id=ELSER_MODEL, include="definition_status" ) + if status["trained_model_configs"][0]["fully_defined"]: + break + time.sleep(1) + + # Step 2: Ensure ELSER_MODEL is fully allocated + if not is_elser_fully_allocated(): + try: + es.ml.start_trained_model_deployment( + model_id=ELSER_MODEL, wait_for="fully_allocated" + ) + print(f'"{ELSER_MODEL}" model is deployed') + except BadRequestError: + # Already started, and likely fully allocated + pass + + print(f'"{ELSER_MODEL}" model is ready') + + +def is_elser_fully_allocated(): + stats = es.ml.get_trained_models_stats(model_id=ELSER_MODEL) + deployment_stats = stats["trained_model_stats"][0].get("deployment_stats", {}) + allocation_status = deployment_stats.get("allocation_status", {}) + return allocation_status.get("state") == "fully_allocated" def main(): @@ -82,20 +108,85 @@ def main(): print(f"Split {len(workplace_docs)} documents into {len(docs)} chunks") - print(f"Creating Elasticsearch sparse vector store in {ELASTICSEARCH_URL}") + print(f"Creating Elasticsearch sparse vector store for {ELASTICSEARCH_URL}") - elasticsearch_client.indices.delete(index=INDEX, ignore_unavailable=True) - - ElasticsearchStore.from_documents( - docs, - es_connection=elasticsearch_client, + store = ElasticsearchStore( + es_connection=es, index_name=INDEX, strategy=ElasticsearchStore.SparseVectorRetrievalStrategy(model_id=ELSER_MODEL), - bulk_kwargs={ - "request_timeout": 60, - }, ) + # The first call creates ML tasks to support the index, and typically fails + # with the default 10-second timeout, at least when Elasticsearch is a + # container running on Apple Silicon. + # + # Once elastic/elasticsearch#107077 is fixed, we can use bulk_kwargs to + # adjust the timeout. + + print(f"Adding documents to index {INDEX}") + + if stdout.isatty(): + spinner = Halo(text="Processing bulk operation", spinner="dots") + spinner.start() + + try: + es.indices.delete(index=INDEX, ignore_unavailable=True) + store.add_documents(list(docs)) + except BadRequestError: + # This error means the index already exists + pass + except (ConnectionTimeout, ApiError) as e: + if isinstance(e, ApiError) and e.status_code != 408: + raise + warn(f"Error occurred, will retry after ML jobs complete: {e}") + await_ml_tasks() + es.indices.delete(index=INDEX, ignore_unavailable=True) + store.add_documents(list(docs)) + + if stdout.isatty(): + spinner.stop() + + print(f"Documents added to index {INDEX}") + + +def await_ml_tasks(max_timeout=1200, interval=5): + """ + Waits for all machine learning tasks to complete within a specified timeout period. + + Parameters: + max_timeout (int): Maximum time to wait for tasks to complete, in seconds. + interval (int): Time to wait between status checks, in seconds. + + Raises: + TimeoutError: If the timeout is reached and machine learning tasks are still running. + """ + start_time = time.time() + ml_tasks = get_ml_tasks() + if not ml_tasks: + return # likely a lost race on tasks + + print(f"Awaiting {len(ml_tasks)} ML tasks") + + while time.time() - start_time < max_timeout: + ml_tasks = get_ml_tasks() + if not ml_tasks: + return + time.sleep(interval) + + raise TimeoutError( + f"Timeout reached. ML tasks are still running: {', '.join(ml_tasks)}" + ) + + +def get_ml_tasks(): + """Return a list of ML task actions from the ES tasks API.""" + tasks = [] + resp = es.tasks.list(detailed=True, actions=["cluster:monitor/xpack/ml/*"]) + for node_info in resp["nodes"].values(): + for task_info in node_info.get("tasks", {}).values(): + tasks.append(task_info["action"]) + return tasks + # Unless we run through flask, we can miss critical settings or telemetry signals. if __name__ == "__main__": diff --git a/example-apps/chatbot-rag-app/docker-compose.yml b/example-apps/chatbot-rag-app/docker-compose.yml index 3f0f7dcf..5a9e781d 100644 --- a/example-apps/chatbot-rag-app/docker-compose.yml +++ b/example-apps/chatbot-rag-app/docker-compose.yml @@ -1,14 +1,12 @@ name: chatbot-rag-app services: - ingest-data: + create-index: image: ghcr.io/elastic/elasticsearch-labs/chatbot-rag-app build: context: . - container_name: ingest-data + container_name: create-index restart: 'no' # no need to re-ingest on successive runs - environment: - FLASK_APP: api/app.py env_file: - .env command: flask create-index @@ -20,7 +18,7 @@ services: api-frontend: depends_on: - ingest-data: + create-index: condition: service_completed_successfully container_name: api-frontend image: ghcr.io/elastic/elasticsearch-labs/chatbot-rag-app diff --git a/example-apps/chatbot-rag-app/env.example b/example-apps/chatbot-rag-app/env.example index 640e2b56..0cd2fb75 100644 --- a/example-apps/chatbot-rag-app/env.example +++ b/example-apps/chatbot-rag-app/env.example @@ -1,5 +1,10 @@ # Make a copy of this file with the name .env and assign values to variables +# Location of the application routes +FLASK_APP=api/app.py +# Ensure print statements appear as they happen +PYTHONUNBUFFERED=1 + # How you connect to Elasticsearch: change details to your instance ELASTICSEARCH_URL=http://localhost:9200 ELASTICSEARCH_USER=elastic diff --git a/example-apps/chatbot-rag-app/requirements.in b/example-apps/chatbot-rag-app/requirements.in index b50a986c..ed60d8cf 100644 --- a/example-apps/chatbot-rag-app/requirements.in +++ b/example-apps/chatbot-rag-app/requirements.in @@ -5,6 +5,7 @@ langchain-elasticsearch tiktoken flask flask-cors +halo # LLM dependencies langchain-openai diff --git a/example-apps/chatbot-rag-app/requirements.txt b/example-apps/chatbot-rag-app/requirements.txt index 6a49af4a..917a9b98 100644 --- a/example-apps/chatbot-rag-app/requirements.txt +++ b/example-apps/chatbot-rag-app/requirements.txt @@ -4,9 +4,9 @@ # # pip-compile # -aiohappyeyeballs==2.4.4 +aiohappyeyeballs==2.4.6 # via aiohttp -aiohttp==3.11.11 +aiohttp==3.11.12 # via # langchain # langchain-community @@ -22,17 +22,17 @@ attrs==25.1.0 # via aiohttp blinker==1.9.0 # via flask -boto3==1.36.7 +boto3==1.36.24 # via # langchain-aws # langtrace-python-sdk -botocore==1.36.7 +botocore==1.36.24 # via # boto3 # s3transfer cachetools==5.5.1 # via google-auth -certifi==2024.12.14 +certifi==2025.1.31 # via # elastic-transport # httpcore @@ -43,10 +43,13 @@ charset-normalizer==3.4.1 # via requests click==8.1.8 # via flask -cohere==5.13.11 +cohere==5.13.12 # via langchain-cohere colorama==0.4.6 - # via langtrace-python-sdk + # via + # halo + # langtrace-python-sdk + # log-symbols dataclasses-json==0.6.7 # via langchain-community deprecated==1.2.18 @@ -59,7 +62,7 @@ distro==1.9.0 # via openai docstring-parser==0.16 # via google-cloud-aiplatform -elastic-opentelemetry==0.6.1 +elastic-opentelemetry==0.7.0 # via -r requirements.in elastic-transport==8.17.0 # via elasticsearch @@ -83,7 +86,7 @@ frozenlist==1.5.0 # via # aiohttp # aiosignal -fsspec==2024.12.0 +fsspec==2025.2.0 # via # huggingface-hub # langtrace-python-sdk @@ -102,7 +105,7 @@ google-auth==2.38.0 # google-cloud-core # google-cloud-resource-manager # google-cloud-storage -google-cloud-aiplatform==1.78.0 +google-cloud-aiplatform==1.81.0 # via langchain-google-vertexai google-cloud-bigquery==3.29.0 # via google-cloud-aiplatform @@ -124,7 +127,7 @@ google-resumable-media==2.7.2 # via # google-cloud-bigquery # google-cloud-storage -googleapis-common-protos[grpc]==1.66.0 +googleapis-common-protos[grpc]==1.67.0 # via # google-api-core # grpc-google-iam-v1 @@ -144,6 +147,8 @@ grpcio-status==1.70.0 # via google-api-core h11==0.14.0 # via httpcore +halo==0.0.31 + # via -r requirements.in httpcore==1.0.7 # via httpx httpx==0.28.1 @@ -159,7 +164,7 @@ httpx-sse==0.4.0 # langchain-community # langchain-google-vertexai # langchain-mistralai -huggingface-hub==0.27.1 +huggingface-hub==0.29.0 # via # tokenizers # transformers @@ -185,17 +190,17 @@ jsonpatch==1.33 # via langchain-core jsonpointer==3.0.0 # via jsonpatch -langchain==0.3.15 +langchain==0.3.19 # via # -r requirements.in # langchain-community -langchain-aws==0.2.11 +langchain-aws==0.2.13 # via -r requirements.in langchain-cohere==0.4.2 # via -r requirements.in -langchain-community==0.3.15 +langchain-community==0.3.18 # via langchain-cohere -langchain-core==0.3.31 +langchain-core==0.3.37 # via # langchain # langchain-aws @@ -208,26 +213,28 @@ langchain-core==0.3.31 # langchain-text-splitters langchain-elasticsearch==0.3.2 # via -r requirements.in -langchain-google-vertexai==2.0.12 +langchain-google-vertexai==2.0.13 # via -r requirements.in -langchain-mistralai==0.2.4 +langchain-mistralai==0.2.6 # via -r requirements.in -langchain-openai==0.3.2 +langchain-openai==0.3.6 # via -r requirements.in -langchain-text-splitters==0.3.5 +langchain-text-splitters==0.3.6 # via langchain -langsmith==0.3.2 +langsmith==0.3.8 # via # langchain # langchain-community # langchain-core -langtrace-python-sdk==3.5.0 +langtrace-python-sdk==3.6.2 # via -r requirements.in +log-symbols==0.0.14 + # via halo markupsafe==3.0.2 # via # jinja2 # werkzeug -marshmallow==3.26.0 +marshmallow==3.26.1 # via dataclasses-json multidict==6.1.0 # via @@ -235,7 +242,7 @@ multidict==6.1.0 # yarl mypy-extensions==1.0.0 # via typing-inspect -numpy==2.2.2 +numpy==2.2.3 # via # elasticsearch # langchain @@ -243,9 +250,9 @@ numpy==2.2.2 # langchain-community # shapely # transformers -openai==1.60.2 +openai==1.63.2 # via langchain-openai -opentelemetry-api==1.29.0 +opentelemetry-api==1.30.0 # via # elastic-opentelemetry # langtrace-python-sdk @@ -257,31 +264,31 @@ opentelemetry-api==1.29.0 # opentelemetry-resourcedetector-gcp # opentelemetry-sdk # opentelemetry-semantic-conventions -opentelemetry-exporter-otlp==1.29.0 +opentelemetry-exporter-otlp==1.30.0 # via elastic-opentelemetry -opentelemetry-exporter-otlp-proto-common==1.29.0 +opentelemetry-exporter-otlp-proto-common==1.30.0 # via # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -opentelemetry-exporter-otlp-proto-grpc==1.29.0 +opentelemetry-exporter-otlp-proto-grpc==1.30.0 # via # langtrace-python-sdk # opentelemetry-exporter-otlp -opentelemetry-exporter-otlp-proto-http==1.29.0 +opentelemetry-exporter-otlp-proto-http==1.30.0 # via # langtrace-python-sdk # opentelemetry-exporter-otlp -opentelemetry-instrumentation==0.50b0 +opentelemetry-instrumentation==0.51b0 # via # elastic-opentelemetry # langtrace-python-sdk # opentelemetry-instrumentation-sqlalchemy # opentelemetry-instrumentation-system-metrics -opentelemetry-instrumentation-sqlalchemy==0.50b0 +opentelemetry-instrumentation-sqlalchemy==0.51b0 # via langtrace-python-sdk -opentelemetry-instrumentation-system-metrics==0.50b0 +opentelemetry-instrumentation-system-metrics==0.51b0 # via elastic-opentelemetry -opentelemetry-proto==1.29.0 +opentelemetry-proto==1.30.0 # via # opentelemetry-exporter-otlp-proto-common # opentelemetry-exporter-otlp-proto-grpc @@ -290,7 +297,7 @@ opentelemetry-resource-detector-azure==0.1.5 # via elastic-opentelemetry opentelemetry-resourcedetector-gcp==1.8.0a0 # via elastic-opentelemetry -opentelemetry-sdk==1.29.0 +opentelemetry-sdk==1.30.0 # via # elastic-opentelemetry # langtrace-python-sdk @@ -301,7 +308,7 @@ opentelemetry-sdk==1.29.0 # opentelemetry-sdk-extension-aws opentelemetry-sdk-extension-aws==2.1.0 # via elastic-opentelemetry -opentelemetry-semantic-conventions==0.50b0 +opentelemetry-semantic-conventions==0.51b0 # via # elastic-opentelemetry # opentelemetry-instrumentation @@ -407,19 +414,23 @@ s3transfer==0.11.2 # via boto3 safetensors==0.5.2 # via transformers -sentry-sdk==2.20.0 +sentry-sdk==2.22.0 # via langtrace-python-sdk -shapely==2.0.6 +shapely==2.0.7 # via google-cloud-aiplatform simsimd==6.2.1 # via elasticsearch six==1.17.0 - # via python-dateutil + # via + # halo + # python-dateutil sniffio==1.3.1 # via # anyio # openai -sqlalchemy==2.0.37 +spinners==0.0.24 + # via halo +sqlalchemy==2.0.38 # via # langchain # langchain-community @@ -429,7 +440,9 @@ tenacity==9.0.0 # langchain # langchain-community # langchain-core -tiktoken==0.8.0 +termcolor==2.5.0 + # via halo +tiktoken==0.9.0 # via # -r requirements.in # langchain-openai @@ -444,9 +457,9 @@ tqdm==4.67.1 # huggingface-hub # openai # transformers -trace-attributes==7.1.1 +trace-attributes==7.2.0 # via langtrace-python-sdk -transformers==4.48.1 +transformers==4.49.0 # via langtrace-python-sdk types-pyyaml==6.0.12.20241230 # via langchain-cohere @@ -493,27 +506,27 @@ zstandard==0.23.0 # The following packages are considered to be unsafe in a requirements file: # setuptools -opentelemetry-instrumentation-asyncio==0.50b0 -opentelemetry-instrumentation-dbapi==0.50b0 -opentelemetry-instrumentation-logging==0.50b0 -opentelemetry-instrumentation-sqlite3==0.50b0 -opentelemetry-instrumentation-threading==0.50b0 -opentelemetry-instrumentation-urllib==0.50b0 -opentelemetry-instrumentation-wsgi==0.50b0 -opentelemetry-instrumentation-aiohttp-client==0.50b0 -opentelemetry-instrumentation-aiohttp-server==0.50b0 -opentelemetry-instrumentation-boto3sqs==0.50b0 -opentelemetry-instrumentation-botocore==0.50b0 -opentelemetry-instrumentation-click==0.50b0 -opentelemetry-instrumentation-elasticsearch==0.50b0 -opentelemetry-instrumentation-flask==0.50b0 -opentelemetry-instrumentation-grpc==0.50b0 -opentelemetry-instrumentation-httpx==0.50b0 -opentelemetry-instrumentation-jinja2==0.50b0 -opentelemetry-instrumentation-requests==0.50b0 -opentelemetry-instrumentation-sqlalchemy==0.50b0 -opentelemetry-instrumentation-system-metrics==0.50b0 -opentelemetry-instrumentation-tortoiseorm==0.50b0 -opentelemetry-instrumentation-urllib3==0.50b0 +opentelemetry-instrumentation-asyncio==0.51b0 +opentelemetry-instrumentation-dbapi==0.51b0 +opentelemetry-instrumentation-logging==0.51b0 +opentelemetry-instrumentation-sqlite3==0.51b0 +opentelemetry-instrumentation-threading==0.51b0 +opentelemetry-instrumentation-urllib==0.51b0 +opentelemetry-instrumentation-wsgi==0.51b0 +opentelemetry-instrumentation-aiohttp-client==0.51b0 +opentelemetry-instrumentation-aiohttp-server==0.51b0 +opentelemetry-instrumentation-boto3sqs==0.51b0 +opentelemetry-instrumentation-botocore==0.51b0 +opentelemetry-instrumentation-click==0.51b0 +opentelemetry-instrumentation-elasticsearch==0.51b0 +opentelemetry-instrumentation-flask==0.51b0 +opentelemetry-instrumentation-grpc==0.51b0 +opentelemetry-instrumentation-httpx==0.51b0 +opentelemetry-instrumentation-jinja2==0.51b0 +opentelemetry-instrumentation-requests==0.51b0 +opentelemetry-instrumentation-sqlalchemy==0.51b0 +opentelemetry-instrumentation-system-metrics==0.51b0 +opentelemetry-instrumentation-tortoiseorm==0.51b0 +opentelemetry-instrumentation-urllib3==0.51b0 elastic-opentelemetry-instrumentation-openai==0.6.0 setuptools