commit 92a23740c6b146ac4b0accab68130a66dfd11b78 Author: narawat Date: Tue Sep 22 05:53:59 2026 +0700 1st commit diff --git a/.drone.yml b/.drone.yml new file mode 100755 index 0000000..2945ac5 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,83 @@ +# ------------------------------------------------------------------------------------------------ # +# sommpanion/wine-db-api/.drone.yml # +# ------------------------------------------------------------------------------------------------ # +kind: pipeline +type: docker +name: build-and-push + +steps: + # Step 1: Normal pushes — any branch except tags + - name: build-push-dev + image: plugins/docker:latest + settings: + registry: registry.yiem.cc + repo: registry.yiem.cc/sommpanion/wine-db-api + username: + from_secret: registry_Caddy_username # fill in registry.yiem.cc's Caddy basic auth secret in Drone UI + password: + from_secret: registry_Caddy_password + dockerfile: Dockerfile + context: . + buildkit: true + platforms: + - linux/amd64 + - linux/arm64 + tags: + - latest + - sha-${DRONE_COMMIT_SHA:0:8} + - build-${DRONE_BUILD_NUMBER} + - ${DRONE_BRANCH} + when: + event: + - push + branch: + exclude: + - refs/tags/* + + - name: build-push-pr + image: plugins/docker:latest + settings: + registry: registry.yiem.cc + repo: registry.yiem.cc/sommpanion/wine-db-api + username: + from_secret: registry_Caddy_username # fill in registry.yiem.cc's Caddy basic auth secret in Drone UI + password: + from_secret: registry_Caddy_password + dockerfile: Dockerfile + context: . + buildkit: true + platforms: + - linux/amd64 + - linux/arm64 + tags: + - pr-${DRONE_PULL_REQUEST} + - sha-${DRONE_COMMIT_SHA:0:8} + - build-${DRONE_BUILD_NUMBER} + when: + event: + - pull_request + + - name: build-push-release + image: plugins/docker:latest + settings: + registry: registry.yiem.cc + repo: registry.yiem.cc/sommpanion/wine-db-api + username: + from_secret: registry_Caddy_username # fill in registry.yiem.cc's Caddy basic auth secret in Drone UI + password: + from_secret: registry_Caddy_password + dockerfile: Dockerfile + context: . + buildkit: true + platforms: + - linux/amd64 + - linux/arm64 + tags: + - latest + - sha-${DRONE_COMMIT_SHORT} + - build-${DRONE_BUILD_NUMBER} + - ${DRONE_TAG} + when: + event: + - tag + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..2ad99d8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,107 @@ +# FROM nvidia/cuda:12.2.0-devel-ubuntu20.04 +FROM julia:1.13 +# FROM debian:latest + +# ---------------------------------------------- 100 --------------------------------------------- # + +# install required APT packages +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -y \ + # build-essential \ + # busybox \ + # g++ \ + # gcc \ + # wget \ + # net-tools \ + # iputils-ping \ + # unzip \ + # unixodbc \ + # unixodbc-dev \ + # libicu-dev \ + # nano \ + # libmosquitto-dev \ + # git \ + # ffmpeg \ + # libsm6 \ + # libxext6 \ + # tar \ + # zip \ + # libssl-dev \ + # # python3 \ + # # python3-pip \ + # # python-is-python3 \ + # postgresql-client \ + # cargo \ + # procps \ + curl + +# # For webapp frontend +# RUN apt-get update && apt-get install -y nginx +# COPY nginx.conf /etc/nginx/nginx.conf +# # Copy your static website files to the Nginx HTML directory +# COPY . /usr/share/nginx/html + +# # install nodejs https://deb.nodesource.com/ +# RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - && \ +# apt-get update && apt-get install -y nodejs + +# set up the app +RUN mkdir /appfolder +# RUN mkdir /appfolder/mountvolume +RUN mkdir /appfolder/app +RUN mkdir /appfolder/app/temp +COPY . /appfolder/app/temp +COPY ./env_preparation.jl /appfolder/app +# RUN mkdir /appfolder +# COPY . /appfolder + +# install Conda as primary python environment with specified python version +WORKDIR /appfolder +# RUN PATH="${HOME}/conda/bin:${PATH}" \ +# # && wget -O Miniforge3.sh https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-aarch64.sh \ +# && wget -O Miniforge3.sh https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh \ +# && bash Miniforge3.sh -b -p "${HOME}/conda" \ +# && rm -f Miniforge3.sh \ +# && echo "Running $(conda --version)" \ +# && conda init bash \ +# && . /root/.bashrc \ +# && conda update conda -y \ +# && conda install python=3.10 -y + +# # install pip into conda's base env +# RUN PATH="${HOME}/conda/bin:${PATH}" \ +# && conda install pip \ +# && pip install --trusted-host pypi.python.org -r required_python_packages.txt \ +# # && CMAKE_ARGS="-DLLAMA_BLAS=ON -DLLAMA_BLAS_VENDOR=OpenBLAS -DLLAMA_AVX2=OFF -DLLAMA_F16C=OFF -DLLAMA_FMA=OFF" FORCE_CMAKE=1 pip install llama-cpp-python --no-cache-dir +# && CMAKE_ARGS="-DLLAMA_CUBLAS=on -DLLAMA_AVX2=OFF -DLLAMA_F16C=OFF -DLLAMA_FMA=OFF" FORCE_CMAKE=1 pip install llama-cpp-python==0.2.77 --no-cache-dir +# # https://github.com/abetlen/llama-cpp-python/issues/412 old CPU cause CUBLAS compile problem + +# # to be able to use CMD python -m at the last line +# ENV PATH=/root/conda/bin:$PATH + +# # install powershell +# WORKDIR /appfolder/app/temp +# RUN wget https://github.com/PowerShell/PowerShell/releases/download/v7.4.5/powershell_7.4.5-1.deb_amd64.deb +# RUN dpkg -i powershell_7.4.5-1.deb_amd64.deb +# RUN apt-get install -f + +# using juliaup +# RUN curl -fsSL https://install.julialang.org | sh -s -- -y +# SHELL ["/bin/bash", "--login" , "-c"] + +# install julia package for my app +WORKDIR /appfolder/app +RUN julia -t 1 env_preparation.jl + + +# Make port 80 available to the world outside this container. If I use --publish at docker run command, there is no need to use EXPOSE +# EXPOSE 1883 + +# Health check server port +EXPOSE 3000 + +# Run app when the container launches +WORKDIR /appfolder/app +RUN julia --project -t 1 -e 'using Pkg; Pkg.instantiate(); Pkg.precompile()' +CMD ["julia", "--project", "-t", "2", "/appfolder/app/main.jl"] +# CMD ["nginx", "-g", "daemon off;"] +# CMD [ "sleep", "infinity" ] diff --git a/Manifest.toml b/Manifest.toml new file mode 100755 index 0000000..d03acb3 --- /dev/null +++ b/Manifest.toml @@ -0,0 +1,1362 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.13.0" +manifest_format = "2.1" +project_hash = "8c0db6da542b5572f3479ef2f741b4dabbf5a9a9" + +[[deps.AWS]] +deps = ["Base64", "Compat", "Dates", "Downloads", "GitHub", "HTTP", "IniFile", "JSON", "MD5", "Mocking", "OrderedCollections", "Random", "SHA", "ScopedValues", "Sockets", "URIs", "UUIDs", "XMLDict"] +git-tree-sha1 = "842075b12a7f763c5f815a04037a99b6ac346df9" +registries = "General" +uuid = "fbe9abb3-538b-5e4e-ba9e-bc94f4f92ebc" +version = "1.99.0" + +[[deps.AWSS3]] +deps = ["AWS", "ArrowTypes", "Base64", "Compat", "Dates", "EzXML", "FilePathsBase", "HTTP", "MbedTLS", "Mocking", "OrderedCollections", "Retry", "SymDict", "URIs", "UUIDs", "XMLDict"] +git-tree-sha1 = "af99cea71364c8c4483405a6b03697d197ab4d3b" +registries = "General" +uuid = "1c724243-ef5b-51ab-93f4-b0a88ac62a95" +version = "0.11.6" + +[[deps.Accessors]] +deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"] +git-tree-sha1 = "7063ad1083578215c7c4bf410368150abe8d5524" +registries = "General" +uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" +version = "0.1.45" + + [deps.Accessors.extensions] + AxisKeysExt = "AxisKeys" + IntervalSetsExt = "IntervalSets" + LinearAlgebraExt = "LinearAlgebra" + StaticArraysExt = "StaticArrays" + StructArraysExt = "StructArrays" + TestExt = "Test" + UnitfulExt = "Unitful" + + [deps.Accessors.weakdeps] + AxisKeys = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5" + IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" + LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" + Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" + +[[deps.AliasTables]] +deps = ["PtrArrays", "Random"] +git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" +registries = "General" +uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" +version = "1.1.3" + +[[deps.ArgCheck]] +git-tree-sha1 = "f9e9a66c9b7be1ad7372bbd9b062d9230c30c5ce" +registries = "General" +uuid = "dce04be8-c92d-5529-be00-80e4d2c0e197" +version = "2.5.0" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" + +[[deps.ArnoldiMethod]] +deps = ["LinearAlgebra", "Random", "StaticArrays"] +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" +registries = "General" +uuid = "ec485272-7323-5ecc-a04f-4719b315124d" +version = "0.4.0" + +[[deps.Arrow]] +deps = ["ArrowTypes", "BitIntegers", "CodecLz4", "CodecZstd", "ConcurrentUtilities", "DataAPI", "Dates", "EnumX", "Mmap", "PooledArrays", "SentinelArrays", "StringViews", "Tables", "TimeZones", "TranscodingStreams", "UUIDs"] +git-tree-sha1 = "4a69a3eadc1f7da78d950d1ef270c3a62c1f7e01" +registries = "General" +uuid = "69666777-d1a9-59fb-9406-91d4454c9d45" +version = "2.8.1" + +[[deps.ArrowTypes]] +deps = ["Sockets", "UUIDs"] +git-tree-sha1 = "404265cd8128a2515a81d5eae16de90fdef05101" +registries = "General" +uuid = "31f734f8-188a-4ce0-8406-c8a06bd891cd" +version = "2.3.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.BitIntegers]] +deps = ["Random"] +git-tree-sha1 = "091d591a060e43df1dd35faab3ca284925c48e46" +registries = "General" +uuid = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" +version = "0.3.7" + +[[deps.BufferedStreams]] +git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643" +registries = "General" +uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d" +version = "1.2.2" + +[[deps.CEnum]] +git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc" +registries = "General" +uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" +version = "0.5.0" + +[[deps.CRC32c]] +uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc" +version = "1.11.0" + +[[deps.CSV]] +deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"] +git-tree-sha1 = "abed1e735dd4152f48c90cf0767e1790e25f332f" +registries = "General" +uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +version = "0.10.17" + +[[deps.CodeTracking]] +deps = ["InteractiveUtils", "REPL", "UUIDs"] +git-tree-sha1 = "cfb7a2e89e245a9d5016b70323db412b3a7438d5" +registries = "General" +uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" +version = "3.0.2" + +[[deps.CodecBase]] +deps = ["TranscodingStreams"] +git-tree-sha1 = "40956acdbef3d8c7cc38cba42b56034af8f8581a" +registries = "General" +uuid = "6c391c72-fb7b-5838-ba82-7cfb1bcfecbf" +version = "0.3.4" + +[[deps.CodecInflate64]] +deps = ["TranscodingStreams"] +git-tree-sha1 = "d981a6e8656b1e363a2731716f46851a2257deb7" +registries = "General" +uuid = "6309b1aa-fc58-479c-8956-599a07234577" +version = "0.1.3" + +[[deps.CodecLz4]] +deps = ["Lz4_jll", "TranscodingStreams"] +git-tree-sha1 = "d58afcd2833601636b48ee8cbeb2edcb086522c2" +registries = "General" +uuid = "5ba52731-8f18-5e0d-9241-30f10d1ec561" +version = "0.4.6" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "970758a3d591a2a5c2a907c53f2e2f8c1b1d3537" +registries = "General" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.9" + +[[deps.CodecZstd]] +deps = ["TranscodingStreams", "Zstd_jll"] +git-tree-sha1 = "da54a6cd93c54950c15adf1d336cfd7d71f51a56" +registries = "General" +uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2" +version = "0.8.7" + +[[deps.CommonSolve]] +deps = ["PrecompileTools"] +git-tree-sha1 = "6c389fa857f6ca5a95474b52a52023fd77f24cb7" +registries = "General" +uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +version = "0.2.14" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad" +registries = "General" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.18.1" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.Compiler]] +git-tree-sha1 = "382d79bfe72a406294faca39ef0c3cef6e6ce1f1" +registries = "General" +uuid = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1" +version = "0.1.1" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.5.5+2" + +[[deps.CompositionsBase]] +git-tree-sha1 = "802bb88cd69dfd1509f6670416bd4434015693ad" +registries = "General" +uuid = "a33af91c-f02d-484b-be07-31d278c5ca2b" +version = "0.1.2" +weakdeps = ["InverseFunctions"] + + [deps.CompositionsBase.extensions] + CompositionsBaseInverseFunctionsExt = "InverseFunctions" + +[[deps.ConcurrentUtilities]] +deps = ["Serialization", "Sockets"] +git-tree-sha1 = "3c9be947934c38475bafe822c6d61aaed17f0738" +registries = "General" +uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" +version = "2.6.0" + +[[deps.ConstructionBase]] +git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb" +registries = "General" +uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" +version = "1.6.0" + + [deps.ConstructionBase.extensions] + ConstructionBaseIntervalSetsExt = "IntervalSets" + ConstructionBaseLinearAlgebraExt = "LinearAlgebra" + ConstructionBaseStaticArraysExt = "StaticArrays" + + [deps.ConstructionBase.weakdeps] + IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" + LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[[deps.Crayons]] +git-tree-sha1 = "54b76cbb40d9a0f5368c880725b2f141da77c94f" +registries = "General" +uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" +version = "4.2.0" + +[[deps.DBInterface]] +git-tree-sha1 = "265d7f87f329c063cbde41920f9d00713a43cedd" +registries = "General" +uuid = "a10d1c49-ce27-4219-8d33-6db1a4562965" +version = "2.7.0" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +registries = "General" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataFrames]] +deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] +git-tree-sha1 = "5fab31e2e01e70ad66e3e24c968c264d1cf166d6" +registries = "General" +uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +version = "1.8.2" + +[[deps.DataStructures]] +deps = ["OrderedCollections"] +git-tree-sha1 = "b0bc6d2cad1fed8b7fd59a1551a991cb3d2809e6" +registries = "General" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.19.6" + +[[deps.DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +registries = "General" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.Decimals]] +git-tree-sha1 = "e98abef36d02a0ec385d68cd7dadbce9b28cbd88" +registries = "General" +uuid = "abce61dc-4473-55a0-ba07-351d65e31d42" +version = "0.4.1" + +[[deps.Distances]] +deps = ["LinearAlgebra", "Statistics", "StatsAPI"] +git-tree-sha1 = "c7e3a542b999843086e2f29dac96a618c105be1d" +registries = "General" +uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" +version = "0.10.12" + + [deps.Distances.extensions] + DistancesChainRulesCoreExt = "ChainRulesCore" + DistancesSparseArraysExt = "SparseArrays" + + [deps.Distances.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" +version = "1.11.0" + +[[deps.Distributions]] +deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "Roots", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"] +git-tree-sha1 = "a958ab3a40c755563f5e1405c0846cb0446bf19d" +registries = "General" +uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" +version = "0.25.131" + + [deps.Distributions.extensions] + DistributionsChainRulesCoreExt = "ChainRulesCore" + DistributionsDensityInterfaceExt = "DensityInterface" + DistributionsSparseConnectivityTracerExt = "SparseConnectivityTracer" + DistributionsTestExt = "Test" + + [deps.Distributions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d" + SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5" + Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.DocStringExtensions]] +git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" +registries = "General" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.5" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.7.0" + +[[deps.EnumX]] +git-tree-sha1 = "c49898e8438c828577f04b92fc9368c388ac783c" +registries = "General" +uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" +version = "1.0.7" + +[[deps.ExprTools]] +git-tree-sha1 = "d2e49e7efd29719d6f28b891b0e0e159daa9d2b4" +registries = "General" +uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" +version = "0.1.11" + +[[deps.EzXML]] +deps = ["Printf", "XML2_jll"] +git-tree-sha1 = "7ea1aa5869e2626ccae84480e4f37185bc6f41d3" +registries = "General" +uuid = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" +version = "1.2.3" + +[[deps.FileIO]] +deps = ["Pkg", "Requires", "UUIDs"] +git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819" +registries = "General" +uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +version = "1.20.0" +weakdeps = ["HTTP"] + + [deps.FileIO.extensions] + HTTPExt = "HTTP" + +[[deps.FilePathsBase]] +deps = ["Compat", "Dates"] +git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2" +registries = "General" +uuid = "48062228-2e41-5def-b9a4-89aafe57970f" +version = "0.9.24" +weakdeps = ["Mmap", "Test"] + + [deps.FilePathsBase.extensions] + FilePathsBaseMmapExt = "Mmap" + FilePathsBaseTestExt = "Test" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.FillArrays]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "5bad39456d9f0166184fce2248783dd9862645c1" +registries = "General" +uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" +version = "1.17.0" +weakdeps = ["PDMats", "SparseArrays", "StaticArrays", "Statistics"] + + [deps.FillArrays.extensions] + FillArraysPDMatsExt = "PDMats" + FillArraysSparseArraysExt = "SparseArrays" + FillArraysStaticArraysExt = "StaticArrays" + FillArraysStatisticsExt = "Statistics" + +[[deps.Future]] +deps = ["Random"] +uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" +version = "1.11.0" + +[[deps.Gamma]] +deps = ["LogExpFunctions"] +git-tree-sha1 = "becc397f7cfb06e343496ae6ffb04818a851da51" +registries = "General" +uuid = "a0844989-3bd2-4988-8bea-c9407ab0941b" +version = "1.2.0" + +[[deps.GeneralUtils]] +deps = ["AWS", "AWSS3", "Base64", "CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "Graphs", "HTTP", "JSON", "LibPQ", "MbedTLS", "NATS", "OrderedCollections", "PrettyPrinting", "Random", "Revise", "SHA", "StringDistances", "URIs", "UUIDs"] +git-tree-sha1 = "e4472b9ae1262bb55fb8673399cfea21891a6ab8" +repo-rev = "main" +repo-url = "https://git.yiem.cc/ton/GeneralUtils" +uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe" +version = "0.6.10" + +[[deps.GitHub]] +deps = ["Base64", "Dates", "HTTP", "JSON", "MbedTLS", "Sockets", "SodiumSeal", "URIs"] +git-tree-sha1 = "33de1bc5665494f0a9669fd6027e28180709d874" +registries = "General" +uuid = "bc5e4493-9b4d-5f90-b8aa-2b2bcaad7a26" +version = "5.13.0" + +[[deps.Graphs]] +deps = ["ArnoldiMethod", "DataStructures", "Inflate", "LinearAlgebra", "Random", "SimpleTraits", "SparseArrays", "Statistics"] +git-tree-sha1 = "7eb45fe833a5b7c51cf6d89c5a841d5967e44be3" +registries = "General" +uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" +version = "1.14.0" + + [deps.Graphs.extensions] + GraphsSharedArraysExt = "SharedArrays" + + [deps.Graphs.weakdeps] + Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" + SharedArrays = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[deps.HTTP]] +deps = ["Base64", "CodecZlib", "Dates", "EnumX", "PrecompileTools", "Random", "Reseau", "SHA", "URIs", "UUIDs", "Zlib_jll"] +git-tree-sha1 = "d6e390d1515836237b837fb7b6fca7ba1d752db6" +registries = "General" +uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" +version = "2.6.5" + +[[deps.HashArrayMappedTries]] +git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae" +registries = "General" +uuid = "076d061b-32b6-4027-95e0-9a2c6f6d7e74" +version = "0.2.0" + +[[deps.HypergeometricFunctions]] +deps = ["Gamma", "LinearAlgebra"] +git-tree-sha1 = "31bb6c92405c084617facc1d7ed9eb6c402d061e" +registries = "General" +uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a" +version = "0.3.30" + +[[deps.ICU_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "b3d8be712fbf9237935bde0ce9b5a736ae38fc34" +registries = "General" +uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b" +version = "76.2.0+0" + +[[deps.Infinity]] +deps = ["Dates", "Random", "Requires"] +git-tree-sha1 = "cf8234411cbeb98676c173f930951ea29dca3b23" +registries = "General" +uuid = "a303e19e-6eb4-11e9-3b09-cd9505f79100" +version = "0.2.4" + +[[deps.Inflate]] +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" +registries = "General" +uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" +version = "0.1.5" + +[[deps.IniFile]] +git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625" +registries = "General" +uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f" +version = "0.5.1" + +[[deps.InlineStrings]] +git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d" +registries = "General" +uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" +version = "1.4.5" +weakdeps = ["ArrowTypes", "Parsers"] + + [deps.InlineStrings.extensions] + ArrowTypesExt = "ArrowTypes" + ParsersExt = "Parsers" + +[[deps.InputBuffers]] +git-tree-sha1 = "e5392ea00942566b631e991dd896942189937b2f" +registries = "General" +uuid = "0c81fc1b-5583-44fc-8770-48be1e1cca08" +version = "1.1.1" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.Intervals]] +deps = ["ArrowTypes", "Dates", "Printf", "RecipesBase", "Serialization", "TimeZones"] +git-tree-sha1 = "d6fe00b123e32ddd17231b35d69a6394e696fd5a" +registries = "General" +uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5" +version = "1.11.0" + +[[deps.InverseFunctions]] +git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb" +registries = "General" +uuid = "3587e190-3f89-42d0-90ee-14403ec27112" +version = "0.1.17" +weakdeps = ["Dates", "Test"] + + [deps.InverseFunctions.extensions] + InverseFunctionsDatesExt = "Dates" + InverseFunctionsTestExt = "Test" + +[[deps.InvertedIndices]] +git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" +registries = "General" +uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" +version = "1.3.1" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758" +registries = "General" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.6" + +[[deps.IterTools]] +git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023" +registries = "General" +uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e" +version = "1.10.0" + +[[deps.IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +registries = "General" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7204148362dafe5fe6a273f855b8ccbe4df8173e" +registries = "General" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.8.0" + +[[deps.JSON]] +deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"] +git-tree-sha1 = "c7345ab1a7ca4dc8a02c9f6510da0d9857bbe513" +registries = "General" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "1.7.1" +weakdeps = ["ArrowTypes"] + + [deps.JSON.extensions] + JSONArrowExt = ["ArrowTypes"] + +[[deps.JSON3]] +deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"] +git-tree-sha1 = "411eccfe8aba0814ffa0fdf4860913ed09c34975" +registries = "General" +uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +version = "1.14.3" +weakdeps = ["ArrowTypes"] + + [deps.JSON3.extensions] + JSON3ArrowExt = ["ArrowTypes"] + +[[deps.JuliaInterpreter]] +deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"] +git-tree-sha1 = "c3d401f110454b4ea24a76be33f6ee0d7d385103" +registries = "General" +uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" +version = "0.11.4" + +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.12.0" + +[[deps.Kerberos_krb5_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0f2899fdadaab4b8f57db558ba21bdb4fb52f1f0" +registries = "General" +uuid = "b39eb1a6-c29a-53d7-8c32-632cd16f18da" +version = "1.21.3+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "f88f3ccef05a6a72a0cf0ed417c8fd68530f4ab2" +registries = "General" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.4.1" + +[[deps.LayerDicts]] +git-tree-sha1 = "6087ad3521d6278ebe5c27ae55e7bbb15ca312cb" +registries = "General" +uuid = "6f188dcb-512c-564b-bc01-e0f76e72f166" +version = "1.0.0" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "1.0.0" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "Zstd_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.18.0+1" + +[[deps.LibGit2]] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.9.1+0" + +[[deps.LibPQ]] +deps = ["CEnum", "DBInterface", "Dates", "Decimals", "DocStringExtensions", "FileWatching", "Infinity", "Intervals", "IterTools", "LayerDicts", "LibPQ_jll", "Libdl", "Memento", "OffsetArrays", "SQLStrings", "Tables", "TimeZones", "UTCDateTimes"] +git-tree-sha1 = "3d227cd13cbf1e9a54d7748dab33e078da6f9168" +registries = "General" +uuid = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1" +version = "1.18.0" + +[[deps.LibPQ_jll]] +deps = ["Artifacts", "ICU_jll", "JLLWrappers", "Kerberos_krb5_jll", "Libdl", "OpenSSL_jll", "Zstd_jll"] +git-tree-sha1 = "c692057e05ba6da348bc45d5dab8c7a2c88da518" +registries = "General" +uuid = "08be9ffa-1c94-5ee5-a977-46a84ec9b350" +version = "16.14.0+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl", "OpenSSL_jll", "Zlib_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.103+0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" +registries = "General" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.18.0+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.13.0" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "bba2d9aa057d8f126415de240573e86a8f39d2a1" +registries = "General" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "1.0.1" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.LoweredCodeUtils]] +deps = ["CodeTracking", "Compiler", "JuliaInterpreter"] +git-tree-sha1 = "1d4c737ab26f51ceed52ab2019c09b7660eb7440" +registries = "General" +uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" +version = "3.8.0" + +[[deps.Lz4_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "191686b1ac1ea9c89fc52e996ad15d1d241d1e33" +registries = "General" +uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" +version = "1.10.1+0" + +[[deps.MD5]] +deps = ["Random", "SHA"] +git-tree-sha1 = "1576f756617d31eb397a4a517b68562fd28dc2b4" +registries = "General" +uuid = "6ac74813-4b46-53a4-afec-0b5dc9d7885c" +version = "0.2.3" + +[[deps.MacroTools]] +git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522" +registries = "General" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.16" + +[[deps.Markdown]] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MbedTLS]] +deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] +git-tree-sha1 = "8785729fa736197687541f7053f6d8ab7fc44f92" +registries = "General" +uuid = "739be429-bea8-5141-9913-cc70e7f3736d" +version = "1.1.10" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ff69a2b1330bcb730b9ac1ab7dd680176f5896b8" +registries = "General" +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.1010+0" + +[[deps.Memento]] +deps = ["Dates", "Distributed", "Requires", "Serialization", "Sockets", "Test", "UUIDs"] +git-tree-sha1 = "e03a25cb3b6569623f8246d3d8b3faa7ce86f4ad" +registries = "General" +uuid = "f28f55f0-a522-5efc-85c2-fe41dfb9b2d9" +version = "1.5.0" + +[[deps.Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" +registries = "General" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.2.0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.Mocking]] +deps = ["Compat", "ExprTools"] +git-tree-sha1 = "2c140d60d7cb82badf06d8783800d0bcd1a7daa2" +registries = "General" +uuid = "78c3b35d-d492-501b-9361-3d52fe80e533" +version = "0.8.1" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2026.8.13" + +[[deps.NATS]] +deps = ["Base64", "BufferedStreams", "CodecBase", "Dates", "DocStringExtensions", "JSON3", "MbedTLS", "NanoDates", "Random", "ScopedValues", "Sockets", "Sodium", "StructTypes", "URIs"] +git-tree-sha1 = "a1cdf34ba90ee5cd2658e487d3277ffafee712ce" +registries = "General" +uuid = "55e73f9c-eeeb-467f-b4cc-a633fde63d2a" +version = "0.1.1" + +[[deps.NanoDates]] +deps = ["Dates", "Parsers"] +git-tree-sha1 = "77c7e98ca39aefb481f9b97a2f4f5c5471c08a1d" +registries = "General" +uuid = "46f1a544-deae-4307-8689-c12aa3c955c6" +version = "1.1.0" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.3.0" + +[[deps.OffsetArrays]] +git-tree-sha1 = "117432e406b5c023f665fa73dc26e79ec3630151" +registries = "General" +uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" +version = "1.17.0" + + [deps.OffsetArrays.extensions] + OffsetArraysAdaptExt = "Adapt" + + [deps.OffsetArrays.weakdeps] + Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.30+0" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.7+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.5.6+0" + +[[deps.OpenSpecFun_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" +registries = "General" +uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" +version = "0.5.6+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "05f45c2e0de6259db764adbfd2f1dc6d3f8de13c" +registries = "General" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "2.0.1" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.46.0+0" + +[[deps.PDMats]] +deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"] +git-tree-sha1 = "123266c25174ef6c8d4718920abc206452cf8de6" +registries = "General" +uuid = "90014a1f-27ba-587c-ab20-58faa44d9150" +version = "0.11.41" +weakdeps = ["StatsBase"] + + [deps.PDMats.extensions] + StatsBaseExt = "StatsBase" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "3de8f5e6e90ebfa8d6d1f86997d6cdcd6a912ff3" +registries = "General" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.7" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "Zstd_jll", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.13.0" +weakdeps = ["REPL"] + + [deps.Pkg.extensions] + REPLExt = "REPL" + +[[deps.PooledArrays]] +deps = ["DataAPI", "Future"] +git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" +registries = "General" +uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +version = "1.4.3" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "edbeefc7a4889f528644251bdb5fc9ab5348bc2c" +registries = "General" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.3.4" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "8b770b60760d4451834fe79dd483e318eee709c4" +registries = "General" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.5.2" + +[[deps.PrettyPrinting]] +git-tree-sha1 = "0b7f4ad437e31c51cf5b91fb103579b04025170a" +registries = "General" +uuid = "54e16d92-306c-5ea0-a30b-337be88ac337" +version = "0.4.3" + +[[deps.PrettyTables]] +deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"] +git-tree-sha1 = "1b8aa19f229b1cea7fc93874a52e49db6a854450" +registries = "General" +uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" +version = "3.4.8" + + [deps.PrettyTables.extensions] + PrettyTablesExcelExt = "XLSX" + PrettyTablesTypstryExt = "Typstry" + + [deps.PrettyTables.weakdeps] + Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e" + XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + +[[deps.ProgressMeter]] +deps = ["Distributed", "Printf"] +git-tree-sha1 = "fbb92c6c56b34e1a2c4c36058f68f332bec840e7" +registries = "General" +uuid = "92933f4c-e287-5a05-a399-4b506db050ca" +version = "1.11.0" + +[[deps.PtrArrays]] +git-tree-sha1 = "4fbbafbc6251b883f4d2705356f3641f3652a7fe" +registries = "General" +uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" +version = "1.4.0" + +[[deps.QuadGK]] +deps = ["DataStructures", "LinearAlgebra"] +git-tree-sha1 = "5e8e8b0ab68215d7a2b14b9921a946fee794749e" +registries = "General" +uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" +version = "2.11.3" + + [deps.QuadGK.extensions] + QuadGKEnzymeExt = "Enzyme" + + [deps.QuadGK.weakdeps] + Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" + +[[deps.REPL]] +deps = ["Base64", "Dates", "FileWatching", "InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.RecipesBase]] +deps = ["PrecompileTools"] +git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff" +registries = "General" +uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +version = "1.3.4" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +registries = "General" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" +registries = "General" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.1" + +[[deps.Reseau]] +deps = ["NetworkOptions", "OpenSSL_jll", "PrecompileTools", "Random", "SHA"] +git-tree-sha1 = "aaee74fab545e9af4b21d10c2203c93c636c4ae0" +registries = "General" +uuid = "802f3686-a58f-41ce-bb0c-3c43c75bba36" +version = "1.4.1" + +[[deps.Retry]] +git-tree-sha1 = "41ac127cd281bb33e42aba46a9d3b25cd35fc6d5" +registries = "General" +uuid = "20febd7b-183b-5ae2-ac4a-720e7ce64774" +version = "0.4.1" + +[[deps.Revise]] +deps = ["CRC32c", "CodeTracking", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Preferences", "REPL", "UUIDs"] +git-tree-sha1 = "f6ad10ad2fa1063c2ae0f75d17d92d1d0cae4c77" +registries = "General" +uuid = "295af30f-e4ad-537b-8983-00126c2a3abe" +version = "3.16.5" +weakdeps = ["Distributed"] + + [deps.Revise.extensions] + DistributedExt = "Distributed" + +[[deps.Rmath]] +deps = ["Random", "Rmath_jll"] +git-tree-sha1 = "5b3d50eb374cea306873b371d3f8d3915a018f0b" +registries = "General" +uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa" +version = "0.9.0" + +[[deps.Rmath_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6d40b2fe70437b01397d2a4d5b020008da4e7019" +registries = "General" +uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f" +version = "0.5.2+0" + +[[deps.Roots]] +deps = ["Accessors", "CommonSolve", "Printf"] +git-tree-sha1 = "13a9e0164267bb9ebc55c1c5d72fceea8f09555b" +registries = "General" +uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665" +version = "3.0.7" + + [deps.Roots.extensions] + RootsChainRulesCoreExt = "ChainRulesCore" + RootsForwardDiffExt = "ForwardDiff" + RootsIntervalRootFindingExt = "IntervalRootFinding" + RootsSymPyExt = "SymPy" + RootsSymPyPythonCallExt = "SymPyPythonCall" + RootsUnitfulExt = "Unitful" + + [deps.Roots.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" + IntervalRootFinding = "d2bf35a9-74e0-55ec-b149-d360ff49b807" + SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6" + SymPyPythonCall = "bc8888f7-b21e-4b7c-a06a-5d9c9496438c" + Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "1.0.0" + +[[deps.SQLStrings]] +git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c" +registries = "General" +uuid = "af517c2e-c243-48fa-aab8-efac3db270f5" +version = "0.1.0" + +[[deps.ScopedValues]] +deps = ["HashArrayMappedTries", "Logging"] +git-tree-sha1 = "67a144433c4ce877ee6d1ada69a124d6b1ecf7be" +registries = "General" +uuid = "7e506255-f358-4e82-b7e4-beb19740aa63" +version = "1.6.2" + +[[deps.Scratch]] +deps = ["Dates"] +git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" +registries = "General" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.3.0" + +[[deps.SentinelArrays]] +deps = ["Dates", "Random"] +git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80" +registries = "General" +uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +version = "1.4.10" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "7ddb0b49c109481b046972c0e4ab02b2127d6a75" +registries = "General" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.6" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" + +[[deps.Sodium]] +deps = ["Base64", "libsodium_jll"] +git-tree-sha1 = "907703e0d50846f300650d7225bdcab145b7bca9" +registries = "General" +uuid = "4f5b5e99-b0ad-42cd-b47a-334e172ec8bd" +version = "1.1.2" + +[[deps.SodiumSeal]] +deps = ["Base64", "Libdl", "libsodium_jll"] +git-tree-sha1 = "80cef67d2953e33935b41c6ab0a178b9987b1c99" +registries = "General" +uuid = "2133526b-2bfb-4018-ac12-889fb3908a75" +version = "0.1.1" + +[[deps.SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "13cd91cc9be159e3f4d95b857fa2aa383b53772a" +registries = "General" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.2.3" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.13.0" + +[[deps.SpecialFunctions]] +deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] +git-tree-sha1 = "429071b23f4c9a13fb6582f807cc2ef454082408" +registries = "General" +uuid = "276daf66-3868-5448-9aa4-cd146d93841b" +version = "2.9.0" + + [deps.SpecialFunctions.extensions] + SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" + + [deps.SpecialFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "fac51faf3bb96e8bc0bf6f9f39ca4955652776bb" +registries = "General" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.19" + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "6ab403037779dae8c514bad259f32a447262455a" +registries = "General" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.4" + +[[deps.Statistics]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "389592b8592e5fb48f498ff60dc6acb4a0e62953" +registries = "General" +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.11.4" +weakdeps = ["SparseArrays"] + + [deps.Statistics.extensions] + SparseArraysExt = ["SparseArrays"] + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "178ed29fd5b2a2cfc3bd31c13375ae925623ff36" +registries = "General" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.8.0" + +[[deps.StatsBase]] +deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "adb9da019510162e67a4493fc235c23203d8b09e" +registries = "General" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.34.13" + +[[deps.StatsFuns]] +deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"] +git-tree-sha1 = "91a5737baed20ee31f3faea0e51f57461f6a689e" +registries = "General" +uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c" +version = "2.2.1" + + [deps.StatsFuns.extensions] + StatsFunsChainRulesCoreExt = "ChainRulesCore" + StatsFunsInverseFunctionsExt = "InverseFunctions" + + [deps.StatsFuns.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.StringDistances]] +deps = ["Distances", "StatsAPI"] +git-tree-sha1 = "456f343c7a6c8f52d8b501c0da2a05538ab1b907" +registries = "General" +uuid = "88034a9c-02f8-509d-84a9-84ec65e18404" +version = "1.0.1" + +[[deps.StringManipulation]] +deps = ["PrecompileTools"] +git-tree-sha1 = "773065c6e0e903924a9d838259be74338422aef2" +registries = "General" +uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" +version = "0.5.0" + +[[deps.StringViews]] +git-tree-sha1 = "f2dcb92855b31ad92fe8f079d4f75ac57c93e4b8" +registries = "General" +uuid = "354b36f9-a18e-4713-926e-db85100087ba" +version = "1.3.7" + +[[deps.StructTypes]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "159331b30e94d7b11379037feeb9b690950cace8" +registries = "General" +uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" +version = "1.11.0" + +[[deps.StructUtils]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "2d0fc55c61321ba245c47be599570d11bac50303" +registries = "General" +uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42" +version = "2.8.5" + + [deps.StructUtils.extensions] + StructUtilsMeasurementsExt = ["Measurements"] + StructUtilsStaticArraysCoreExt = ["StaticArraysCore"] + StructUtilsTablesExt = ["Tables"] + + [deps.StructUtils.weakdeps] + Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" + StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" + Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + +[[deps.SuiteSparse]] +deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] +uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.10.1+0" + +[[deps.SymDict]] +deps = ["Test"] +git-tree-sha1 = "0108ccdaea3ef69d9680eeafc8d5ad198b896ec8" +registries = "General" +uuid = "2da68c74-98d7-5633-99d6-8493888d7b1e" +version = "0.3.0" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.TZJData]] +deps = ["Artifacts"] +git-tree-sha1 = "d3b30a9f29a898e21fb4bdf280101b7a12e1f39c" +registries = "General" +uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7" +version = "1.5.1+2025b" + +[[deps.TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +registries = "General" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[deps.Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] +git-tree-sha1 = "a94d9bdda1b7bed0046cea645639ab3f62196fac" +registries = "General" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.14.0" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.TimeZones]] +deps = ["Artifacts", "Dates", "Downloads", "InlineStrings", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"] +git-tree-sha1 = "d422301b2a1e294e3e4214061e44f338cafe18a2" +registries = "General" +uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53" +version = "1.22.2" +weakdeps = ["RecipesBase"] + + [deps.TimeZones.extensions] + TimeZonesRecipesBaseExt = "RecipesBase" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" +registries = "General" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.11.3" + +[[deps.URIs]] +git-tree-sha1 = "908fec9df6c5de98548ead82a468c95ccf6cd263" +registries = "General" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.7.0" + +[[deps.UTCDateTimes]] +deps = ["Dates", "TimeZones"] +git-tree-sha1 = "4af3552bf0cf4a071bf3d14bd20023ea70f31b62" +registries = "General" +uuid = "0f7cfa37-7abf-4834-b969-a8aa512401c2" +version = "1.6.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.WeakRefStrings]] +deps = ["DataAPI", "InlineStrings", "Parsers"] +git-tree-sha1 = "0716e01c3b40413de5dedbc9c5c69f27cddfddfc" +registries = "General" +uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" +version = "1.4.3" + +[[deps.WorkerUtilities]] +git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7" +registries = "General" +uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60" +version = "1.6.1" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] +git-tree-sha1 = "3f3315d89fc954a28f5b471bce698ed6e27481be" +registries = "General" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.15.3+0" + +[[deps.XMLDict]] +deps = ["EzXML", "IterTools", "OrderedCollections"] +git-tree-sha1 = "8e8cdc9c93e1f442eba471b56db6f86ffeb1a33a" +registries = "General" +uuid = "228000da-037f-5747-90a9-8195ccbf91a5" +version = "0.4.2" + +[[deps.ZipArchives]] +deps = ["ArgCheck", "CodecInflate64", "CodecZlib", "InputBuffers", "PrecompileTools", "TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "83f728ecb873c58b794964f8b4bed811814d4b0d" +registries = "General" +uuid = "49080126-0e18-4c2a-b176-c102e4b3760c" +version = "2.6.0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.3.1+2" + +[[deps.Zstd_jll]] +deps = ["CompilerSupportLibraries_jll", "Libdl"] +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.7+1" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.15.0+0" + +[[deps.libsodium_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "011b0a7331b41c25524b64dc42afc9683ee89026" +registries = "General" +uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8" +version = "1.0.21+0" + +[[deps.msghandler]] +deps = ["Arrow", "Base64", "DataFrames", "Dates", "GeneralUtils", "HTTP", "JSON", "NATS", "PrettyPrinting", "UUIDs"] +git-tree-sha1 = "e7889a120327db84cdf1b1acf4a2c7caba6e8ef7" +repo-rev = "main" +repo-url = "https://git.yiem.cc/ton/msghandler" +uuid = "f2724d33-f338-4a57-b9f8-1be882570d10" +version = "1.4.0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.67.1+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.8.2+0" + +[registries.General] +url = "https://github.com/JuliaRegistries/General.git" +uuid = "23338594-aafe-5451-b93e-139f81909106" diff --git a/Project.toml b/Project.toml new file mode 100755 index 0000000..07c19b4 --- /dev/null +++ b/Project.toml @@ -0,0 +1,23 @@ +[deps] +Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +GeneralUtils = "c6c72f09-b708-4ac8-ac7c-2084d70108fe" +HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1" +NATS = "55e73f9c-eeeb-467f-b4cc-a633fde63d2a" +PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337" +ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" +URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +ZipArchives = "49080126-0e18-4c2a-b176-c102e4b3760c" +msghandler = "f2724d33-f338-4a57-b9f8-1be882570d10" + +[sources] +GeneralUtils = {rev = "main", url = "https://git.yiem.cc/ton/GeneralUtils"} +msghandler = {rev = "main", url = "https://git.yiem.cc/ton/msghandler"} diff --git a/README.md b/README.md new file mode 100755 index 0000000..fd6615e --- /dev/null +++ b/README.md @@ -0,0 +1,194 @@ +# llamacpp_embedding_1_nats + +A Julia backend service that forwards messages from NATS to llama.cpp embedding API. + +## Overview + +This service acts as a bridge between a NATS message broker and a llama.cpp HTTP embedding endpoint. It receives messages via NATS queue groups and forwards them to the llama.cpp server for embedding processing. + +## Architecture + +``` +┌─────────────┐ NATS ┌──────────────────────┐ HTTP ┌─────────────────┐ +│ Requester │ ────────────> │ this service │ ────────────> │ llama.cpp │ +│ │ <─────────── <│ (embedding) │ <─────────── <│ /v1/embeddings │ +│ │ Queue │ │ │ │ +│ │ Group │ │ │ │ +└─────────────┘ (free) └──────────────────────┘ └─────────────────┘ +``` + +## Features + +- NATS queue group subscription for load balancing across multiple service instances +- HTTP connection pooling cleanup to prevent llama.cpp server timeouts +- Health check endpoint on port 3000 +- Service registration with load balancer +- Message envelope packing/unpacking via `msghandler` package + +## Prerequisites + +- Julia 1.12+ +- NATS server +- llama.cpp server running with embedding endpoint +- Docker (for containerized deployment) + +## Configuration + +Edit `appconfig.json` to configure the service: + +```json +{ + "config_version": "0.1", + "nats_server_info": { + "url": "nats.yiem.cc" + }, + "this_service_name": "embedding", + "this_service_input_channel": { + "nats": "sommpanion.backend.embedding_nats_1.v1.tonpc.inbox" + }, + "externalService": { + "fileserver": { + "url": "https://fileserver.yiem.cc" + }, + "servicesloadbalancer": { + "url": "sommpanion.backend.servicesloadbalancer.v1.inbox" + }, + "llamacpp_embedding": { + "url": "http://192.168.88.50:9010/v1/embeddings" + } + } +} +``` + +## Local Development + +```bash +julia --project -t 2 main.jl +``` + +The service exposes: +- Health check endpoints on port 3000: `/`, `/health`, `/ready` +- NATS subscriber on configured topic + +## Docker Deployment + +```bash +docker build -t llamacpp_embedding_1_nats . +docker run -d --name llamacpp_embedding_1_nats -p 3000:3000 llamacpp_embedding_1_nats +``` + +Or use docker-compose: + +```bash +docker-compose up -d +``` + +## Usage Example + +### Using NATS with msghandler + +```julia +using JSON, NATS, msghandler + +const config = JSON.parsefile("./appconfig.json") + +# Prepare documents to embed +documents = [ + "System architecture specification for heart monitor logic.", + "NATS broker cluster configuration options for low-latency.", + "Julia signal processing worker initialization sequence." +] + +documents_dict = Dict("documents" => documents) + +# Pack message envelope +payloads = [("documents", documents_dict, "dictionary")] +message_envelope, forward_msg_envelope_json_str = msghandler.smartpack( + config["this_service_input_channel"]["nats"], + payloads; + msg_purpose="embedding", + broker_url=config["nats_server_info"]["url"], + fileserver_url=config["externalService"]["fileserver"]["url"]) + +# Send via NATS +connection = NATS.connect(config["nats_server_info"]["url"]) +reply = NATS.request(connection, + config["this_service_input_channel"]["nats"], + forward_msg_envelope_json_str, + timeout=120) + +# Unpack response +incoming_env_json_str = String(reply.payload) +incoming_env = msghandler.smartunpack(incoming_env_json_str) +embedding_response = incoming_env["payloads"][1][2] + +# Process embeddings +for (i, item) in enumerate(embedding_response["data"]) + vector = item["embedding"] + println("Document $i embedding vector size: ", length(vector)) +end +``` + +### Using Direct HTTP (for testing) + +```julia +using HTTP, JSON + +url = "http://localhost:9010/v1/embeddings" +headers = [ + "Content-Type" => "application/json", + "Authorization" => "Bearer no-key" +] + +documents = [ + "System architecture specification for heart monitor logic.", + "NATS broker cluster configuration options for low-latency." +] + +body_data = Dict( + "model" => "embeddinggemma-300M-BF16", + "input" => documents +) + +response = HTTP.post(url, headers, JSON.json(body_data)) +parsed = JSON.parse(String(response.body)) + +for (i, item) in enumerate(parsed["data"]) + println("Document $i embedding size: ", length(item["embedding"])) +end +``` + +## Message Flow + +1. **Registration**: Service registers with load balancer via NATS +2. **Subscription**: Subscribes to input topic with queue group "group1" +3. **Processing**: + - Receives message via NATS + - Unpacks message envelope + - Forwards to llama.cpp HTTP endpoint + - Cleans up HTTP connections + - Repacks response and publishes to reply-to topic + - Re-registers with load balancer + +## Health Monitoring + +```bash +curl http://localhost:3000/health +curl http://localhost:3000/ready +``` + +## Dependencies + +- `NATS.jl` - NATS client +- `HTTP.jl` - HTTP client with connection pooling +- `JSON.jl` - JSON serialization +- `msghandler` - Message envelope handling +- `GeneralUtils` - Utility functions + +## Scaling + +Multiple instances can be deployed. NATS queue group "group1" ensures each message is delivered to only one instance. + +## License + +MIT diff --git a/appconfig.json b/appconfig.json new file mode 100755 index 0000000..f69bca7 --- /dev/null +++ b/appconfig.json @@ -0,0 +1,99 @@ +{ + "config_version": "0.1", + "nats_server_info": { + "description": "nats server", + "url": "nats.yiem.cc" + }, + "thisServiceName": "embedding", + "thisServiceInputChannel": { + "inbox": "embedding.janeWhitePC.v1.inbox" + }, + "thisServiceOutputChannel": { + "status": "embedding.janeWhitePC.v1.status" + }, + "externalService": { + "servicesloadbalancer": { + "description": "load balancer", + "url": "serviceLoadBalancer.v1.inbox" + }, + "llamacpp_embedding": { + "description": "llamacpp_embedding", + "url": "https://llmembedding.yiem.cc/v1/embeddings" + }, + "objectStorage": { + "description": "shared S3 object storage for file uploads/downloads", + "endpoint": "https://s3-api.yiem.cc", + "access_key_id": "GKb080154a2e5b19100b1b2c6e", + "secret_access_key": "a2c6b1379c2f731d3e6e5a408dd4d6cffca7511717675f55114ff94828febca1", + "bucket": "sommpanion" + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100755 index 0000000..09f1750 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,71 @@ +# A Docker Compose must always start with the version tag. +# We use '3' because it's the last version. +version: "3.8" + +# You should know that Docker Compose works with services. +# 1 service = 1 container. +# For example, a service, a server, a client, a database... +# We use the keyword 'services' to start to create services. +services: + wine-db-api-v1: # service name + container_name: llamacpp_embedding_1_nats + build: . + image: llamacpp_embedding_1_nats + restart: unless-stopped + environment: + - TZ=Asia/Bangkok + # network_mode: "app-network" + # networks: + # - backend + # ports: + # - 3000:3000 + # volumes: + # - ./mountvolume:/appfolder/mountvolume + #privileged: true + deploy: + resources: + limits: + cpus: '2' + # memory: 7200M + # reservations: + # devices: + # - driver: nvidia + # device_ids: ['2', '3'] # choose between device_ids or count + # count: 1 + # capabilities: [gpu] + + # # The name of our service is "database" + # # but you can use the name of your choice. + # # Note: This may change the commands you are going to use a little bit. + # database: + # # Official Postgres image from DockerHub (we use the last version) + # image: 'postgres:latest' + + # # By default, a Postgres database is running on the 5432 port. + # # If we want to access the database from our computer (outside the container), + # # we must share the port with our computer's port. + # # The syntax is [port we want on our machine]:[port we want to retrieve in the container] + # # Note: You are free to change your computer's port, + # # but take into consideration that it will change the way + # # you are connecting to your database. + # networks: + # - backend + # ports: + # - 5432:5432 + + # environment: + # POSTGRES_USER: username # The PostgreSQL user (useful to connect to the database) + # POSTGRES_PASSWORD: password # The PostgreSQL password (useful to connect to the database) + + # volumes: + # # In this example, we share the folder `db-data` in our root repository, with the default PostgreSQL data path. + # # It means that every time the repository is modifying the data inside + # # of `/var/lib/postgresql/data/`, automatically the change will appear in `db-data`. + # # You don't need to create the `db-data` folder. Docker Compose will do it for you. + # - ./data/db-data/:/var/lib/postgresql/data/ + +# networks: +# default: + # name: https-network + # driver: bridge + # external: true diff --git a/env_preparation.jl b/env_preparation.jl new file mode 100755 index 0000000..601c802 --- /dev/null +++ b/env_preparation.jl @@ -0,0 +1,286 @@ +""" +For docker container: + - This script must be placed at the root of your project e.g. /appfolder/app + - This script expects all other files are in workfolder/temp e.g. /appfolder/app/temp +""" + +using Pkg + +programName = "someapp" # use my project name here +devPackageName = "somepackage" +appfolder = pwd() # use current folder as project folder + + +# ---------------------------------------------- 100 --------------------------------------------- # + +""" list of all recommend package +required_packages = [ + "Arrow" + "BenchmarkTools" + "CSV" + "CUDA" + "ClickHouse" + "DataFrames" + "DataFramesMeta" + "DataStructures" + "Distributions" + "Enzyme" + "FileIO" + "Flux" + "Genie" + "HTTP" + "ImageTransformations" + "Images" + "IterTools" + "JSON3" + "LinearAlgebra" + "Logging" + "Lux" + "MLDataUtils" + "MLDatasets" + "MLLabelUtils" + "Makie" + "ODBC" + "Optimisers" + "ParameterSchedulers" + "PkgTemplates" + "ProgressMeter" + "PyCall" + "Random" + "Revise" + "Serialization" + "Statistics" + "Stipple" + "TensorBoardLogger" + "TextAnalysis" + "Transformers" + "UUIDs" + "WordTokenizers" + "Zygote" + "https://github.com/denglerchr/Mosquitto.jl" +] +""" + + +# ---------------------------------------------------------------------------- # +# add julia packages in container's general registry folder # +# ---------------------------------------------------------------------------- # +privatejuliapkgpath = "$appfolder/dev" +required_julia_private_packages = [ #CHANGE + # "https://git.yiem.cc/ton/GeneralUtils", + # "https://git.yiem.cc/ton/LLMMCTS", + # "https://git.yiem.cc/ton/SQLLLM", + # "https://git.yiem.cc/ton/YiemAgent -b v0.1.1", +] + + +# ---------------------------------------------------------------------------- # +# add julia package in app folder # +# ---------------------------------------------------------------------------- # +required_julia_packages = [ #CHANGE + # "Revise", + # "CondaPkg", + # "PythonCall", + # "HTTP", + # "Oxygen", + # "JSON", + # "Dates", + # "UUIDs", + # "Random", + # "URIs", + # "DataStructures", + # "FileIO", + # "PrettyPrinting", + # "ZipArchives", + # "LibPQ", + # "ProgressMeter", + # "NATS" +] # use only for new project # use only for new project + +function copy_folder_contents(src_folder::AbstractString, dst_folder::AbstractString) + # Get a list of all files in the source folder + files = readdir(src_folder) + + # Iterate over each file and copy it to the destination folder + for itemname in files + src_path = joinpath(src_folder, itemname) + dst_path = joinpath(dst_folder, itemname) + if isdir(src_path) + run(`cp -r $src_path $dst_path`) + else + run(`cp $src_path $dst_path`) + end + end +end + +function install_required_julia_packages(required_julia_packages) + for i in required_julia_packages + println("adding ", i) + if startswith(i, "http") + Pkg.add(url=i) + else + Pkg.add(i) + end + end +end + +function install_required_julia_private_packages(required_julia_private_packages::Vector, + appfolderpath::String, privatejuliapkgpath::String) + + !isdir(privatejuliapkgpath) ? mkpath(privatejuliapkgpath) : nothing + cd(privatejuliapkgpath) + + if length(required_julia_private_packages) > 0 + # remove private packages already in Project.toml in case private package path is different + for i in required_julia_private_packages + _pkgname = split(i, "/")[end] + _pkgname = split(_pkgname, " ")[1] + pkgname = split(_pkgname, ".")[1] + println("removing Julia private package: $pkgname if it already in Project.toml") + try Pkg.rm(pkgname) catch end + end + + # clone all required julia private packages + for i in required_julia_private_packages + _pkgname = split(i, "/")[end] + _pkgname = split(_pkgname, " ")[1] + pkgname = split(_pkgname, ".")[1] + pkgpath = joinpath(privatejuliapkgpath, pkgname) + println("cloning Julia private package: $pkgname") + + # only clone a package if it is not found in local + gitcommand = "git clone $i" + args = split(gitcommand, " ") + !isdir(pkgpath) ? run(`$args`) : nothing + end + + # install all required julia private packages + for i in required_julia_private_packages + _pkgname = split(i, "/")[end] + _pkgname = split(_pkgname, " ")[1] + pkgname = split(_pkgname, ".")[1] # in case there is a .jl at the end of package name + pkgpath = joinpath(privatejuliapkgpath, pkgname) + println("installing Julia private package: $pkgname") + + Pkg.develop(path=pkgpath) + end + end + + cd(appfolderpath) +end + + +# ------------------------------------------------------------------------------------------------ # +# start script # +# ------------------------------------------------------------------------------------------------ # + +# all files are put in ./temp (most likely in a docker container) +if isdir(joinpath(appfolder, "temp")) + @show "temp folder is available." + if isfile(joinpath(appfolder, "temp", "Project.toml")) # use this option if one already have project.toml + @show "there is Project.toml. instantiating..." + copy_folder_contents(joinpath(appfolder, "temp"), appfolder) + Pkg.activate(".") + install_required_julia_private_packages(required_julia_private_packages, appfolder, privatejuliapkgpath) + Pkg.instantiate() + else # no Project.toml, create new package + @show "there is no Project.toml. creating new package..." + copy_folder_contents(joinpath(appfolder, "temp"), appfolder) + Pkg.activate(".") + length(required_julia_packages) != 0 ? install_required_julia_packages(required_julia_packages) : nothing + + # for app + write("main.jl", "using Revise # remove when this package is completed\n", "using $devPackageName\n", "# ---------------------------------------------- 100 --------------------------------------------- #\n") + mkdir("testapp") + cd("testapp") + write("runtests.jl", "# ---------------------------------------------- 100 --------------------------------------------- #\n") + cd(appfolder) + install_required_julia_private_packages(required_julia_private_packages, appfolder, privatejuliapkgpath) + + # create new dev package folder + !isdir(privatejuliapkgpath) ? mkpath(privatejuliapkgpath) : nothing + cd(privatejuliapkgpath) + Pkg.generate(devPackageName) + newPackagePath = joinpath(privatejuliapkgpath, devPackageName) + Pkg.develop(path=newPackagePath) + cd(newPackagePath) + mkdir("test") + cd("./test") + write("runtests.jl", "using $devPackageName\n", "# ---------------------------------------------- 100 --------------------------------------------- #\n") + cd(appfolder) + end +else # all files are in the current work folder + @show "temp folder is not available." + if isfile(joinpath(appfolder, "Project.toml")) # use this option if one already have project.toml + @show "there is Project.toml. instantiating..." + Pkg.activate(".") + install_required_julia_private_packages(required_julia_private_packages, appfolder, privatejuliapkgpath) + Pkg.instantiate() + else # create new package folder + @show "there is no Project.toml. creating new package..." + Pkg.activate(".") + length(required_julia_packages) != 0 ? install_required_julia_packages(required_julia_packages) : nothing + + # for app + write("main.jl", "using Revise # remove when this package is completed\n", "using $devPackageName\n", "# ---------------------------------------------- 100 --------------------------------------------- #\n") + mkdir("testapp") + cd("testapp") + write("runtests.jl", "# ---------------------------------------------- 100 --------------------------------------------- #\n") + cd(appfolder) + install_required_julia_private_packages(required_julia_private_packages, appfolder, privatejuliapkgpath) + + # create new dev package folder + !isdir(privatejuliapkgpath) ? mkpath(privatejuliapkgpath) : nothing + cd(privatejuliapkgpath) + Pkg.generate(devPackageName) + newPackagePath = joinpath(privatejuliapkgpath, devPackageName) + Pkg.develop(path=newPackagePath) + cd(newPackagePath) + mkdir("test") + cd("./test") + write("runtests.jl", "using $devPackageName\n", "# ---------------------------------------------- 100 --------------------------------------------- #\n") + cd(appfolder) + + # preparing typical julia project finished now doing project's specific preparation here + end +end + + +# ---------------------------------------------------------------------------- # +# python libraries used by Julia's PythonCall # +# ---------------------------------------------------------------------------- # +pythonlib = [] +channels = ["anaconda", "conda-forge", "pytorch"] +if length(pythonlib) != 0 + using CondaPkg; + for i in channels CondaPkg.add_channel(i) end + for i in pythonlib CondaPkg.add_pip(i) end +end + +# ---------------------------------------------------------------------------- # +# create symlink # +# ---------------------------------------------------------------------------- # +# if isfile("/app/run.jl") +# symlink("/app/$programName/run.jl", "/app/run.jl") +# elseif isfile("/app/$programName/main.py") +# symlink("/app/$programName/main.py", "/app/main.py") +# else +# cd("/app") +# write("no run.jl in package folder.txt", "") +# end + + +# ------------------------------------------------------------------------------------------------ # +# for web app # +# ------------------------------------------------------------------------------------------------ # +# intendedURL = "wine.yiem.cc/hq/agent/sommelier/frontend/dbadmin" # e.g. "wine.yiem.cc/hq/agent/sommelier/frontend/dbadmin" +# urlList = split(intendedURL, "/")[2:end] # e.g. ["hq", "agent", "sommelier", "frontend", "dbadmin"] +# # use joinpath to compose the path from urlList +# _path = joinpath(urlList...) +# path = "./$_path" +# mkpath(path) +# # move all files in the current folder to the path +# copy_folder_contents(joinpath(appfolder, "temp"), path) + + +println("--> Julia env preparation done") \ No newline at end of file diff --git a/etc.jl b/etc.jl new file mode 100755 index 0000000..6c79e89 --- /dev/null +++ b/etc.jl @@ -0,0 +1,172 @@ + +using JSON, Dates, UUIDs, PrettyPrinting, Base64, DataFrames, NATS, HTTP, Base.Threads +using GeneralUtils, msghandler + +# ---------------------------------------------- 100 --------------------------------------------- # +const config = JSON.parsefile("./appconfig.json") +println("Config loaded") + +# ----------------------------------------- healthserver ----------------------------------------- # +function healthcheck(req::HTTP.Request) + return HTTP.Response(200, JSON.json(Dict("status"=>"ok"))) +end +const ROUTER = HTTP.Router() +HTTP.register!(ROUTER, "GET", "/ready", healthcheck) +HTTP.register!(ROUTER, "GET", "/health", healthcheck) +HTTP.register!(ROUTER, "GET", "/", healthcheck) +healthserver = HTTP.serve!(ROUTER, "0.0.0.0", 3000) + + +# ------------------------------------------------------------------------------------------------ # +# registration helper function # +# ------------------------------------------------------------------------------------------------ # +""" + register_service(connection, service_name, topic, config) + +Register a service with the load balancer via NATS request. + +# Arguments +- `connection`: NATS connection object +- `service_name`: Name of the service being registered +- `topic`: NATS topic/channel of the registering service +- `config`: Configuration dictionary containing service URLs + +# Returns +- Response from the load balancer NATS request + +Registers the service with the servicesloadbalancer topic using msghandler.smartpack +to create the registration message envelope. +""" +function register_service(connection, service_name, topic, config) + register_dict = Dict( + "register_service_name" => service_name, + "topic" => topic, + "free_or_paid" => "free" + ) + register_payloads = [("register", register_dict, "dictionary")] + _, register_msg_envelope_json_str = msghandler.smartpack( + config["externalService"]["servicesloadbalancer"]["url"], + register_payloads; + msg_purpose="register", + broker_url=config["nats_server_info"]["url"], + fileserver_url=config["externalService"]["fileserver"]["url"]) + try + reply = NATS.request(connection, + config["externalService"]["servicesloadbalancer"]["url"], + register_msg_envelope_json_str, + timeout=60) + println("service $service_name is registered") + catch + println("registering service $service_name failed") + end +end + +ch = Channel(8) +flag = Channel(1) +# Initialize NATS client +connection = NATS.connect(config["nats_server_info"]["url"]) + +# use reply() here so that when this_service scale to >1 instance, +# NATS will randomly select only one receiving instance +sub1 = NATS.subscribe(connection, + config["this_service_input_channel"]["nats"][1]; + queue_group="group1", spawn=true) do msg + put!(ch, msg) + end + +# ------------------------------------------------------------------------------------------------ # +# this service main loop # +# ------------------------------------------------------------------------------------------------ # +Threads.@spawn begin # Register service periodically + while true + if isready(flag) + register_service(connection, + config["this_service_name"], + config["this_service_input_channel"]["nats"][1], + config) + end + sleep(60) # this line should make spawned thread sleep while allowing main thread running + end +end + +function main() + +# ---------------------------------- register service initially ---------------------------------- # + register_service(connection, + config["this_service_name"], + config["this_service_input_channel"]["nats"][1], + config) +# ---------------------------------------------- 100 --------------------------------------------- # + println("NATS to HTTP forwarder service for llamacpp_chat_completion service is ready to accept NATS messages at broker=$(config["nats_server_info"]["url"]), topic=$(config["this_service_input_channel"]["nats"][1])") + while true + put!(flag, "ready") # ready for new request. do not send register message during llm working + incoming_nats_msg = take!(ch) # keep this service running. + println("processing new request") + + # start working, the work may take more than a few minutes. pause async loop registeration to + # prevent a loadbalancer assigning new work to this service. + _ = take!(flag) + + incoming_envelope_json_str = String(incoming_nats_msg.payload) + incoming_envelope = msghandler.smartunpack(incoming_envelope_json_str) + if incoming_envelope["msg_purpose"] == "ping" + outgoing_envelope, outgoing_envelope_json_str = + msghandler.smartpack("requester inbox", + [("response", "available", "text")]; + broker_url=config["nats_server_info"]["url"], + fileserver_url=config["externalService"]["fileserver"]["url"]) + NATS.publish(connection, incoming_nats_msg.reply_to, outgoing_envelope_json_str) + else + incoming_text_array = incoming_envelope["payloads"][1][2] + body_data = Dict( + "model" => "embeddinggemma-300M-BF16", + "input" => incoming_text_array + ) + headers = [ + "Content-Type" => "application/json", + "Authorization" => "Bearer no-key" + ] + http_response = HTTP.post(config["externalService"]["llamacpp_chat_completion"]["url"], + headers, JSON.json(body_data)) + + # HTTP.jl doesn't immediately close connections after post() - it pools them for reuse. + # Without explicit cleanup, connections accumulate and the llama.cpp server times out + # on subsequent requests. This forces connection pool cleanup between requests. + HTTP.close_idle_connections!() + http_response_body = JSON.parse(http_response.body) + outgoing_envelope, outgoing_envelope_json_str = + msghandler.smartpack("requester inbox", + [("http_response", http_response_body, "dictionary")]; + broker_url=config["nats_server_info"]["url"], + fileserver_url=config["externalService"]["fileserver"]["url"]) + # re-register this service to let a loadbalancer knows that this service is ready for next request + register_service(connection, + config["this_service_name"], + config["this_service_input_channel"]["nats"][1], + config) + NATS.publish(connection, incoming_envelope.reply_to, outgoing_envelope_json_str) + end + end +end + +main() + + + + + + + + + + + + + + + + + + + + diff --git a/main.jl b/main.jl new file mode 100755 index 0000000..85b4564 --- /dev/null +++ b/main.jl @@ -0,0 +1,226 @@ + +using JSON, Dates, UUIDs, PrettyPrinting, Base64, DataFrames, NATS, HTTP, Base.Threads +using GeneralUtils, msghandler + +# ---------------------------------------------- 100 --------------------------------------------- # +const config = JSON.parsefile("./appconfig.json") + +# Initialize NATS client +nats_conn = NATS.connect(config["nats_server_info"]["url"]) + +es = GeneralUtils.eventSink(natsConn=nats_conn, + topic=config["thisServiceOutputChannel"]["status"], + service_name=config["thisServiceName"]) + +function _log(msg::String) + es(msg) +end + +# -------------------------------------------- storage --------------------------------------------- # +const storage1 = GeneralUtils.GarageStorage( + config["externalService"]["objectStorage"]["endpoint"], + config["externalService"]["objectStorage"]["access_key_id"], + config["externalService"]["objectStorage"]["secret_access_key"], + config["externalService"]["objectStorage"]["bucket"] +) + +function fileserverUploadHandler(_::String, dataname::String, data::Vector{UInt8})::Dict{String, Any} + _, _ = GeneralUtils.s3PutFile(storage1, dataname, data) + return Dict("status" => 200, "uploadid" => "", "fileid" => "", "url" => dataname) +end + +function fileserverDownloadHandler(url::String, max_retries::Int, base_delay_ms::Int, + max_delay_ms::Int, correlation_id::String)::Union{Vector{UInt8}, Nothing} + delay = base_delay_ms + for attempt in 1:max_retries + data = GeneralUtils.s3GetFile(storage1, url) + if data !== nothing + GeneralUtils.s3DeleteFile(storage1, url) + return data + else + @info "correlation_id: $correlation_id, Attempt $attempt" + if attempt < max_retries + sleep(delay / 1000.0) + delay = min(delay * 2, max_delay_ms) + end + end + end + return nothing +end + +# ----------------------------------------- healthserver ----------------------------------------- # +# function healthcheck(req::HTTP.Request) +# return HTTP.Response(200, JSON.json(Dict("status"=>"ok"))) +# end +# const ROUTER = HTTP.Router() +# HTTP.register!(ROUTER, "GET", "/ready", healthcheck) +# HTTP.register!(ROUTER, "GET", "/health", healthcheck) +# HTTP.register!(ROUTER, "GET", "/", healthcheck) +# healthserver = HTTP.serve!(ROUTER, "0.0.0.0", 3000) + + +# ------------------------------------------------------------------------------------------------ # +# registration helper function # +# ------------------------------------------------------------------------------------------------ # +""" + register_service(nats_conn, service_name, topic, config) + +Register a service with the load balancer via NATS request. + +# Arguments +- `nats_conn`: NATS connection object +- `service_name`: Name of the service being registered +- `topic`: NATS topic/channel of the registering service +- `config`: Configuration dictionary containing service URLs + +# Returns +- Response from the load balancer NATS request + +Registers the service with the servicesloadbalancer topic using msghandler.smartpack +to create the registration message envelope. +""" +function register_service(nats_conn, service_name, topic, config) + register_dict = Dict( + "register_service_name" => service_name, + "topic" => topic, + "free_or_paid" => "free" + ) + register_payloads = [("register", register_dict, "dictionary")] + _, register_msg_envelope_json_str = msghandler.smartpack( + config["externalService"]["servicesloadbalancer"]["url"], + register_payloads; + msg_purpose="register", + broker_url=config["nats_server_info"]["url"]) + try + reply = NATS.request(nats_conn, + config["externalService"]["servicesloadbalancer"]["url"], + register_msg_envelope_json_str, + timeout=60) + _log("service $service_name is registered") + catch e + _log("registering service $service_name failed: $(sprint(showerror, e))") + end +end + +ch = Channel(1) +flag = Channel(1) + +# use reply() here so that when this_service scale to >1 instance, +# NATS will randomly select only one receiving instance +sub1 = NATS.subscribe(nats_conn, + config["thisServiceInputChannel"]["inbox"]; + queue_group="group1", spawn=true) do msg + put!(ch, msg) + end + +_log("service started") + +# ------------------------------------------------------------------------------------------------ # +# this service main loop # +# ------------------------------------------------------------------------------------------------ # +Threads.@spawn begin # Register service periodically + while true + if isready(flag) + register_service(nats_conn, + config["thisServiceName"], + config["thisServiceInputChannel"]["inbox"], + config) + # _log("periodic registration sent") + end + sleep(60) # this line should make spawned thread sleep while allowing main thread running + end +end + +function main() + +# ---------------------------------- register service initially ---------------------------------- # + register_service(nats_conn, + config["thisServiceName"], + config["thisServiceInputChannel"]["inbox"], + config) +# ---------------------------------------------- 100 --------------------------------------------- # + _log("ready to accept NATS messages at broker=$(config["nats_server_info"]["url"]), topic=$(config["thisServiceInputChannel"]["inbox"])") + while true + try + put!(flag, "ready") # ready for new request. do not send register message during llm working + incoming_nats_msg = take!(ch) # keep this service running. + + # start working, the work may take more than a few minutes. pause async loop registeration to + # prevent a loadbalancer assigning new work to this service. + _ = take!(flag) + + incoming_envelope_json_str = String(incoming_nats_msg.payload) + incoming_envelope = msghandler.smartunpack( + incoming_envelope_json_str; + fileserver_download_handler=fileserverDownloadHandler) + if incoming_envelope["msg_purpose"] == "ping" + _log("ping response") + outgoing_envelope, outgoing_envelope_json_str = + msghandler.smartpack("requester inbox", + [("response", "available", "text")]; + broker_url=config["nats_server_info"]["url"], + fileserver_upload_handler=fileserverUploadHandler) + NATS.publish(nats_conn, incoming_nats_msg.reply_to, outgoing_envelope_json_str) + else + _log("processing new request, msg_purpose=$(incoming_envelope["msg_purpose"])") + dataname1 = incoming_envelope["payloads"][1][1] + documents_dict = incoming_envelope["payloads"][1][2] + documents = documents_dict["documents"] + _log("received $(length(documents)) documents") + body_data = Dict( + "model" => "", + "input" => documents + ) + headers = [ + "Content-Type" => "application/json", + "Authorization" => "Bearer no-key" + ] + http_response = HTTP.post(config["externalService"]["llamacpp_embedding"]["url"], + headers, JSON.json(body_data)) + _log("HTTP response status=$(http_response.status)") + + # HTTP.jl doesn't immediately close connections after post() - it pools them for reuse. + # Without explicit cleanup, connections accumulate and the llama.cpp server times out + # on subsequent requests. This forces connection pool cleanup between requests. + HTTP.close_idle_connections!() + http_response_body = JSON.parse(http_response.body) + dataname2 = "transferjob/$(UUIDs.uuid4())" + outgoing_envelope, outgoing_envelope_json_str = + msghandler.smartpack("requester inbox", + [(dataname2, http_response_body, "dictionary")]; + msg_purpose=incoming_envelope["msg_purpose"], + sender_id=incoming_envelope["sender_id"], + broker_url=config["nats_server_info"]["url"], + fileserver_upload_handler=fileserverUploadHandler) + # re-register this service to let a loadbalancer knows that this service is ready for next request + register_service(nats_conn, + config["thisServiceName"], + config["thisServiceInputChannel"]["inbox"], + config) + _log("re-registered for next request") + reply_topic = + if incoming_nats_msg.reply_to !== nothing + incoming_nats_msg.reply_to + elseif incoming_envelope["reply_to"] != "" + incoming_envelope["reply_to"] + else + _log("no reply_to found, using fallback topic") + "sommpanion.unknown_subject" + end + NATS.publish(nats_conn, reply_topic, outgoing_envelope_json_str) + _log("reply published to $reply_topic, dataname2=$dataname2") + end + catch e + bt = catch_backtrace() + err_msg = sprint() do io + showerror(io, e, bt) + println(io) + end + + _log(err_msg) + # println(err_msg) + end + end +end + +main() diff --git a/my_AI_prompt.md b/my_AI_prompt.md new file mode 100755 index 0000000..e69de29 diff --git a/required_python_packages.txt b/required_python_packages.txt new file mode 100755 index 0000000..39d3dfa --- /dev/null +++ b/required_python_packages.txt @@ -0,0 +1,2 @@ +# Base ---------------------------------------- +python-dev-tools \ No newline at end of file diff --git a/test/large_image.png b/test/large_image.png new file mode 100755 index 0000000..2a89fd8 Binary files /dev/null and b/test/large_image.png differ diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100755 index 0000000..e375a6c --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,243 @@ +# ---------------------------------------------- 100 --------------------------------------------- # + + +using JSON, Dates, UUIDs, PrettyPrinting, Base64, DataFrames, NATS, HTTP +using GeneralUtils, msghandler + + + +# ------------------------------------------------------------------------------------------------ # +# llama.cpp chat request example (direct HTTP) # +# ------------------------------------------------------------------------------------------------ # + +# 1. Read local file and encode to base64 string +image_path = "test/large_image.png" +image_bytes = read(image_path) +base64_string = base64encode(image_bytes) + +# 2. Match the MIME type according to your file extension (e.g., png, jpeg) +mime_type = "image/png" +data_uri = "data:$(mime_type);base64,$(base64_string)" + + +headers = [ + "Content-Type" => "application/json", + "Authorization" => "Bearer no-key" +] + +# 3. Construct payload with the Data URI +messages = Dict( + "model" => "gemma-4-E4B-it-UD-Q4_K_XL", + "messages" => [ + Dict( + "role" => "user", + "content" => [ + Dict("type" => "text", "text" => "Describe this image for me."), + Dict( + "type" => "image_url", + "image_url" => Dict("url" => data_uri) + ) + ] + ) + ], + "temperature" => 0.7 +) +url = "http://192.168.1.8:9001/v1/chat/completions" +response = HTTP.post(url, headers, JSON.json(messages)) +r = JSON.parse(response.body) + + + + + + + + + + +# ------------------------------------------------------------------------------------------------ # +# Llama.cpp Embedding multiple text example (direct HTTP) # +# ------------------------------------------------------------------------------------------------ # +using HTTP +using JSON + +url = "http://192.168.88.50:9010/v1/embeddings" +headers = [ + "Content-Type" => "application/json", + "Authorization" => "Bearer no-key" +] + +# Pass an array of strings instead of a single string +documents = [ + "System architecture specification for heart monitor logic.", + "NATS broker cluster configuration options for low-latency.", + "Julia signal processing worker initialization sequence." +] + +body_data = Dict( + "model" => "embeddinggemma-300M-BF16", + "input" => documents +) + +try + response = HTTP.post(url, headers, JSON.json(body_data)) + parsed = JSON.parse(String(response.body)) + + # Extract multiple vectors (ordered identically to your input array) + for (i, item) in enumerate(parsed["data"]) + idx = item["index"] + 1 # API indexes are 0-based; Julia is 1-based + vector = item["embedding"] + println("Document $idx unified vector size: ", length(vector)) + end + +catch e + println("Batch embedding failed: ", e) +end + + +# ------------------------------------------------------------------------------------------------ # +# test openai_message (NATS) # +# ------------------------------------------------------------------------------------------------ # +# ------------------------------------------------------------------------------------------------ # +# Llama.cpp Embedding multiple text with NATS # +# ------------------------------------------------------------------------------------------------ # +using JSON, Dates, UUIDs, PrettyPrinting, Base64, DataFrames, NATS, HTTP +using GeneralUtils, msghandler + +const config = JSON.parsefile("./appconfig.json") + +documents = [ + "System architecture specification for heart monitor logic.", + # "NATS broker cluster configuration options for low-latency.", + # "Julia signal processing worker initialization sequence." +] + +documents_dict = Dict("documents"=> documents) + +payloads = [("documents", documents_dict, "dictionary")] +message_envelope, forward_msg_envelope_json_str = msghandler.smartpack( + config["this_service_input_channel"]["nats"], + payloads; + msg_purpose="embedding", + broker_url=config["nats_server_info"]["url"], + fileserver_url=config["externalService"]["fileserver"]["url"]) + +connection = NATS.connect(config["nats_server_info"]["url"]) +reply = NATS.request(connection, + config["this_service_input_channel"]["nats"], + forward_msg_envelope_json_str, timeout=120) + +incoming_env_json_str = String(reply.payload) +incoming_env = msghandler.smartunpack(incoming_env_json_str) +embedding_response = incoming_env["payloads"][1][2] + +for (i, item) in enumerate(embedding_response["data"]) + vector = item["embedding"] + println("Document $i embedding vector size: ", length(vector)) +end + + +# ------------------------------------------------------------------------------------------------ # +# test openai_message (NATS) # +# ------------------------------------------------------------------------------------------------ # +using JSON, Dates, UUIDs, PrettyPrinting, Base64, DataFrames, NATS, HTTP +using GeneralUtils, msghandler + +const config = JSON.parsefile("./appconfig.json") + +# 1. Read local file and encode to base64 string +image_path = "test/large_image.png" +image_bytes = read(image_path) +base64_string = base64encode(image_bytes) + +# 2. Match the MIME type according to your file extension (e.g., png, jpeg) +mime_type = "image/png" +data_uri = "data:$(mime_type);base64,$(base64_string)" + + +headers = [ + "Content-Type" => "application/json", + "Authorization" => "Bearer no-key" +] + +# 3. Construct payload with the Data URI +openai_msg = Dict( + "model" => "gemma-4-E4B-it-UD-Q4_K_XL", + "messages" => [ + Dict( + "role" => "user", + "content" => [ + Dict("type" => "text", "text" => "Describe this image for me in detail."), + Dict( + "type" => "image_url", + "image_url" => Dict("url" => data_uri) + ) + ] + ) + ], + "temperature" => 0.7 +) + +payloads = [("msg", openai_msg, "dictionary")] # List of tuples +message_envelope, forward_msg_envelope_json_str = msghandler.smartpack( + config["this_service_input_channel"]["nats"], + payloads; + msg_purpose="text2text", + broker_url=config["nats_server_info"]["url"], + fileserver_url=config["externalService"]["fileserver"]["url"]) + + +connection = NATS.connect(config["nats_server_info"]["url"]) +reply = NATS.request(connection, + config["this_service_input_channel"]["nats"], + forward_msg_envelope_json_str, timeout=120) + +incoming_env_json_str = String(reply.payload) +incoming_env = msghandler.smartunpack(incoming_env_json_str) +ai_response = incoming_env["payloads"][1][2] +content = ai_response["choices"][1]["message"]["content"] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +