Artem Kastrov hai 2 meses
achega
8590338b77
Modificáronse 16 ficheiros con 820 adicións e 0 borrados
  1. 12 0
      .docker/ollama/Dockerfile
  2. 33 0
      .docker/ollama/entrypoint.sh
  3. 11 0
      .docker/python/Dockerfile
  4. 17 0
      .env.example
  5. 16 0
      .gitignore
  6. 15 0
      Pipfile
  7. 303 0
      Pipfile.lock
  8. 45 0
      README.md
  9. 96 0
      Taskfile.yml
  10. 10 0
      config.py
  11. 2 0
      datasets/.gitignore
  12. 20 0
      devbox.json
  13. 130 0
      devbox.lock
  14. 26 0
      docker-compose.traefik.yml
  15. 33 0
      docker-compose.yml
  16. 51 0
      src/app.py

+ 12 - 0
.docker/ollama/Dockerfile

@@ -0,0 +1,12 @@
+FROM debian:bookworm-slim
+
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends ca-certificates curl unzip bash && \
+    rm -rf /var/lib/apt/lists/*
+
+RUN curl -fsSL https://ollama.com/install.sh | sh
+
+COPY entrypoint.sh /tmp/entrypoint.sh
+RUN chmod +x /tmp/entrypoint.sh
+
+ENTRYPOINT ["/tmp/entrypoint.sh"]

+ 33 - 0
.docker/ollama/entrypoint.sh

@@ -0,0 +1,33 @@
+#!/bin/bash
+set -e
+
+PORT=${OLLAMA_PORT:-11434}
+MODEL=${OLLAMA_MODEL:null}
+
+export OLLAMA_HOST="0.0.0.0:$PORT"
+ollama serve &
+SERVER_PID=$!
+
+echo "Waiting for Ollama server to be healthy..."
+until ollama ps >/dev/null 2>&1; do
+    sleep 1
+done
+
+echo "Server is up. Starting model downloads..."
+
+MODELS=${MODELS:-qwen3:8b}
+for model in $(echo $MODELS | tr ',' ' '); do
+    if ! ollama list | grep -q "$model"; then
+        echo "Pulling model $model..."
+        ollama pull "$model"
+    else
+        echo "Model $model already exists."
+    fi
+done
+
+if [ "$MODEL" != "null" ]; then
+    echo "Starting model $MODEL..."
+    ollama run "$MODEL"
+fi
+
+wait $SERVER_PID

+ 11 - 0
.docker/python/Dockerfile

@@ -0,0 +1,11 @@
+FROM python:3.11-slim
+
+WORKDIR /app
+
+COPY Pipfile Pipfile.lock ./
+
+RUN apt-get update && apt-get install -y curl \
+    && pip install pipenv \
+    && pipenv install --system --deploy --ignore-pipfile
+
+CMD ["python", "app.py"]

+ 17 - 0
.env.example

@@ -0,0 +1,17 @@
+####################################################### DOCKER #######################################################
+COMPOSE_FILE=docker-compose.yml
+COMPOSE_PROJECT_NAME=rag-inference
+
+####################################################### PROJECT #######################################################
+
+# Ollama
+OLLAMA_HOST=ollama
+OLLAMA_PORT=11434
+OLLAMA_URL=http://${OLLAMA_HOST}:${OLLAMA_PORT}
+OLLAMA_MODEL=qwen3:8b
+OLLAMA_MODELS=qwen3:8b
+OLLAMA_MAX_LOADED_MODELS=1
+
+# Chroma
+CHROMA_HOST=chroma
+CHROMA_PORT=8000

+ 16 - 0
.gitignore

@@ -0,0 +1,16 @@
+.env
+.env.backup
+.env.production
+/.fleet
+/.idea
+/.task
+/.vscode
+/.venv
+/.git
+.DS_Store
+.idea
+.devbox
+.project
+.settings
+Thumbs.db
+**/__pycache__

+ 15 - 0
Pipfile

@@ -0,0 +1,15 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+fastapi = "*"
+uvicorn = "*"
+ollama = "*"
+load-dotenv = "*"
+
+[dev-packages]
+
+[requires]
+python_version = "3.11"

+ 303 - 0
Pipfile.lock

@@ -0,0 +1,303 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "dd1390ada31bd59da03b6eb46e5e70f724a7729793190bfe14c98994a5d88a49"
+        },
+        "pipfile-spec": 6,
+        "requires": {
+            "python_version": "3.11"
+        },
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://pypi.org/simple",
+                "verify_ssl": true
+            }
+        ]
+    },
+    "default": {
+        "annotated-doc": {
+            "hashes": [
+                "sha256:348ec6664a76f1fd3be81f43dffbee4c7e8ce931ba71ec67cc7f4ade7fbbb580",
+                "sha256:e18370014c70187422c33e945053ff4c286f453a984eba84d0dbfa0c935adeda"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==0.0.3"
+        },
+        "annotated-types": {
+            "hashes": [
+                "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53",
+                "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==0.7.0"
+        },
+        "anyio": {
+            "hashes": [
+                "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc",
+                "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==4.11.0"
+        },
+        "certifi": {
+            "hashes": [
+                "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de",
+                "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==2025.10.5"
+        },
+        "click": {
+            "hashes": [
+                "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc",
+                "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"
+            ],
+            "markers": "python_version >= '3.10'",
+            "version": "==8.3.0"
+        },
+        "fastapi": {
+            "hashes": [
+                "sha256:2c5c7028bc3a58d8f5f09aecd3fd88a000ccc0c5ad627693264181a3c33aa1fc",
+                "sha256:b6dba0538fd15dab6fe4d3e5493c3957d8a9e1e9257f56446b5859af66f32441"
+            ],
+            "index": "pypi",
+            "markers": "python_version >= '3.8'",
+            "version": "==0.121.1"
+        },
+        "h11": {
+            "hashes": [
+                "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1",
+                "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==0.16.0"
+        },
+        "httpcore": {
+            "hashes": [
+                "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55",
+                "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==1.0.9"
+        },
+        "httpx": {
+            "hashes": [
+                "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc",
+                "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==0.28.1"
+        },
+        "idna": {
+            "hashes": [
+                "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea",
+                "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==3.11"
+        },
+        "load-dotenv": {
+            "hashes": [
+                "sha256:614803f720153fb8a5f97124a72aaa3930a67aa5547cbe8603d01ea7f9ac1adf",
+                "sha256:bbe5f40072d4a61eadca66de6c222df5a2d935d6d41b703be1ff75396f635145"
+            ],
+            "index": "pypi",
+            "version": "==0.1.0"
+        },
+        "ollama": {
+            "hashes": [
+                "sha256:534511b3ccea2dff419ae06c3b58d7f217c55be7897c8ce5868dfb6b219cf7a0",
+                "sha256:da2b2d846b5944cfbcee1ca1e6ee0585f6c9d45a2fe9467cbcd096a37383da2f"
+            ],
+            "index": "pypi",
+            "markers": "python_version >= '3.8'",
+            "version": "==0.6.0"
+        },
+        "pydantic": {
+            "hashes": [
+                "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac",
+                "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==2.12.4"
+        },
+        "pydantic-core": {
+            "hashes": [
+                "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90",
+                "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740",
+                "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504",
+                "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84",
+                "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33",
+                "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c",
+                "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0",
+                "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e",
+                "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0",
+                "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a",
+                "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34",
+                "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2",
+                "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3",
+                "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815",
+                "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14",
+                "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba",
+                "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375",
+                "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf",
+                "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963",
+                "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1",
+                "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808",
+                "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553",
+                "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1",
+                "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2",
+                "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5",
+                "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470",
+                "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2",
+                "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b",
+                "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660",
+                "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c",
+                "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093",
+                "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5",
+                "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594",
+                "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008",
+                "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a",
+                "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a",
+                "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd",
+                "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284",
+                "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586",
+                "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869",
+                "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294",
+                "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f",
+                "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66",
+                "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51",
+                "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc",
+                "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97",
+                "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a",
+                "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d",
+                "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9",
+                "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c",
+                "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07",
+                "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36",
+                "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e",
+                "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05",
+                "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e",
+                "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941",
+                "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3",
+                "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612",
+                "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3",
+                "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b",
+                "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe",
+                "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146",
+                "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11",
+                "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60",
+                "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd",
+                "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b",
+                "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c",
+                "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a",
+                "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460",
+                "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1",
+                "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf",
+                "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf",
+                "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858",
+                "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2",
+                "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9",
+                "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2",
+                "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3",
+                "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6",
+                "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770",
+                "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d",
+                "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc",
+                "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23",
+                "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26",
+                "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa",
+                "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8",
+                "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d",
+                "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3",
+                "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d",
+                "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034",
+                "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9",
+                "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1",
+                "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56",
+                "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b",
+                "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c",
+                "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a",
+                "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e",
+                "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9",
+                "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5",
+                "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a",
+                "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556",
+                "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e",
+                "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49",
+                "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2",
+                "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9",
+                "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b",
+                "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc",
+                "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb",
+                "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0",
+                "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8",
+                "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82",
+                "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69",
+                "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b",
+                "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c",
+                "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75",
+                "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5",
+                "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f",
+                "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad",
+                "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b",
+                "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7",
+                "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425",
+                "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==2.41.5"
+        },
+        "python-dotenv": {
+            "hashes": [
+                "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6",
+                "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==1.2.1"
+        },
+        "sniffio": {
+            "hashes": [
+                "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
+                "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==1.3.1"
+        },
+        "starlette": {
+            "hashes": [
+                "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284",
+                "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==0.49.3"
+        },
+        "typing-extensions": {
+            "hashes": [
+                "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466",
+                "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==4.15.0"
+        },
+        "typing-inspection": {
+            "hashes": [
+                "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7",
+                "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==0.4.2"
+        },
+        "uvicorn": {
+            "hashes": [
+                "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02",
+                "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d"
+            ],
+            "index": "pypi",
+            "markers": "python_version >= '3.9'",
+            "version": "==0.38.0"
+        }
+    },
+    "develop": {}
+}

+ 45 - 0
README.md

@@ -0,0 +1,45 @@
+# Описание
+Была реализована RAG система с историей сообщений, которая отвечает на вопросы по Terraria Wiki.
+- Dataset: parkourer10/terraria-wiki
+- Embeddings: paraphrase-multilingual-MiniLM-L12-v2
+
+## Результат
+Настроен мониторинг в Langfuse, протестированны некоторые запросы к модели, можно сравнить время выполнения операций. Мне больше понравилась Qwen3 по качеству ответа.
+llama3.1 не корректно переформулировала запрос к векторной базе данных, возможно требуется переписать промпт на этом этапе. Для качественного ответа процесс можно настраивать и настраивать
+
+P.S. Не имею GPU, ключа для OpenAI тоже нет, так что использовал Ollama для моделей. Поэтому время выполнения операция большое. Ollama не возвращает количество сгенерированных токенов, поэтому количество токенов и стоимость в метриках не учитывается
+
+P.P.S. Сначала RAG был реализован реализован на Chroma, а при настройки Langfuse, он требовал clickhouse. Одну из них, по иде, можно убрать
+
+
+# Стек
+- Python 3.11
+- Ollama
+- Сhroma - векторная БД для хранения Dataset с Terraria Wiki
+- Langchain
+- Langfuse - требует для работы Postgres, Minio, Clickhouse, Redis
+
+
+# Запуск
+#### Вариант 1. Dev Container 
+- Запустить Dev Container
+- Скопировать .env.example в .env (переменные уже настроены)
+- Установить pip install -r requirement.txt
+- Выполнить команду task up для запуска сервиса
+- Дождаться пока Ollama скачает модели (Модели указываются в env OLLAMA_MODELS и качаются автоматически, после запуска контейнера)
+- Запустить скрипт src/main.py
+- При желании, можно изменить список загружаемых моделей в env (необходим перезапуск контейнера Ollama)
+- При желании, можно изменить используемую модель (В самом файле main.py из списка OLLAMA_MODELS)
+- При желании, можно изменить список вопросов к модели (В файле main.py)
+
+
+#### Вариант 2. Ручной запуск
+- Скопировать .env.example в .env
+- Изменить значение переменных в .env OLLAMA_HOST, CHROMA_URL и LANGFUSE_HOST на `localhost`
+- Установить pip install -r requirement.txt
+- Выполнить команду docker compose up -d для запуска сервиса
+- Дождаться пока Ollama скачает модели (Модели указываются в env OLLAMA_MODELS и качаются автоматически, после запуска контейнера)
+- Запустить скрипт src/main.py
+- При желании, можно изменить список загружаемых моделей в env (необходим перезапуск контейнера Ollama)
+- При желании, можно изменить используемую модель (В самом файле main.py из списка OLLAMA_MODELS)
+- При желании, можно изменить список вопросов к модели (В файле main.py)

+ 96 - 0
Taskfile.yml

@@ -0,0 +1,96 @@
+version: "3"
+
+dotenv: [ '.env' ]
+
+tasks:
+  docker-compose:
+    internal: true
+    cmd: docker compose {{.COMPOSE}}
+
+  init:
+    desc: Первый запуск
+    cmds:
+      - task: pull
+      - task: up
+
+  docker-up:
+    desc: Запустить контейнеры
+    aliases: [ up ]
+    cmds:
+      - task: docker-compose
+        vars:
+          COMPOSE: up -d
+      - task: status
+
+  docker-pull:
+    desc: Загрузить образы
+    aliases: [ pull ]
+    cmds:
+      - task: docker-compose
+        vars:
+          COMPOSE: pull
+
+  docker-build:
+    desc: Build образов
+    aliases: [ build ]
+    cmds:
+      - task: docker-compose
+        vars:
+          COMPOSE: build
+
+  docker-down:
+    desc: Остановить контейнеры
+    aliases: [ down ]
+    cmds:
+      - task: docker-compose
+        vars:
+          COMPOSE: down
+
+  docker-restart:
+    desc: Перезапустить все контейнеры
+    aliases: [ restart ]
+    cmds:
+      - task: down
+      - task: up
+
+  docker-pause:
+    desc: Пауза контейнеров
+    aliases: [ pause ]
+    cmds:
+      - task: docker-compose
+        vars:
+          COMPOSE: pause
+
+  docker-unpause:
+    desc: Пуск контейнеров
+    aliases: [ unpause, play ]
+    cmds:
+      - task: docker-compose
+        vars:
+          COMPOSE: unpause
+
+  docker-ps:
+    desc: Список контейнеров
+    aliases: [ ps ]
+    cmds:
+      - task: docker-compose
+        vars:
+          COMPOSE: ps
+
+  docker-clean:
+    desc: Удалить volumes и кэш laravel
+    aliases: [ clean ]
+    cmds:
+      - task: optimize-clear
+      - cmd: docker compose down -v --remove-orphans
+
+  status:
+    desc: Статус приложения
+    cmds:
+      - echo -e "\033[1;36m========================================\033[0m"
+      - echo -e "\033[1;32m✔ Application is ready!\033[0m"
+      - echo -e "\033[1;36m========================================\033[0m"
+      # - echo ""
+      - echo -e "\033[1;33mLangfuse:\033[0m          http://localhost:3000"
+      - echo -e "\033[1;36m========================================\033[0m"
+    silent: true

+ 10 - 0
config.py

@@ -0,0 +1,10 @@
+from dotenv import load_dotenv
+import os
+
+load_dotenv()
+
+# CHROMA_HOST = os.getenv("CHROMA_HOST")
+# CHROMA_PORT = os.getenv("CHROMA_PORT")
+
+OLLAMA_URL = os.getenv("OLLAMA_URL")
+OLLAMA_MODEL = os.getenv("OLLAMA_MODEL")

+ 2 - 0
datasets/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 20 - 0
devbox.json

@@ -0,0 +1,20 @@
+{
+  "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.16.0/.schema/devbox.schema.json",
+  "packages": [
+    "python@3.11",
+    "pipenv@latest"
+  ],
+  "shell": {
+    "init_hook": [
+      "pipenv install --dev",
+      "pipenv shell"
+    ],
+    "scripts": {
+      "serve": [
+        "source .venv/bin/activate",
+        "uvicorn src.app:app --reload --host 0.0.0.0 --port 8000"
+      ]
+    }
+  },
+  "env": {}
+}

+ 130 - 0
devbox.lock

@@ -0,0 +1,130 @@
+{
+  "lockfile_version": "1",
+  "packages": {
+    "github:NixOS/nixpkgs/nixpkgs-unstable": {
+      "last_modified": "2025-11-07T02:32:13Z",
+      "resolved": "github:NixOS/nixpkgs/e1ebeec86b771e9d387dd02d82ffdc77ac753abc?lastModified=1762482733&narHash=sha256-g%2Fda4FzvckvbiZT075Sb1%2FYDNDr%2BtGQgh4N8i5ceYMg%3D"
+    },
+    "pipenv@latest": {
+      "last_modified": "2025-10-22T20:59:19Z",
+      "resolved": "github:NixOS/nixpkgs/01b6809f7f9d1183a2b3e081f0a1e6f8f415cb09#pipenv",
+      "source": "devbox-search",
+      "version": "2025.0.4",
+      "systems": {
+        "aarch64-darwin": {
+          "outputs": [
+            {
+              "name": "out",
+              "path": "/nix/store/xdqhyl85hz5qscal3wk6p5426rk9gzpk-pipenv-2025.0.4",
+              "default": true
+            },
+            {
+              "name": "dist",
+              "path": "/nix/store/v41pjscwfqlkqxgg7wbc0796d8kn16lw-pipenv-2025.0.4-dist"
+            }
+          ],
+          "store_path": "/nix/store/xdqhyl85hz5qscal3wk6p5426rk9gzpk-pipenv-2025.0.4"
+        },
+        "aarch64-linux": {
+          "outputs": [
+            {
+              "name": "out",
+              "path": "/nix/store/laj18m8gk47d9wqicnqhpx8g09bj59gb-pipenv-2025.0.4",
+              "default": true
+            },
+            {
+              "name": "dist",
+              "path": "/nix/store/jx5q3qqy9s31cxz07k0glwar32793i2k-pipenv-2025.0.4-dist"
+            }
+          ],
+          "store_path": "/nix/store/laj18m8gk47d9wqicnqhpx8g09bj59gb-pipenv-2025.0.4"
+        },
+        "x86_64-darwin": {
+          "outputs": [
+            {
+              "name": "out",
+              "path": "/nix/store/35qai7pxm4lxz5ayaawq1pnqadz6r807-pipenv-2025.0.4",
+              "default": true
+            },
+            {
+              "name": "dist",
+              "path": "/nix/store/db7p55kzkjck246ckgr6gxg1na9vv2s2-pipenv-2025.0.4-dist"
+            }
+          ],
+          "store_path": "/nix/store/35qai7pxm4lxz5ayaawq1pnqadz6r807-pipenv-2025.0.4"
+        },
+        "x86_64-linux": {
+          "outputs": [
+            {
+              "name": "out",
+              "path": "/nix/store/kdw6i5qlh7j8gmd6cr9bp2a2pil6xw2n-pipenv-2025.0.4",
+              "default": true
+            },
+            {
+              "name": "dist",
+              "path": "/nix/store/g4iy73w0anmwv8dh9l9a8i8p1w93ir5s-pipenv-2025.0.4-dist"
+            }
+          ],
+          "store_path": "/nix/store/kdw6i5qlh7j8gmd6cr9bp2a2pil6xw2n-pipenv-2025.0.4"
+        }
+      }
+    },
+    "python@3.11": {
+      "last_modified": "2025-10-22T20:59:19Z",
+      "plugin_version": "0.0.4",
+      "resolved": "github:NixOS/nixpkgs/01b6809f7f9d1183a2b3e081f0a1e6f8f415cb09#python311",
+      "source": "devbox-search",
+      "version": "3.11.14",
+      "systems": {
+        "aarch64-darwin": {
+          "outputs": [
+            {
+              "name": "out",
+              "path": "/nix/store/0710xrjndhjvpssqif83gyaz9qkxlwi3-python3-3.11.14",
+              "default": true
+            }
+          ],
+          "store_path": "/nix/store/0710xrjndhjvpssqif83gyaz9qkxlwi3-python3-3.11.14"
+        },
+        "aarch64-linux": {
+          "outputs": [
+            {
+              "name": "out",
+              "path": "/nix/store/82404paf3vf9nmj8gdfwjnz26g32cfq9-python3-3.11.14",
+              "default": true
+            },
+            {
+              "name": "debug",
+              "path": "/nix/store/yrbnpgrbj34kp80rhhmf9rqy0prncvgc-python3-3.11.14-debug"
+            }
+          ],
+          "store_path": "/nix/store/82404paf3vf9nmj8gdfwjnz26g32cfq9-python3-3.11.14"
+        },
+        "x86_64-darwin": {
+          "outputs": [
+            {
+              "name": "out",
+              "path": "/nix/store/ccb93zakkfa6rm36pnd7jwzpddfq2f9h-python3-3.11.14",
+              "default": true
+            }
+          ],
+          "store_path": "/nix/store/ccb93zakkfa6rm36pnd7jwzpddfq2f9h-python3-3.11.14"
+        },
+        "x86_64-linux": {
+          "outputs": [
+            {
+              "name": "out",
+              "path": "/nix/store/sm8xkb2i0b6dbsll92iyfavy0b0cw0p6-python3-3.11.14",
+              "default": true
+            },
+            {
+              "name": "debug",
+              "path": "/nix/store/pqcz3x6ny4psxhw3p07b1l2pmaa2yz2v-python3-3.11.14-debug"
+            }
+          ],
+          "store_path": "/nix/store/sm8xkb2i0b6dbsll92iyfavy0b0cw0p6-python3-3.11.14"
+        }
+      }
+    }
+  }
+}

+ 26 - 0
docker-compose.traefik.yml

@@ -0,0 +1,26 @@
+services:
+  application:
+    command: uvicorn src.app:app --reload --host 0.0.0.0 --port 8000
+    labels:
+      - "traefik.enable=true"
+      - "traefik.http.routers.rag-http.entrypoints=web"
+      - "traefik.http.routers.rag-http.rule=Host(`rag.localhost`)"
+      - "traefik.http.routers.rag-http.service=rag"
+      - "traefik.http.services.rag.loadbalancer.server.port=8000"
+      - "traefik.docker.network=proxy"
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+      start_period: 15s
+    networks:
+      default:
+      proxy:
+        aliases:
+          - rag
+
+networks:
+  proxy:
+    name: proxy
+    external: true

+ 33 - 0
docker-compose.yml

@@ -0,0 +1,33 @@
+services:
+  application:
+    build:
+      context: .
+      dockerfile: .docker/python/Dockerfile
+    user: "${APP_UID:-1000}:${APP_GID:-1000}"
+    volumes:
+      - ./:/app:cached
+
+  ollama:
+    build:
+      context: .docker/ollama
+      dockerfile: Dockerfile
+    volumes:
+      - ollama-data:/root/.ollama
+    environment:
+      OLLAMA_MODELS: ${OLLAMA_MODELS}
+      OLLAMA_MODEL: ${OLLAMA_MODEL}
+    healthcheck:
+      test: ["CMD", "ollama", "ps"]
+      interval: 15s
+      retries: 5
+      start_period: 5s
+      timeout: 3s
+
+  chroma:
+    image: chromadb/chroma
+    volumes:
+      - chroma-data:/data
+
+volumes:
+  ollama-data: {}
+  chroma-data: {}

+ 51 - 0
src/app.py

@@ -0,0 +1,51 @@
+from fastapi import Body, FastAPI
+from ollama import ChatResponse, Client
+
+from config import OLLAMA_MODEL, OLLAMA_URL
+
+client = Client(host=f"{OLLAMA_URL}")
+# ollama.create(model='example', from_='gemma3', system="You are Mario from Super Mario Bros.") // TODO: Для асистента?
+
+
+def message(text: str) -> ChatResponse:
+    return chat(
+        [
+            {
+                "role": "system",
+                "content": "Отвечай строго в формате Markdown. Не нужно пихать везде большие заголовки! Пиши как обычный человек, но красиво оформляй ответ",
+            },
+            {"role": "user", "content": text},
+        ]
+    )
+
+
+def chat(messages: list) -> ChatResponse:
+    return client.chat(model=f"{OLLAMA_MODEL}", messages=messages, think=True)
+
+
+app = FastAPI()
+
+
+@app.post("/chat")
+def _(prompt: str = Body(..., embed=True)):
+    print(prompt)
+    response = message(prompt)
+
+    return response
+
+
+@app.post("/generate")
+def generate(prompt: str = Body(..., embed=True)):
+    response = client.generate(
+        model=f"{OLLAMA_MODEL}",
+        prompt=prompt,
+        think=True,
+        options={"temperature": 0.15},
+    )
+
+    return response
+
+
+@app.get("/health")
+def health():
+    return {"status": "ok"}