diff --git a/.github/workflows/e2e_archgw.yml b/.github/workflows/e2e_archgw.yml index 5897e5f2..64454d57 100644 --- a/.github/workflows/e2e_archgw.yml +++ b/.github/workflows/e2e_archgw.yml @@ -24,7 +24,7 @@ jobs: - name: build arch docker image run: | - cd ../../ && docker build -f arch/Dockerfile . -t katanemo/archgw -t katanemo/archgw:0.2.8 + cd ../../ && docker build -f arch/Dockerfile . -t katanemo/archgw -t katanemo/archgw:0.2.8 -t katanemo/archgw:latest - name: start archgw env: diff --git a/.github/workflows/rust_tests.yml b/.github/workflows/rust_tests.yml index 66d261a3..ed031167 100644 --- a/.github/workflows/rust_tests.yml +++ b/.github/workflows/rust_tests.yml @@ -24,7 +24,8 @@ jobs: run: rustup target add wasm32-wasip1 - name: Build wasm module - run: cargo build --release --target=wasm32-wasip1 + run: | + cargo build --release --target=wasm32-wasip1 -p llm_gateway -p prompt_gateway - name: Run unit tests run: cargo test --lib diff --git a/arch/Dockerfile b/arch/Dockerfile index 7f933da5..b3117630 100644 --- a/arch/Dockerfile +++ b/arch/Dockerfile @@ -1,11 +1,11 @@ -# build filter using rust toolchain +# build docker image for arch gateway FROM rust:1.82.0 as builder RUN rustup -v target add wasm32-wasip1 WORKDIR /arch COPY crates . -RUN cd prompt_gateway && cargo build --release --target wasm32-wasip1 -RUN cd llm_gateway && cargo build --release --target wasm32-wasip1 +RUN cargo build --release --target wasm32-wasip1 -p prompt_gateway -p llm_gateway +RUN cargo build --release -p brightstaff # copy built filter into envoy image FROM docker.io/envoyproxy/envoy:v1.32-latest as envoy @@ -13,20 +13,27 @@ FROM docker.io/envoyproxy/envoy:v1.32-latest as envoy #Build config generator, so that we have a single build image for both Rust and Python FROM python:3.12-slim as arch -RUN apt-get update && apt-get install -y gettext-base curl && apt-get clean && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y supervisor gettext-base curl && apt-get clean && rm -rf /var/lib/apt/lists/* COPY --from=builder /arch/target/wasm32-wasip1/release/prompt_gateway.wasm /etc/envoy/proxy-wasm-plugins/prompt_gateway.wasm COPY --from=builder /arch/target/wasm32-wasip1/release/llm_gateway.wasm /etc/envoy/proxy-wasm-plugins/llm_gateway.wasm +COPY --from=builder /arch/target/release/brightstaff /app/brightstaff COPY --from=envoy /usr/local/bin/envoy /usr/local/bin/envoy + WORKDIR /app COPY arch/requirements.txt . RUN pip install -r requirements.txt COPY arch/tools/cli/config_generator.py . COPY arch/envoy.template.yaml . COPY arch/arch_config_schema.yaml . +COPY arch/supervisord.conf /etc/supervisor/conf.d/supervisord.conf RUN pip install requests RUN touch /var/log/envoy.log +RUN mkdir -p /var/log/supervisor/ +RUN touch /var/log/supervisor/supervisord.log + +ENTRYPOINT ["sh","-c", "/usr/bin/supervisord"] # ENTRYPOINT ["sh","-c", "python config_generator.py && envsubst < /etc/envoy/envoy.yaml > /etc/envoy.env_sub.yaml && envoy -c /etc/envoy.env_sub.yaml --log-level trace 2>&1 | tee /var/log/envoy.log"] -ENTRYPOINT ["sh","-c", "python config_generator.py && envsubst < /etc/envoy/envoy.yaml > /etc/envoy.env_sub.yaml && envoy -c /etc/envoy.env_sub.yaml --component-log-level wasm:info 2>&1 | tee /var/log/envoy.log"] +# ENTRYPOINT ["sh","-c", "python config_generator.py && envsubst < /etc/envoy/envoy.yaml > /etc/envoy.env_sub.yaml && envoy -c /etc/envoy.env_sub.yaml --component-log-level wasm:info 2>&1 | tee /var/log/envoy.log"] diff --git a/arch/arch_config_schema.yaml b/arch/arch_config_schema.yaml index 59276589..a72db695 100644 --- a/arch/arch_config_schema.yaml +++ b/arch/arch_config_schema.yaml @@ -90,6 +90,8 @@ properties: - https http_host: type: string + usage: + type: string additionalProperties: false required: - name @@ -225,6 +227,12 @@ properties: enum: - llm - prompt + routing: + type: object + properties: + model: + type: string + additionalProperties: false prompt_guards: type: object properties: diff --git a/arch/envoy.template.yaml b/arch/envoy.template.yaml index cac17187..3a2856b6 100644 --- a/arch/envoy.template.yaml +++ b/arch/envoy.template.yaml @@ -328,11 +328,15 @@ static_resources: domains: - "*" routes: + - match: + prefix: "/healthz" + direct_response: + status: 200 - match: prefix: "/" route: auto_host_rewrite: true - cluster: arch_listener_llm + cluster: bright_staff timeout: {{ llm_gateway_listener.timeout }} http_filters: - name: envoy.filters.http.router @@ -380,12 +384,6 @@ static_resources: domains: - "*" routes: - - match: - prefix: "/healthz" - route: - auto_host_rewrite: true - cluster: openai - timeout: 60s {% for provider in arch_llm_providers %} # if endpoint is set then use custom cluster for upstream llm {% if provider.endpoint %} @@ -615,6 +613,38 @@ static_resources: port_value: 11000 hostname: arch_internal + - name: bright_staff + connect_timeout: 0.5s + type: LOGICAL_DNS + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: bright_staff + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 0.0.0.0 + port_value: 9091 + hostname: localhost + + - name: router_model_host + connect_timeout: 0.5s + type: LOGICAL_DNS + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: router_model_host + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 34.30.16.38 + port_value: 8000 + hostname: router_model_host + - name: arch_prompt_gateway_listener connect_timeout: 0.5s type: LOGICAL_DNS diff --git a/arch/supervisord.conf b/arch/supervisord.conf new file mode 100644 index 00000000..b20a510a --- /dev/null +++ b/arch/supervisord.conf @@ -0,0 +1,16 @@ +[supervisord] +nodaemon=true + +[program:brightstaff] +command=sh -c "/app/brightstaff 2>&1 | tee /var/log/brightstaff.log" +stdout_logfile=/dev/stdout +redirect_stderr=true +stdout_logfile_maxbytes=0 +stderr_logfile_maxbytes=0 + +[program:envoy] +command=/bin/sh -c "python /app/config_generator.py && envsubst < /etc/envoy/envoy.yaml > /etc/envoy.env_sub.yaml && envoy -c /etc/envoy.env_sub.yaml 2>&1 | tee /var/log//envoy.log" +stdout_logfile=/dev/stdout +redirect_stderr=true +stdout_logfile_maxbytes=0 +stderr_logfile_maxbytes=0 diff --git a/arch/tools/cli/core.py b/arch/tools/cli/core.py index b0a6e58c..47590f2f 100644 --- a/arch/tools/cli/core.py +++ b/arch/tools/cli/core.py @@ -6,6 +6,7 @@ import sys import yaml from cli.utils import getLogger from cli.consts import ( + ARCHGW_DOCKER_IMAGE, ARCHGW_DOCKER_NAME, KATANEMO_LOCAL_MODEL_LIST, ) @@ -55,7 +56,9 @@ def start_arch(arch_config_file, env, log_timeout=120, foreground=False): path (str): The path where the prompt_config.yml file is located. log_timeout (int): Time in seconds to show logs before checking for healthy state. """ - log.info("Starting arch gateway") + log.info( + f"Starting arch gateway, image name: {ARCHGW_DOCKER_NAME}, tag: {ARCHGW_DOCKER_IMAGE}" + ) try: archgw_container_status = docker_container_status(ARCHGW_DOCKER_NAME) @@ -92,10 +95,15 @@ def start_arch(arch_config_file, env, log_timeout=120, foreground=False): current_time = time.time() elapsed_time = current_time - start_time + if archgw_status == "exited": + log.info("archgw container exited unexpectedly.") + stream_gateway_logs(follow=False) + sys.exit(1) + # Check if timeout is reached if elapsed_time > log_timeout: log.info(f"stopping log monitoring after {log_timeout} seconds.") - break + sys.exit(1) if prompt_gateway_health_check_status or llm_gateway_health_check_status: log.info("archgw is running and is healthy!") @@ -109,27 +117,27 @@ def start_arch(arch_config_file, env, log_timeout=120, foreground=False): except KeyboardInterrupt: log.info("Keyboard interrupt received, stopping arch gateway service.") - stop_arch() + stop_docker_container() -def stop_arch(): +def stop_docker_container(service=ARCHGW_DOCKER_NAME): """ Shutdown all Docker Compose services by running `docker-compose down`. Args: path (str): The path where the docker-compose.yml file is located. """ - log.info("Shutting down arch gateway service.") + log.info(f"Shutting down {service} service.") try: subprocess.run( - ["docker", "stop", ARCHGW_DOCKER_NAME], + ["docker", "stop", service], ) subprocess.run( - ["docker", "rm", ARCHGW_DOCKER_NAME], + ["docker", "rm", service], ) - log.info("Successfully shut down arch gateway service.") + log.info(f"Successfully shut down {service} service.") except subprocess.CalledProcessError as e: log.info(f"Failed to shut down services: {str(e)}") diff --git a/arch/tools/cli/docker_cli.py b/arch/tools/cli/docker_cli.py index 6edfb8dc..b5a31040 100644 --- a/arch/tools/cli/docker_cli.py +++ b/arch/tools/cli/docker_cli.py @@ -3,7 +3,10 @@ import json import sys import requests -from cli.consts import ARCHGW_DOCKER_IMAGE, ARCHGW_DOCKER_NAME +from cli.consts import ( + ARCHGW_DOCKER_IMAGE, + ARCHGW_DOCKER_NAME, +) from cli.utils import getLogger log = getLogger(__name__) @@ -54,7 +57,6 @@ def docker_start_archgw_detached( port_mappings_args = [item for port in port_mappings for item in ("-p", port)] volume_mappings = [ - f"{logs_path_abs}:/var/log:rw", f"{arch_config_file}:/app/arch_config.yaml:ro", # "/Users/adilhafeez/src/intelligent-prompt-gateway/crates/target/wasm32-wasip1/release:/etc/envoy/proxy-wasm-plugins:ro", ] @@ -90,7 +92,7 @@ def health_check_endpoint(endpoint: str) -> bool: return False -def stream_gateway_logs(follow): +def stream_gateway_logs(follow, service="archgw"): """ Stream logs from the arch gateway service. """ @@ -99,7 +101,7 @@ def stream_gateway_logs(follow): options = ["docker", "logs"] if follow: options.append("-f") - options.append(ARCHGW_DOCKER_NAME) + options.append(service) try: # Run `docker-compose logs` to stream logs from the gateway service subprocess.run( diff --git a/arch/tools/cli/main.py b/arch/tools/cli/main.py index 6541b51a..001f3d9c 100644 --- a/arch/tools/cli/main.py +++ b/arch/tools/cli/main.py @@ -16,7 +16,7 @@ from cli.core import ( start_arch_modelserver, stop_arch_modelserver, start_arch, - stop_arch, + stop_docker_container, download_models_from_hf, ) from cli.consts import ( @@ -51,6 +51,18 @@ def get_version(): return "version not found" +def verify_service_name(service): + """Verify if the service name is valid.""" + if service not in [ + SERVICE_NAME_ARCHGW, + SERVICE_NAME_MODEL_SERVER, + SERVICE_ALL, + ]: + print(f"Error: Invalid service {service}. Exiting") + sys.exit(1) + return True + + @click.group(invoke_without_command=True) @click.option("--version", is_flag=True, help="Show the archgw cli version and exit.") @click.pass_context @@ -75,9 +87,8 @@ def main(ctx, version): ) def build(service): """Build Arch from source. Must be in root of cloned repo.""" - if service not in [SERVICE_NAME_ARCHGW, SERVICE_NAME_MODEL_SERVER, SERVICE_ALL]: - print(f"Error: Invalid service {service}. Exiting") - sys.exit(1) + verify_service_name(service) + # Check if /arch/Dockerfile exists if service == SERVICE_NAME_ARCHGW or service == SERVICE_ALL: if os.path.exists(ARCHGW_DOCKERFILE): @@ -146,9 +157,7 @@ def build(service): ) def up(file, path, service, foreground): """Starts Arch.""" - if service not in [SERVICE_NAME_ARCHGW, SERVICE_NAME_MODEL_SERVER, SERVICE_ALL]: - log.info(f"Error: Invalid service {service}. Exiting") - sys.exit(1) + verify_service_name(service) if service == SERVICE_ALL and foreground: # foreground can only be specified when starting individual services @@ -156,7 +165,7 @@ def up(file, path, service, foreground): sys.exit(1) if service == SERVICE_NAME_MODEL_SERVER: - log.info("Download archgw models from HuggingFace...") + log.info("Download models from HuggingFace...") download_models_from_hf() start_arch_modelserver(foreground) return @@ -186,8 +195,6 @@ def up(file, path, service, foreground): log.info(f"Validation stderr: {validation_stderr}") sys.exit(1) - log.info("Starting arch model server and arch gateway") - # Set the ARCH_CONFIG_FILE environment variable env_stage = { "OTEL_TRACING_HTTP_ENDPOINT": "http://host.docker.internal:4318/v1/traces", @@ -210,7 +217,6 @@ def up(file, path, service, foreground): else: app_env_file = os.path.abspath(os.path.join(path, ".env")) - print(f"app_env_file: {app_env_file}") if not os.path.exists( app_env_file ): # check to see if the environment variables in the current environment or not @@ -248,17 +254,15 @@ def up(file, path, service, foreground): def down(service): """Stops Arch.""" - if service not in [SERVICE_NAME_ARCHGW, SERVICE_NAME_MODEL_SERVER, SERVICE_ALL]: - log.info(f"Error: Invalid service {service}. Exiting") - sys.exit(1) + verify_service_name(service) if service == SERVICE_NAME_MODEL_SERVER: stop_arch_modelserver() elif service == SERVICE_NAME_ARCHGW: - stop_arch() + stop_docker_container() else: stop_arch_modelserver() - stop_arch() + stop_docker_container(SERVICE_NAME_ARCHGW) @click.command() diff --git a/crates/Cargo.lock b/crates/Cargo.lock index b585ef6e..8ce8097e 100644 --- a/crates/Cargo.lock +++ b/crates/Cargo.lock @@ -17,9 +17,24 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli", + "gimli 0.28.1", ] +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli 0.31.1", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.3.8" @@ -82,9 +97,15 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -102,12 +123,33 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line 0.24.2", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit-set" version = "0.5.3" @@ -144,6 +186,38 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brightstaff" +version = "0.1.0" +dependencies = [ + "bytes", + "common", + "eventsource-client", + "eventsource-stream", + "futures", + "futures-util", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-otlp", + "opentelemetry-stdout", + "opentelemetry_sdk", + "pretty_assertions", + "reqwest", + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", +] + [[package]] name = "bstr" version = "1.10.0" @@ -151,7 +225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata", + "regex-automata 0.4.8", "serde", ] @@ -169,15 +243,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.1.30" +version = "1.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ "jobserver", "libc", @@ -196,6 +270,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "num-traits", +] + [[package]] name = "clap" version = "2.34.0" @@ -228,16 +311,32 @@ dependencies = [ "log", "pretty_assertions", "proxy-wasm", - "rand", + "rand 0.8.5", "serde", "serde_json", "serde_yaml", - "thiserror", + "thiserror 1.0.64", "tiktoken-rs", "url", "urlencoding", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpp_demangle" version = "0.4.4" @@ -289,7 +388,7 @@ dependencies = [ "cranelift-control", "cranelift-entity", "cranelift-isle", - "gimli", + "gimli 0.28.1", "hashbrown 0.14.5", "log", "regalloc2", @@ -487,7 +586,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -542,6 +641,34 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "eventsource-client" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75810b04951eb0b44bd2800345f6ee15321be90894568bd93aaef9abad1b646c" +dependencies = [ + "base64 0.22.1", + "futures", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "hyper-timeout 0.4.1", + "log", + "pin-project", + "rand 0.8.5", + "tokio", +] + +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -558,6 +685,12 @@ dependencies = [ "regex", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fnv" version = "1.0.7" @@ -570,6 +703,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -627,6 +775,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -648,6 +807,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -696,7 +856,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -706,10 +878,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" dependencies = [ "fallible-iterator", - "indexmap", + "indexmap 2.6.0", "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "governor" version = "0.6.3" @@ -724,6 +908,44 @@ dependencies = [ "spinning_top", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.8.2" @@ -734,6 +956,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.13.2" @@ -793,6 +1021,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.1.0" @@ -804,6 +1043,191 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.9", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "rustls-native-certs", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.26", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.32", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.6.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.6.0", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -919,7 +1343,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -949,6 +1373,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -960,6 +1394,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "itertools" version = "0.12.1" @@ -1004,6 +1444,16 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1018,9 +1468,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" @@ -1058,18 +1508,18 @@ dependencies = [ "common", "derivative", "governor", - "http", + "http 1.1.0", "log", "md5", "proxy-wasm", "proxy-wasm-test-framework", - "rand", + "rand 0.8.5", "serde", "serde_json", "serde_yaml", "serial_test", "sha2", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -1097,6 +1547,15 @@ dependencies = [ "libc", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "md5" version = "0.7.0" @@ -1118,12 +1577,61 @@ dependencies = [ "rustix", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + [[package]] name = "more-asserts" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "no-std-compat" version = "0.4.1" @@ -1133,12 +1641,32 @@ dependencies = [ "hashbrown 0.8.2", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonzero_ext" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1156,7 +1684,7 @@ checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "crc32fast", "hashbrown 0.15.0", - "indexmap", + "indexmap 2.6.0", "memchr", ] @@ -1166,6 +1694,146 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "openssl" +version = "0.10.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +dependencies = [ + "bitflags 2.6.0", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e87237e2775f74896f9ad219d26a2081751187eb7c9f5c58dde20a23b95d16c" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "opentelemetry-http" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d7ab32b827b5b495bd90fa95a6cb65ccc293555dcc3199ae2937d2d237c8ed" +dependencies = [ + "async-trait", + "bytes", + "http 1.1.0", + "opentelemetry", + "reqwest", + "tracing", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d899720fe06916ccba71c01d04ecd77312734e2de3467fd30d9d580c8ce85656" +dependencies = [ + "futures-core", + "http 1.1.0", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror 2.0.12", + "tokio", + "tonic", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c40da242381435e18570d5b9d50aca2a4f4f4d8e146231adb4e7768023309b3" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry-stdout" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e27d446dabd68610ef0b77d07b102ecde827a4596ea9c01a4d3811e945b286" +dependencies = [ + "chrono", + "futures-util", + "opentelemetry", + "opentelemetry_sdk", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdefb21d1d47394abc1ba6c57363ab141be19e27cc70d0e422b7f303e4d290b" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "opentelemetry", + "percent-encoding", + "rand 0.9.1", + "serde_json", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1186,7 +1854,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1201,6 +1869,26 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1297,19 +1985,42 @@ dependencies = [ "common", "derivative", "governor", - "http", + "http 1.1.0", "log", "md5", "pretty_assertions", "proxy-wasm", "proxy-wasm-test-framework", - "rand", + "rand 0.8.5", "serde", "serde_json", "serde_yaml", "serial_test", "sha2", - "thiserror", + "thiserror 1.0.64", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] @@ -1331,7 +2042,7 @@ dependencies = [ "cfg-if 0.1.10", "lazy_static", "more-asserts", - "rand", + "rand 0.8.5", "structopt", "wasmtime", ] @@ -1354,6 +2065,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -1361,8 +2078,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1372,7 +2099,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1381,7 +2118,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", ] [[package]] @@ -1419,9 +2165,9 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -1445,8 +2191,17 @@ checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -1457,15 +2212,82 @@ checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.9", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls 0.27.5", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower 0.5.2", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1491,6 +2313,94 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.1", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" version = "1.0.18" @@ -1506,18 +2416,60 @@ dependencies = [ "sdd", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sdd" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.23" @@ -1529,29 +2481,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "serde_json" -version = "1.0.130" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610f75ff4a8e3cb29b85da56eabdd1bff5b06739059a4b8e2967fef32e5d9944" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -1568,13 +2520,25 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -1603,7 +2567,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1617,12 +2581,30 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -1647,6 +2629,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spinning_top" version = "0.3.0" @@ -1698,6 +2690,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -1711,15 +2709,24 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -1728,7 +2735,28 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -1737,6 +2765,19 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -1761,7 +2802,16 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.64", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -1772,7 +2822,28 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", ] [[package]] @@ -1782,7 +2853,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c314e7ce51440f9e8f5a497394682a57b7c323d0f4d0a6b1b13c429056e0e234" dependencies = [ "anyhow", - "base64", + "base64 0.21.7", "bstr", "fancy-regex", "lazy_static", @@ -1800,6 +2871,99 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.26", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.19" @@ -1827,13 +2991,171 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-timeout 0.5.2", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd8e764bd6f5813fd8bebc3117875190c5b0415be8f7f8059bffb6ecd979c444" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -1870,6 +3192,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -1905,6 +3233,18 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -1917,12 +3257,101 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + [[package]] name = "wasm-encoder" version = "0.212.0" @@ -1942,6 +3371,19 @@ dependencies = [ "wasmparser 0.219.1", ] +[[package]] +name = "wasm-streams" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.212.0" @@ -1951,7 +3393,7 @@ dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", - "indexmap", + "indexmap 2.6.0", "semver", "serde", ] @@ -1963,7 +3405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5" dependencies = [ "bitflags 2.6.0", - "indexmap", + "indexmap 2.6.0", ] [[package]] @@ -1983,7 +3425,7 @@ version = "23.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe501caefeb9f7b15360bdd7e47ad96e20223846f1c7db485ae5820ba5acc3d2" dependencies = [ - "addr2line", + "addr2line 0.21.0", "anyhow", "async-trait", "bitflags 2.6.0", @@ -1992,9 +3434,9 @@ dependencies = [ "cfg-if 1.0.0", "encoding_rs", "fxprof-processed-profile", - "gimli", + "gimli 0.28.1", "hashbrown 0.14.5", - "indexmap", + "indexmap 2.6.0", "ittapi", "libc", "libm", @@ -2049,7 +3491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dff4d467d6b5bd0d137f5426f45178222e40b59e49ab3a7361420262b9f00df" dependencies = [ "anyhow", - "base64", + "base64 0.21.7", "directories-next", "log", "postcard", @@ -2071,7 +3513,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser", @@ -2097,11 +3539,11 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli", + "gimli 0.28.1", "log", "object", "target-lexicon", - "thiserror", + "thiserror 1.0.64", "wasmparser 0.212.0", "wasmtime-environ", "wasmtime-versioned-export-macros", @@ -2117,8 +3559,8 @@ dependencies = [ "cpp_demangle", "cranelift-bitset", "cranelift-entity", - "gimli", - "indexmap", + "gimli 0.28.1", + "indexmap 2.6.0", "log", "object", "postcard", @@ -2201,7 +3643,7 @@ checksum = "a2bde986038b819bc43a21fef0610aeb47aabfe3ea09ca3533a7b81023b84ec6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2212,7 +3654,7 @@ checksum = "beb1abdc26ddf1d7c819ea0fcbfccb0808410549d28bb3154c9bdb7d11fbcc58" dependencies = [ "anyhow", "cranelift-codegen", - "gimli", + "gimli 0.28.1", "object", "target-lexicon", "wasmparser 0.212.0", @@ -2229,7 +3671,7 @@ checksum = "8f88e49a9b81746ec0cede5505e40a4012c92cb5054cd7ef4300dc57c36f26b1" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap", + "indexmap 2.6.0", "wit-parser", ] @@ -2255,6 +3697,26 @@ dependencies = [ "wast", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2294,7 +3756,7 @@ checksum = "a666bf2cdb838e68b9b8370d7ebf8806b87ccc0d89a634bfc9ed8ffca1f19591" dependencies = [ "anyhow", "cranelift-codegen", - "gimli", + "gimli 0.28.1", "regalloc2", "smallvec", "target-lexicon", @@ -2303,13 +3765,48 @@ dependencies = [ "wasmtime-environ", ] +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2318,7 +3815,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2327,14 +3824,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -2343,48 +3856,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.6.20" @@ -2394,6 +3955,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "wit-parser" version = "0.212.0" @@ -2402,7 +3972,7 @@ checksum = "ceeb0424aa8679f3fcf2d6e3cfa381f3d6fa6179976a2c05a6249dd2bb426716" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.6.0", "log", "semver", "serde", @@ -2450,7 +4020,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "synstructure", ] @@ -2472,7 +4042,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2492,10 +4062,16 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" @@ -2515,7 +4091,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] diff --git a/crates/Cargo.toml b/crates/Cargo.toml index 3ba99280..12eeb3b2 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["llm_gateway", "prompt_gateway", "common"] +members = ["llm_gateway", "prompt_gateway", "common", "brightstaff"] diff --git a/crates/brightstaff/Cargo.toml b/crates/brightstaff/Cargo.toml new file mode 100644 index 00000000..3f51b6a0 --- /dev/null +++ b/crates/brightstaff/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "brightstaff" +version = "0.1.0" +edition = "2021" + +[dependencies] +bytes = "1.10.1" +common = { version = "0.1.0", path = "../common" } +eventsource-client = "0.15.0" +eventsource-stream = "0.2.3" +futures = "0.3.31" +futures-util = "0.3.31" +http-body = "1.0.1" +http-body-util = "0.1.3" +hyper = { version = "1.6.0", features = ["full"] } +hyper-util = "0.1.11" +opentelemetry = "0.29.1" +opentelemetry-http = "0.29.0" +opentelemetry-otlp = {version="0.29.0", features=["trace", "tonic", "grpc-tonic"]} +opentelemetry-stdout = "0.29.0" +opentelemetry_sdk = "0.29.0" +pretty_assertions = "1.4.1" +reqwest = { version = "0.12.15", features = ["stream"] } +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +serde_yaml = "0.9.34" +thiserror = "2.0.12" +tokio = { version = "1.44.2", features = ["full"] } +tokio-stream = "0.1.17" +tracing = "0.1.41" +tracing-opentelemetry = "0.30.0" +tracing-subscriber = { version = "0.3.19", features = ["env-filter", "fmt"] } diff --git a/crates/brightstaff/src/handlers/chat_completions.rs b/crates/brightstaff/src/handlers/chat_completions.rs new file mode 100644 index 00000000..d1610d61 --- /dev/null +++ b/crates/brightstaff/src/handlers/chat_completions.rs @@ -0,0 +1,168 @@ +use std::sync::Arc; + +use bytes::Bytes; +use common::api::open_ai::ChatCompletionsRequest; +use common::consts::ARCH_PROVIDER_HINT_HEADER; +use common::utils::shorten_string; +use http_body_util::combinators::BoxBody; +use http_body_util::{BodyExt, Full, StreamBody}; +use hyper::body::Frame; +use hyper::header::{self}; +use hyper::{Request, Response, StatusCode}; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use tokio_stream::StreamExt; +use tracing::{info, warn}; + +use crate::router::llm_router::RouterService; + +fn full>(chunk: T) -> BoxBody { + Full::new(chunk.into()) + .map_err(|never| match never {}) + .boxed() +} + +pub async fn chat_completions( + request: Request, + router_service: Arc, + llm_provider_endpoint: String, +) -> Result>, hyper::Error> { + let mut request_headers = request.headers().clone(); + + let chat_request_bytes = request.collect().await?.to_bytes(); + let chat_completion_request: ChatCompletionsRequest = + match serde_json::from_slice(&chat_request_bytes) { + Ok(request) => request, + Err(err) => { + let err_msg = format!("Failed to parse request body: {}", err); + let mut bad_request = Response::new(full(err_msg)); + *bad_request.status_mut() = StatusCode::BAD_REQUEST; + return Ok(bad_request); + } + }; + + info!( + "request body received: {}", + shorten_string(&serde_json::to_string(&chat_completion_request).unwrap()) + ); + + let trace_parent = request_headers + .iter() + .find(|(ty, _)| ty.as_str() == "traceparent") + .map(|(_, value)| value.to_str().unwrap_or_default().to_string()); + + let selected_llm = match router_service + .determine_route(&chat_completion_request.messages, trace_parent.clone()) + .await + { + Ok(route) => route, + Err(err) => { + let err_msg = format!("Failed to determine route: {}", err); + let mut internal_error = Response::new(full(err_msg)); + *internal_error.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + return Ok(internal_error); + } + }; + + info!( + "sending request to llm provider: {} with llm model: {:?}", + llm_provider_endpoint, selected_llm + ); + + if let Some(trace_parent) = trace_parent { + request_headers.insert( + header::HeaderName::from_static("traceparent"), + header::HeaderValue::from_str(&trace_parent).unwrap(), + ); + } + + if let Some(selected_llm) = selected_llm { + request_headers.insert( + ARCH_PROVIDER_HINT_HEADER, + header::HeaderValue::from_str(&selected_llm).unwrap(), + ); + } + + let llm_response = match reqwest::Client::new() + .post(llm_provider_endpoint) + .headers(request_headers) + .body(chat_request_bytes) + .send() + .await + { + Ok(res) => res, + Err(err) => { + let err_msg = format!("Failed to send request: {}", err); + let mut internal_error = Response::new(full(err_msg)); + *internal_error.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + return Ok(internal_error); + } + }; + + // copy over the headers from the original response + let response_headers = llm_response.headers().clone(); + let mut response = Response::builder(); + let headers = response.headers_mut().unwrap(); + for (header_name, header_value) in response_headers.iter() { + headers.insert(header_name, header_value.clone()); + } + + if chat_completion_request.stream { + // channel to create async stream + let (tx, rx) = mpsc::channel::(16); + + // Spawn a task to send data as it becomes available + tokio::spawn(async move { + let mut byte_stream = llm_response.bytes_stream(); + + while let Some(item) = byte_stream.next().await { + let item = match item { + Ok(item) => item, + Err(err) => { + warn!("Error receiving chunk: {:?}", err); + break; + } + }; + + if tx.send(item).await.is_err() { + warn!("Receiver dropped"); + break; + } + } + }); + + let stream = ReceiverStream::new(rx).map(|chunk| Ok::<_, hyper::Error>(Frame::data(chunk))); + + let stream_body = BoxBody::new(StreamBody::new(stream)); + + match response.body(stream_body) { + Ok(response) => Ok(response), + Err(err) => { + let err_msg = format!("Failed to create response: {}", err); + let mut internal_error = Response::new(full(err_msg)); + *internal_error.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + Ok(internal_error) + } + } + } else { + let body = match llm_response.text().await { + Ok(body) => body, + Err(err) => { + let err_msg = format!("Failed to read response: {}", err); + let mut internal_error = Response::new(full(err_msg)); + *internal_error.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + return Ok(internal_error); + } + }; + + match response.body(full(body)) { + Ok(response) => Ok(response), + Err(err) => { + let err_msg = format!("Failed to create response: {}", err); + let mut internal_error = Response::new(full(err_msg)); + *internal_error.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + Ok(internal_error) + } + } + } +} diff --git a/crates/brightstaff/src/handlers/mod.rs b/crates/brightstaff/src/handlers/mod.rs new file mode 100644 index 00000000..dcf38982 --- /dev/null +++ b/crates/brightstaff/src/handlers/mod.rs @@ -0,0 +1 @@ +pub mod chat_completions; diff --git a/crates/brightstaff/src/lib.rs b/crates/brightstaff/src/lib.rs new file mode 100644 index 00000000..0591d7d0 --- /dev/null +++ b/crates/brightstaff/src/lib.rs @@ -0,0 +1,2 @@ +pub mod handlers; +pub mod router; diff --git a/crates/brightstaff/src/main.rs b/crates/brightstaff/src/main.rs new file mode 100644 index 00000000..f683638f --- /dev/null +++ b/crates/brightstaff/src/main.rs @@ -0,0 +1,157 @@ +use brightstaff::handlers::chat_completions::chat_completions; +use brightstaff::router::llm_router::RouterService; +use bytes::Bytes; +use common::configuration::Configuration; +use common::utils::shorten_string; +use http_body_util::{combinators::BoxBody, BodyExt, Empty}; +use hyper::body::Incoming; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper::{Method, Request, Response, StatusCode}; +use hyper_util::rt::TokioIo; +use opentelemetry::global::BoxedTracer; +use opentelemetry::trace::FutureExt; +use opentelemetry::{ + global, + trace::{SpanKind, Tracer}, + Context, +}; +use opentelemetry_http::HeaderExtractor; +use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::SdkTracerProvider}; +use opentelemetry_stdout::SpanExporter; +use std::sync::{Arc, OnceLock}; +use std::{env, fs}; +use tokio::net::TcpListener; +use tracing::info; +use tracing_subscriber::EnvFilter; + +pub mod router; + +const BIND_ADDRESS: &str = "0.0.0.0:9091"; + +fn get_tracer() -> &'static BoxedTracer { + static TRACER: OnceLock = OnceLock::new(); + TRACER.get_or_init(|| global::tracer("archgw/router")) +} + +// Utility function to extract the context from the incoming request headers +fn extract_context_from_request(req: &Request) -> Context { + global::get_text_map_propagator(|propagator| { + propagator.extract(&HeaderExtractor(req.headers())) + }) +} + +fn init_tracer() -> SdkTracerProvider { + global::set_text_map_propagator(TraceContextPropagator::new()); + // Install stdout exporter pipeline to be able to retrieve the collected spans. + // For the demonstration, use `Sampler::AlwaysOn` sampler to sample all traces. + let provider = SdkTracerProvider::builder() + .with_simple_exporter(SpanExporter::default()) + .build(); + + global::set_tracer_provider(provider.clone()); + provider +} + +fn empty() -> BoxBody { + Empty::::new() + .map_err(|never| match never {}) + .boxed() +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let _tracer_provider = init_tracer(); + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), + ) + .init(); + + let bind_address = env::var("BIND_ADDRESS").unwrap_or_else(|_| BIND_ADDRESS.to_string()); + + //loading arch_config.yaml file + let arch_config_path = + env::var("ARCH_CONFIG_PATH").unwrap_or_else(|_| "./arch_config.yaml".to_string()); + info!("Loading arch_config.yaml from {}", arch_config_path); + + let config_contents = + fs::read_to_string(&arch_config_path).expect("Failed to read arch_config.yaml"); + + let config: Configuration = + serde_yaml::from_str(&config_contents).expect("Failed to parse arch_config.yaml"); + + let arch_config = Arc::new(config); + + info!( + "arch_config: {:?}", + shorten_string(&serde_json::to_string(arch_config.as_ref()).unwrap()) + ); + + let llm_provider_endpoint = env::var("LLM_PROVIDER_ENDPOINT") + .unwrap_or_else(|_| "http://localhost:12001/v1/chat/completions".to_string()); + + info!("llm provider endpoint: {}", llm_provider_endpoint); + info!("Listening on http://{}", bind_address); + let listener = TcpListener::bind(bind_address).await?; + + + // if routing is null then return gpt-4o as model name + let model = arch_config.routing.as_ref().map_or_else( + || "gpt-4o".to_string(), + |routing| routing.model.clone(), + ); + + let router_service: Arc = Arc::new(RouterService::new( + arch_config.llm_providers.clone(), + llm_provider_endpoint.clone(), + model, + )); + + loop { + let (stream, _) = listener.accept().await?; + let peer_addr = stream.peer_addr()?; + let io = TokioIo::new(stream); + + let router_service = Arc::clone(&router_service); + let llm_provider_endpoint = llm_provider_endpoint.clone(); + + let service = service_fn(move |req| { + let router_service = Arc::clone(&router_service); + let parent_cx = extract_context_from_request(&req); + info!("parent_cx: {:?}", parent_cx); + let tracer = get_tracer(); + let _span = tracer + .span_builder("request") + .with_kind(SpanKind::Server) + .start_with_context(tracer, &parent_cx); + let llm_provider_endpoint = llm_provider_endpoint.clone(); + + async move { + match (req.method(), req.uri().path()) { + (&Method::POST, "/v1/chat/completions") => { + chat_completions(req, router_service, llm_provider_endpoint) + .with_context(parent_cx) + .await + } + _ => { + let mut not_found = Response::new(empty()); + *not_found.status_mut() = StatusCode::NOT_FOUND; + Ok(not_found) + } + } + } + }); + + tokio::task::spawn(async move { + info!("Accepted connection from {:?}", peer_addr); + if let Err(err) = http1::Builder::new() + // .serve_connection(io, service_fn(chat_completion)) + .serve_connection(io, service) + .await + { + info!("Error serving connection: {:?}", err); + } + }); + } +} diff --git a/crates/brightstaff/src/router/llm_router.rs b/crates/brightstaff/src/router/llm_router.rs new file mode 100644 index 00000000..47f2b41c --- /dev/null +++ b/crates/brightstaff/src/router/llm_router.rs @@ -0,0 +1,151 @@ +use std::sync::Arc; + +use common::{ + api::open_ai::{ChatCompletionsResponse, Message}, + configuration::LlmProvider, + consts::ARCH_PROVIDER_HINT_HEADER, + utils::shorten_string, +}; +use hyper::header; +use thiserror::Error; +use tracing::{info, warn}; + +use super::router_model::RouterModel; + +pub struct RouterService { + router_url: String, + client: reqwest::Client, + router_model: Arc, + routing_model_name: String, + llm_usage_defined: bool, +} + +#[derive(Debug, Error)] +pub enum RoutingError { + #[error("Failed to send request: {0}")] + RequestError(#[from] reqwest::Error), + + #[error("Failed to parse JSON: {0}, JSON: {1}")] + JsonError(serde_json::Error, String), + + #[error("Router model error: {0}")] + RouterModelError(#[from] super::router_model::RoutingModelError), +} + +pub type Result = std::result::Result; + +impl RouterService { + pub fn new( + providers: Vec, + router_url: String, + routing_model_name: String, + ) -> Self { + let providers_with_usage = providers + .iter() + .filter(|provider| provider.usage.is_some()) + .cloned() + .collect::>(); + + // convert the llm_providers to yaml string but only include name and usage + let llm_providers_with_usage_yaml = providers_with_usage + .iter() + .map(|provider| { + format!( + "- name: {}\n description: {}", + provider.name, + provider.usage.as_ref().unwrap_or(&"".to_string()) + ) + }) + .collect::>() + .join("\n"); + + info!( + "llm_providers from config with usage: {}...", + shorten_string(&llm_providers_with_usage_yaml.replace("\n", "\\n")) + ); + + let router_model = Arc::new(super::router_model_v1::RouterModelV1::new( + llm_providers_with_usage_yaml.clone(), + routing_model_name.clone(), + )); + + RouterService { + router_url, + client: reqwest::Client::new(), + router_model, + routing_model_name, + llm_usage_defined: !providers_with_usage.is_empty(), + } + } + + pub async fn determine_route( + &self, + messages: &[Message], + trace_parent: Option, + ) -> Result> { + + if !self.llm_usage_defined { + return Ok(None); + } + + let router_request = self.router_model.generate_request(messages); + + info!( + "router_request: {}", + shorten_string(&serde_json::to_string(&router_request).unwrap()), + ); + + let mut llm_route_request_headers = header::HeaderMap::new(); + llm_route_request_headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ); + + llm_route_request_headers.insert( + header::HeaderName::from_static(ARCH_PROVIDER_HINT_HEADER), + header::HeaderValue::from_str(&self.routing_model_name).unwrap(), + ); + + if let Some(trace_parent) = trace_parent { + llm_route_request_headers.insert( + header::HeaderName::from_static("traceparent"), + header::HeaderValue::from_str(&trace_parent).unwrap(), + ); + } + + let res = self + .client + .post(&self.router_url) + .headers(llm_route_request_headers) + .body(serde_json::to_string(&router_request).unwrap()) + .send() + .await?; + + let body = res.text().await?; + + let chat_completion_response: ChatCompletionsResponse = match serde_json::from_str(&body) { + Ok(response) => response, + Err(err) => { + warn!( + "Failed to parse JSON: {}. Body: {}", + err, + &serde_json::to_string(&body).unwrap() + ); + return Err(RoutingError::JsonError( + err, + format!("Failed to parse JSON: {}", body), + )); + } + }; + + let selected_llm = self.router_model.parse_response( + chat_completion_response.choices[0] + .message + .content + .as_ref() + .unwrap(), + )?; + + Ok(selected_llm) + } +} diff --git a/crates/brightstaff/src/router/mod.rs b/crates/brightstaff/src/router/mod.rs new file mode 100644 index 00000000..e35ea731 --- /dev/null +++ b/crates/brightstaff/src/router/mod.rs @@ -0,0 +1,3 @@ +pub mod llm_router; +pub mod router_model; +pub mod router_model_v1; diff --git a/crates/brightstaff/src/router/router_model.rs b/crates/brightstaff/src/router/router_model.rs new file mode 100644 index 00000000..e9f5e256 --- /dev/null +++ b/crates/brightstaff/src/router/router_model.rs @@ -0,0 +1,15 @@ +use common::api::open_ai::{ChatCompletionsRequest, Message}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum RoutingModelError { + #[error("Failed to parse JSON: {0}")] + JsonError(#[from] serde_json::Error), +} + +pub type Result = std::result::Result; + +pub trait RouterModel: Send + Sync { + fn generate_request(&self, messages: &[Message]) -> ChatCompletionsRequest; + fn parse_response(&self, content: &str) -> Result>; +} diff --git a/crates/brightstaff/src/router/router_model_v1.rs b/crates/brightstaff/src/router/router_model_v1.rs new file mode 100644 index 00000000..836c79e5 --- /dev/null +++ b/crates/brightstaff/src/router/router_model_v1.rs @@ -0,0 +1,251 @@ +use common::{ + api::open_ai::{ChatCompletionsRequest, Message}, + consts::{SYSTEM_ROLE, USER_ROLE}, +}; +use serde::{Deserialize, Serialize}; +use tracing::info; + +use super::router_model::{RouterModel, RoutingModelError}; + +pub const ARCH_ROUTER_V1_SYSTEM_PROMPT: &str = r#" +You are a helpful assistant designed to find the best suited route. +You are provided with route description within XML tags: + +{routes} + + +Your task is to decide which route is best suit with user intent on the conversation in XML tags. Follow the instruction: +1. If the latest intent from user is irrelevant, response with empty route {"route": ""}. +2. If the user request is full fill and user thank or ending the conversation , response with empty route {"route": ""}. +3. Understand user latest intent and find the best match route in xml tags. + +Based on your analysis, provide your response in the following JSON formats if you decide to match any route: +{"route": "route_name"} + + + +{conversation} + +"#; + +pub type Result = std::result::Result; + +pub struct RouterModelV1 { + llm_providers_with_usage_yaml: String, + routing_model: String, +} + +impl RouterModelV1 { + pub fn new(llm_providers_with_usage_yaml: String, routing_model: String) -> Self { + RouterModelV1 { + llm_providers_with_usage_yaml, + routing_model, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct LlmRouterResponse { + pub route: Option, +} + +impl RouterModel for RouterModelV1 { + fn generate_request(&self, messages: &[Message]) -> ChatCompletionsRequest { + let messages_str = messages + .iter() + .filter(|m| m.role != SYSTEM_ROLE) + .map(|m| { + let content_json_str = serde_json::to_string(&m.content).unwrap_or_default(); + format!("{}: {}", m.role, content_json_str) + }) + .collect::>() + .join("\n"); + + let message = ARCH_ROUTER_V1_SYSTEM_PROMPT + .replace("{routes}", &self.llm_providers_with_usage_yaml) + .replace("{conversation}", messages_str.as_str()); + + ChatCompletionsRequest { + model: self.routing_model.clone(), + messages: vec![Message { + content: Some(message), + role: USER_ROLE.to_string(), + model: None, + tool_calls: None, + tool_call_id: None, + }], + tools: None, + stream: false, + stream_options: None, + metadata: None, + } + } + + fn parse_response(&self, content: &str) -> Result> { + if content.is_empty() { + return Ok(None); + } + let router_resp_fixed = fix_json_response(content); + info!( + "router response (fixed): {}", + router_resp_fixed.replace("\n", "\\n") + ); + let router_response: LlmRouterResponse = serde_json::from_str(router_resp_fixed.as_str())?; + + let selected_llm = router_response.route.unwrap_or_default().to_string(); + + if selected_llm.is_empty() { + return Ok(None); + } + + Ok(Some(selected_llm)) + } +} + +fn fix_json_response(body: &str) -> String { + let mut updated_body = body.to_string(); + + updated_body = updated_body.replace("'", "\""); + + if updated_body.contains("\\n") { + updated_body = updated_body.replace("\\n", ""); + } + + if updated_body.starts_with("```json") { + updated_body = updated_body + .strip_prefix("```json") + .unwrap_or(&updated_body) + .to_string(); + } + + if updated_body.ends_with("```") { + updated_body = updated_body + .strip_suffix("```") + .unwrap_or(&updated_body) + .to_string(); + } + + updated_body +} + +impl std::fmt::Debug for dyn RouterModel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "RouterModel") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_system_prompt_format() { + let expected_prompt = r#" +You are a helpful assistant designed to find the best suited route. +You are provided with route description within XML tags: + +route1: description1 +route2: description2 + + +Your task is to decide which route is best suit with user intent on the conversation in XML tags. Follow the instruction: +1. If the latest intent from user is irrelevant, response with empty route {"route": ""}. +2. If the user request is full fill and user thank or ending the conversation , response with empty route {"route": ""}. +3. Understand user latest intent and find the best match route in xml tags. + +Based on your analysis, provide your response in the following JSON formats if you decide to match any route: +{"route": "route_name"} + + + +user: "Hello, I want to book a flight." +assistant: "Sure, where would you like to go?" +user: "seattle" + +"#; + + let routes_yaml = "route1: description1\nroute2: description2"; + let routing_model = "test-model".to_string(); + let router = RouterModelV1::new(routes_yaml.to_string(), routing_model.clone()); + + let messages = vec![ + Message { + role: "system".to_string(), + content: Some("You are a helpful assistant.".to_string()), + ..Default::default() + }, + Message { + role: "user".to_string(), + content: Some("Hello, I want to book a flight.".to_string()), + ..Default::default() + }, + Message { + role: "assistant".to_string(), + content: Some("Sure, where would you like to go?".to_string()), + ..Default::default() + }, + Message { + role: "user".to_string(), + content: Some("seattle".to_string()), + ..Default::default() + }, + ]; + + let req = router.generate_request(&messages); + + let prompt = req.messages[0].content.as_ref().unwrap(); + + println!("Prompt: {}", prompt); + + assert_eq!(expected_prompt, prompt); + } +} + +#[test] +fn test_parse_response() { + let router = RouterModelV1::new( + "route1: description1\nroute2: description2".to_string(), + "test-model".to_string(), + ); + + // Case 1: Valid JSON with non-empty route + let input = r#"{"route": "route1"}"#; + let result = router.parse_response(input).unwrap(); + assert_eq!(result, Some("route1".to_string())); + + // Case 2: Valid JSON with empty route + let input = r#"{"route": ""}"#; + let result = router.parse_response(input).unwrap(); + assert_eq!(result, None); + + // Case 3: Valid JSON with null route + let input = r#"{"route": null}"#; + let result = router.parse_response(input).unwrap(); + assert_eq!(result, None); + + // Case 4: JSON missing route field + let input = r#"{}"#; + let result = router.parse_response(input).unwrap(); + assert_eq!(result, None); + + // Case 4.1: empty string + let input = r#""#; + let result = router.parse_response(input).unwrap(); + assert_eq!(result, None); + + // Case 5: Malformed JSON + let input = r#"{"route": "route1""#; // missing closing } + let result = router.parse_response(input); + assert!(result.is_err()); + + // Case 6: Single quotes and \n in JSON + let input = "{'route': 'route2'}\\n"; + let result = router.parse_response(input).unwrap(); + assert_eq!(result, Some("route2".to_string())); + + // Case 7: Code block marker + let input = "```json\n{\"route\": \"route1\"}\n```"; + let result = router.parse_response(input).unwrap(); + assert_eq!(result, Some("route1".to_string())); +} diff --git a/crates/common/src/api/open_ai.rs b/crates/common/src/api/open_ai.rs index d71b0d58..7b3cf66c 100644 --- a/crates/common/src/api/open_ai.rs +++ b/crates/common/src/api/open_ai.rs @@ -171,6 +171,18 @@ pub struct Message { pub tool_call_id: Option, } +impl Default for Message { + fn default() -> Self { + Message { + role: ASSISTANT_ROLE.to_string(), + content: None, + model: None, + tool_calls: None, + tool_call_id: None, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Choice { pub finish_reason: Option, diff --git a/crates/common/src/configuration.rs b/crates/common/src/configuration.rs index 2065b1aa..71c13f8b 100644 --- a/crates/common/src/configuration.rs +++ b/crates/common/src/configuration.rs @@ -6,6 +6,11 @@ use crate::api::open_ai::{ ChatCompletionTool, FunctionDefinition, FunctionParameter, FunctionParameters, ParameterType, }; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Routing { + pub model: String, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Configuration { pub version: String, @@ -19,6 +24,7 @@ pub struct Configuration { pub ratelimits: Option>, pub tracing: Option, pub mode: Option, + pub routing: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -166,6 +172,7 @@ pub struct LlmProvider { pub endpoint: Option, pub port: Option, pub rate_limits: Option, + pub usage: Option, } impl Display for LlmProvider { diff --git a/crates/common/src/consts.rs b/crates/common/src/consts.rs index e58bebde..9eee4693 100644 --- a/crates/common/src/consts.rs +++ b/crates/common/src/consts.rs @@ -11,7 +11,8 @@ pub const MODEL_SERVER_NAME: &str = "model_server"; pub const ARCH_ROUTING_HEADER: &str = "x-arch-llm-provider"; pub const MESSAGES_KEY: &str = "messages"; pub const ARCH_PROVIDER_HINT_HEADER: &str = "x-arch-llm-provider-hint"; -pub const CHAT_COMPLETIONS_PATH: [&str; 2] = ["/v1/chat/completions", "/openai/v1/chat/completions"]; +pub const CHAT_COMPLETIONS_PATH: [&str; 2] = + ["/v1/chat/completions", "/openai/v1/chat/completions"]; pub const HEALTHZ_PATH: &str = "/healthz"; pub const X_ARCH_STATE_HEADER: &str = "x-arch-state"; pub const X_ARCH_API_RESPONSE: &str = "x-arch-api-response-message"; @@ -27,3 +28,4 @@ pub const HALLUCINATION_TEMPLATE: &str = "It seems I'm missing some information. Could you provide the following details "; pub const OTEL_COLLECTOR_HTTP: &str = "opentelemetry_collector_http"; pub const OTEL_POST_PATH: &str = "/v1/traces"; +pub const LLM_ROUTE_HEADER: &str = "x-arch-llm-route"; diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 32549893..76c368f1 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -11,3 +11,4 @@ pub mod routing; pub mod stats; pub mod tokenizer; pub mod tracing; +pub mod utils; diff --git a/crates/common/src/utils.rs b/crates/common/src/utils.rs new file mode 100644 index 00000000..5c5793da --- /dev/null +++ b/crates/common/src/utils.rs @@ -0,0 +1,7 @@ +pub fn shorten_string(s: &str) -> String { + if s.len() > 80 { + format!("{}...", &s[..80]) + } else { + s.to_string() + } +} diff --git a/crates/llm_gateway/src/stream_context.rs b/crates/llm_gateway/src/stream_context.rs index 5b741a43..c5cdfe32 100644 --- a/crates/llm_gateway/src/stream_context.rs +++ b/crates/llm_gateway/src/stream_context.rs @@ -228,6 +228,7 @@ impl HttpContext for StreamContext { stream: None, port: None, rate_limits: None, + usage: None, })); } else { self.select_llm_provider(); @@ -316,10 +317,6 @@ impl HttpContext for StreamContext { } }; - // remove metadata from the request body - //TODO: move this to prompt gateway - // deserialized_body.metadata = None; - // delete model key from message array for message in deserialized_body.messages.iter_mut() { message.model = None; } @@ -342,24 +339,22 @@ impl HttpContext for StreamContext { }; let model_requested = deserialized_body.model.clone(); - if deserialized_body.model.is_empty() || deserialized_body.model.to_lowercase() == "none" { - deserialized_body.model = match model_name { - Some(model_name) => model_name.clone(), - None => { - if use_agent_orchestrator { - "agent_orchestrator".to_string() - } else { - self.send_server_error( - ServerError::BadRequest { - why: format!("No model specified in request and couldn't determine model name from arch_config. Model name in req: {}, arch_config, provider: {}, model: {:?}", deserialized_body.model, self.llm_provider().name, self.llm_provider().model).to_string(), - }, - Some(StatusCode::BAD_REQUEST), - ); - return Action::Continue; - } + deserialized_body.model = match model_name { + Some(model_name) => model_name.clone(), + None => { + if use_agent_orchestrator { + "agent_orchestrator".to_string() + } else { + self.send_server_error( + ServerError::BadRequest { + why: format!("No model specified in request and couldn't determine model name from arch_config. Model name in req: {}, arch_config, provider: {}, model: {:?}", deserialized_body.model, self.llm_provider().name, self.llm_provider().model).to_string(), + }, + Some(StatusCode::BAD_REQUEST), + ); + return Action::Continue; } } - } + }; info!( "on_http_request_body: provider: {}, model requested: {}, model selected: {}", diff --git a/crates/llm_gateway/tests/integration.rs b/crates/llm_gateway/tests/integration.rs index 0f94d2d8..ccd4bb4c 100644 --- a/crates/llm_gateway/tests/integration.rs +++ b/crates/llm_gateway/tests/integration.rs @@ -489,7 +489,6 @@ fn llm_gateway_override_model_name() { .expect_log(Some(LogLevel::Debug), None) .expect_log(Some(LogLevel::Debug), None) .expect_log(Some(LogLevel::Debug), None) - .expect_log(Some(LogLevel::Debug), None) .expect_metric_record("input_sequence_length", 29) .expect_log(Some(LogLevel::Debug), None) .expect_log(Some(LogLevel::Debug), None) diff --git a/crates/prompt_gateway/src/stream_context.rs b/crates/prompt_gateway/src/stream_context.rs index 1cd2fa86..0ceb4aa5 100644 --- a/crates/prompt_gateway/src/stream_context.rs +++ b/crates/prompt_gateway/src/stream_context.rs @@ -777,18 +777,19 @@ impl StreamContext { fn check_intent_matched(model_server_response: &ChatCompletionsResponse) -> bool { let content = model_server_response - .choices.first() + .choices + .first() .and_then(|choice| choice.message.content.as_ref()); let content_has_value = content.is_some() && !content.unwrap().is_empty(); let tool_calls = model_server_response - .choices.first() + .choices + .first() .and_then(|choice| choice.message.tool_calls.as_ref()); // intent was matched if content has some value or tool_calls is empty - content_has_value || (tool_calls.is_some() && !tool_calls.unwrap().is_empty()) } diff --git a/demos/shared/test_runner/run_demo_tests.sh b/demos/shared/test_runner/run_demo_tests.sh index 5b930565..94b26e6d 100644 --- a/demos/shared/test_runner/run_demo_tests.sh +++ b/demos/shared/test_runner/run_demo_tests.sh @@ -1,13 +1,16 @@ #!/bin/bash set -eu +echo "docker images" +docker images + # for demo in currency_exchange hr_agent -for demo in currency_exchange +for demo in samples_python/currency_exchange use_cases/preference_based_routing do echo "******************************************" echo "Running tests for $demo ..." echo "****************************************" - cd ../../samples_python/$demo + cd ../../$demo echo "starting archgw" archgw up arch_config.yaml echo "starting docker containers" diff --git a/demos/use_cases/preference_based_routing/README.md b/demos/use_cases/preference_based_routing/README.md new file mode 100644 index 00000000..4f703afe --- /dev/null +++ b/demos/use_cases/preference_based_routing/README.md @@ -0,0 +1,2 @@ +# Usage based LLM Routing +This demo shows how you can use user preferences to route user prompts to appropriate llm. See [arch_config.yaml](arch_config.yaml) for details on how you can define user preferences. diff --git a/demos/use_cases/preference_based_routing/arch_config.yaml b/demos/use_cases/preference_based_routing/arch_config.yaml new file mode 100644 index 00000000..eea4ef70 --- /dev/null +++ b/demos/use_cases/preference_based_routing/arch_config.yaml @@ -0,0 +1,39 @@ +version: "0.1-beta" + +routing: + model: gpt-4o + +listeners: + egress_traffic: + address: 0.0.0.0 + port: 12000 + message_format: openai + timeout: 30s + +llm_providers: + + - name: archgw-v1-router-model + provider_interface: openai + model: cotran2/llama-1b-4-26 + base_url: http://35.192.87.187:8000/v1 + + - name: gpt-4o-mini + provider_interface: openai + access_key: $OPENAI_API_KEY + model: gpt-4o-mini + default: true + + - name: gpt-4o + provider_interface: openai + access_key: $OPENAI_API_KEY + model: gpt-4o + usage: Generating original content such as scripts, articles, or creative materials. + + - name: o4-mini + provider_interface: openai + access_key: $OPENAI_API_KEY + model: o4-mini + usage: Requesting topic ideas specifically related to personal finance and budgeting. + +tracing: + random_sampling: 100 diff --git a/demos/use_cases/preference_based_routing/docker-compose.yaml b/demos/use_cases/preference_based_routing/docker-compose.yaml new file mode 100644 index 00000000..c2d794c6 --- /dev/null +++ b/demos/use_cases/preference_based_routing/docker-compose.yaml @@ -0,0 +1,32 @@ +services: + + chatbot_ui: + build: + context: ../../shared/chatbot_ui + dockerfile: Dockerfile + ports: + - "18080:8080" + environment: + - CHAT_COMPLETION_ENDPOINT=http://host.docker.internal:12000/v1 + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ./arch_config.yaml:/app/arch_config.yaml + + jaeger: + build: + context: ../../shared/jaeger + ports: + - "16686:16686" + - "4317:4317" + - "4318:4318" + + prometheus: + build: + context: ../../shared/prometheus + + grafana: + build: + context: ../../shared/grafana + ports: + - "3000:3000" diff --git a/demos/use_cases/preference_based_routing/hurl_tests/simple.hurl b/demos/use_cases/preference_based_routing/hurl_tests/simple.hurl new file mode 100644 index 00000000..517767b6 --- /dev/null +++ b/demos/use_cases/preference_based_routing/hurl_tests/simple.hurl @@ -0,0 +1,18 @@ +POST http://localhost:12000/v1/chat/completions +Content-Type: application/json + +{ + "messages": [ + { + "role": "user", + "content": "I am running under debt, how should I keep a tab on my expenses?" + } + ] +} +HTTP 200 +[Asserts] +header "content-type" == "application/json" +jsonpath "$.model" matches /^o4-mini/ +jsonpath "$.usage" != null +jsonpath "$.choices[0].message.content" != null +jsonpath "$.choices[0].message.role" == "assistant" diff --git a/demos/use_cases/preference_based_routing/hurl_tests/simple_stream.hurl b/demos/use_cases/preference_based_routing/hurl_tests/simple_stream.hurl new file mode 100644 index 00000000..d6b770f6 --- /dev/null +++ b/demos/use_cases/preference_based_routing/hurl_tests/simple_stream.hurl @@ -0,0 +1,16 @@ +POST http://localhost:12000/v1/chat/completions +Content-Type: application/json + +{ + "messages": [ + { + "role": "user", + "content": "I am running under debt, how should I keep a tab on my expenses?" + } + ], + "stream": true +} +HTTP 200 +[Asserts] +header "content-type" matches /text\/event-stream/ +body matches /^data: .*?o4-mini.*?\n/ diff --git a/demos/use_cases/preference_based_routing/test_router_endpoint.rest b/demos/use_cases/preference_based_routing/test_router_endpoint.rest new file mode 100644 index 00000000..9fc6f6fe --- /dev/null +++ b/demos/use_cases/preference_based_routing/test_router_endpoint.rest @@ -0,0 +1,24 @@ +@arch_llm_router_endpoint = http://35.192.87.187:8000 + +POST {{arch_llm_router_endpoint}}/v1/chat/completions HTTP/1.1 +Content-Type: application/json + +{ + "model": "cotran2/llama-1b-4-26", + "messages": [ + { + "role": "user", + "content": "You are an advanced Routing Assistant designed to select the optimal route based on user requests. \nYour task is to analyze conversations and match them to the most appropriate predefined route.\nReview the available routes config:\n\n# ROUTES CONFIG START\n- name: gpt-4o()\n description: \"complex reasoning problem, require multi step answer\\n\"\n- name: o4-mini()\n description: \"simple requests, basic fact retrieval, easy to answer\\n\"\n\n# ROUTES CONFIG END\n\nExamine the following conversation between a user and an assistant:\n\n# CONVERSATION START\n\nuser: Hello\nassistant: Hi! How can I assist you today?\nuser: List us presidents who are born in odd years and are still alive. Order them by their age and I also know what is their home city they were born. And what year they became president. Also give me summary of which president was the best for economy of the US.\n\n# CONVERSATION END\n\nYour goal is to identify the most appropriate route that matches the user's LATEST intent. Follow these steps:\n\n1. Carefully read and analyze the provided conversation, focusing on the user's latest request and the conversation scenario.\n2. Check if the user's request and scenario matches any of the routes in the routing configuration (focus on the description).\n3. Find the route that best matches.\n4. Use context clues from the entire conversation to determine the best fit.\n5. Return the best match possible. You only response the name of the route that best matches the user's request, use the exact name in the routes config.\n6. If no route relatively close to matches the user's latest intent or user last message is thank you or greeting, return an empty route ''. \n\n\n# OUTPUT FORMAT\nYour final output must follow this JSON format:\n{\n \"route\": \"route_name\" # The matched route name, or empty string '' if no match\n}\n\nBased on your analysis, provide only the JSON object as your final output with no additional text, explanations, or whitespace." + } + ] +} + +### test 2 + +POST {{arch_llm_router_endpoint}}/v1/chat/completions HTTP/1.1 +Content-Type: application/json + +{"model":"cotran2/llama-1b-4-26","messages":[{"role":"user","content":"\nYou are an advanced Routing Assistant designed to select the optimal route based on user requests. \nYour task is to analyze conversations and match them to the most appropriate predefined route.\nReview the available routes config:\n\n# ROUTES CONFIG START\n- name: gpt-4o\n description: simple requests, basic fact retrieval, easy to answer\n- name: o4-mini()\n description: complex reasoning problem, require multi step answer\n# ROUTES CONFIG END\n\nExamine the following conversation between a user and an assistant:\n\n# CONVERSATION START\n[{\"role\":\"user\",\"content\":\"What is the capital of France?\"}]\n# CONVERSATION END\n\nYour goal is to identify the most appropriate route that matches the user's LATEST intent. Follow these steps:\n\n1. Carefully read and analyze the provided conversation, focusing on the user's latest request and the conversation scenario.\n2. Check if the user's request and scenario matches any of the routes in the routing configuration (focus on the description).\n3. Find the route that best matches.\n4. Use context clues from the entire conversation to determine the best fit.\n5. Return the best match possible. You only response the name of the route that best matches the user's request, use the exact name in the routes config.\n6. If no route relatively close to matches the user's latest intent or user last message is thank you or greeting, return an empty route ''. \n\n# OUTPUT FORMAT\nYour final output must follow this JSON format:\n{\n \"route\": \"route_name\" # The matched route name, or empty string '' if no match\n}\n\nBased on your analysis, provide only the JSON object as your final output with no additional text, explanations, or whitespace.\n"}],"stream":false} + +### get model list +GET http://34.46.85.85:8000/v1/models HTTP/1.1 diff --git a/tests/rest/llm_routing.rest b/tests/rest/llm_routing.rest new file mode 100644 index 00000000..41fcffca --- /dev/null +++ b/tests/rest/llm_routing.rest @@ -0,0 +1,77 @@ +@llm_endpoint = http://localhost:12000 +@openai_endpoint = https://api.openai.com +@access_key = {{$dotenv OPENAI_API_KEY}} + +### openai request +POST {{openai_endpoint}}/v1/chat/completions HTTP/1.1 +Content-Type: application/json +Authorization: Bearer {{access_key}} + +{ + "messages": [ + { + "role": "user", + "content": "hello" + } + ], + "model": "gpt-4o-mini", + "stream": true +} + +### openai request (streaming) +POST {{openai_endpoint}}/v1/chat/completions HTTP/1.1 +Content-Type: application/json +Authorization: Bearer {{access_key}} + +{ + "messages": [ + { + "role": "user", + "content": "hello" + } + ], + "model": "gpt-4o-mini", + "stream": true +} + + +### llm gateway request +POST {{llm_endpoint}}/v1/chat/completions HTTP/1.1 +Content-Type: application/json + +{ + "messages": [ + { + "role": "user", + "content": "hello" + } + ] +} + +### llm gateway request (streaming) +POST {{llm_endpoint}}/v1/chat/completions HTTP/1.1 +Content-Type: application/json + +{ + "messages": [ + { + "role": "user", + "content": "hello" + } + ], + "stream": true +} + +### llm gateway request (provider hint) +POST {{llm_endpoint}}/v1/chat/completions HTTP/1.1 +Content-Type: application/json +x-arch-llm-provider-hint: gpt-3.5-turbo-0125 + +{ + "messages": [ + { + "role": "user", + "content": "hello" + } + ] +}