Spaces:
Sleeping
Sleeping
Deploy free local 4B+4B+8B 829d4932ef224f7b02587a37d9948c47459042ac KB kb-20260814T042741+0900
Browse files- Dockerfile +64 -3
- config/model.yaml +25 -11
- deployment-manifest.json +8 -2
- pyproject.toml +1 -1
- requirements.txt +1 -0
- runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- runtime/__pycache__/answer_quality.cpython-312.pyc +0 -0
- runtime/__pycache__/bootstrap.cpython-312.pyc +0 -0
- runtime/__pycache__/contracts.cpython-312.pyc +0 -0
- runtime/__pycache__/hf_client.cpython-312.pyc +0 -0
- runtime/__pycache__/integration.cpython-312.pyc +0 -0
- runtime/__pycache__/internal_core.cpython-312.pyc +0 -0
- runtime/__pycache__/japanese_skills.cpython-312.pyc +0 -0
- runtime/__pycache__/kagrra_bridge.cpython-312.pyc +0 -0
- runtime/__pycache__/kb_bucket.cpython-312.pyc +0 -0
- runtime/__pycache__/kb_harvest.cpython-312.pyc +0 -0
- runtime/__pycache__/kb_search.cpython-312.pyc +0 -0
- runtime/__pycache__/knowledge.cpython-312.pyc +0 -0
- runtime/__pycache__/live_state.cpython-312.pyc +0 -0
- runtime/__pycache__/model.cpython-312.pyc +0 -0
- runtime/__pycache__/observability.cpython-312.pyc +0 -0
- runtime/__pycache__/quality.cpython-312.pyc +0 -0
- runtime/__pycache__/roles.cpython-312.pyc +0 -0
- runtime/__pycache__/runtime_factory.cpython-312.pyc +0 -0
- runtime/__pycache__/schemas.cpython-312.pyc +0 -0
- runtime/__pycache__/search_planner.cpython-312.pyc +0 -0
- runtime/__pycache__/security.cpython-312.pyc +0 -0
- runtime/__pycache__/service.cpython-312.pyc +0 -0
- runtime/__pycache__/shared_head.cpython-312.pyc +0 -0
- runtime/__pycache__/skill_runtime.cpython-312.pyc +0 -0
- runtime/__pycache__/startup.cpython-312.pyc +0 -0
- runtime/__pycache__/state.cpython-312.pyc +0 -0
- runtime/__pycache__/task_decomposition.cpython-312.pyc +0 -0
- runtime/__pycache__/v8_bridge.cpython-312.pyc +0 -0
- runtime/__pycache__/writing_skills.cpython-312.pyc +0 -0
- runtime/bootstrap.py +15 -5
- runtime/hf_client.py +36 -15
- runtime/model.py +68 -11
- runtime/runtime_factory.py +19 -12
- runtime/startup.py +15 -9
- scripts/start_local_cpu.sh +76 -0
Dockerfile
CHANGED
|
@@ -1,19 +1,80 @@
|
|
| 1 |
-
FROM
|
|
|
|
|
|
|
| 2 |
|
| 3 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 4 |
PYTHONUNBUFFERED=1 \
|
| 5 |
-
PORT=7860
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
WORKDIR /app
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
COPY requirements.txt /app/requirements.txt
|
| 10 |
RUN python -m pip install --no-cache-dir --upgrade pip \
|
| 11 |
&& python -m pip install --no-cache-dir -r /app/requirements.txt
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
COPY app.py /app/app.py
|
| 14 |
COPY runtime /app/runtime
|
| 15 |
COPY config /app/config
|
|
|
|
|
|
|
| 16 |
|
| 17 |
EXPOSE 7860
|
| 18 |
|
| 19 |
-
CMD ["
|
|
|
|
| 1 |
+
FROM ghcr.io/ggml-org/llama.cpp:server AS llama_cpp
|
| 2 |
+
|
| 3 |
+
FROM python:3.12-slim
|
| 4 |
|
| 5 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 6 |
PYTHONUNBUFFERED=1 \
|
| 7 |
+
PORT=7860 \
|
| 8 |
+
LD_LIBRARY_PATH=/opt/llama \
|
| 9 |
+
CUSTOMER_AI_CONSTRUCTIVE_API_URL=http://127.0.0.1:8081/v1/chat/completions \
|
| 10 |
+
CUSTOMER_AI_ADVERSARIAL_API_URL=http://127.0.0.1:8082/v1/chat/completions \
|
| 11 |
+
CUSTOMER_AI_EVIDENCE_API_URL=http://127.0.0.1:8083/v1/chat/completions \
|
| 12 |
+
CUSTOMER_AI_LOCAL_4B_PATH=/models/llm-jp-3-3.7b-instruct3-Q4_K_M.gguf \
|
| 13 |
+
CUSTOMER_AI_LOCAL_8B_PATH=/models/llm-jp-4-8b-instruct-Q4_K_M.gguf
|
| 14 |
|
| 15 |
WORKDIR /app
|
| 16 |
|
| 17 |
+
RUN apt-get update \
|
| 18 |
+
&& apt-get install -y --no-install-recommends ca-certificates curl libgomp1 \
|
| 19 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
+
|
| 21 |
+
COPY --from=llama_cpp /app /opt/llama
|
| 22 |
+
|
| 23 |
COPY requirements.txt /app/requirements.txt
|
| 24 |
RUN python -m pip install --no-cache-dir --upgrade pip \
|
| 25 |
&& python -m pip install --no-cache-dir -r /app/requirements.txt
|
| 26 |
|
| 27 |
+
# Download immutable, public GGUF weights at build time. These are local model
|
| 28 |
+
# files, not Hugging Face Inference Provider calls, so runtime inference credits
|
| 29 |
+
# are never consumed.
|
| 30 |
+
RUN mkdir -p /models \
|
| 31 |
+
&& python - <<'PY'
|
| 32 |
+
from hashlib import sha256
|
| 33 |
+
from pathlib import Path
|
| 34 |
+
from huggingface_hub import hf_hub_download
|
| 35 |
+
|
| 36 |
+
targets = [
|
| 37 |
+
{
|
| 38 |
+
"repo": "mmnga/llm-jp-3-3.7b-instruct3-gguf",
|
| 39 |
+
"revision": "7edef5a4f094ec8c1aed1e196c6a544675efbc2f",
|
| 40 |
+
"filename": "llm-jp-3-3.7b-instruct3-Q4_K_M.gguf",
|
| 41 |
+
"sha256": "a4a09d2141717a01b44e7a8dbdb28da8c01e9078c8051367cd6a20f7008ef5a8",
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"repo": "mmnga-o/llm-jp-4-8b-instruct-gguf",
|
| 45 |
+
"revision": "7ae4da12cee2f109509cb8e1d01cf8a0f1a5fbc1",
|
| 46 |
+
"filename": "llm-jp-4-8b-instruct-Q4_K_M.gguf",
|
| 47 |
+
"sha256": "b6a61b9c8d4e7cb1ae543d8fcf472c9fb9abfc5d48af17f5017ce89c2dc0bd56",
|
| 48 |
+
},
|
| 49 |
+
]
|
| 50 |
+
|
| 51 |
+
for item in targets:
|
| 52 |
+
path = Path(
|
| 53 |
+
hf_hub_download(
|
| 54 |
+
repo_id=item["repo"],
|
| 55 |
+
revision=item["revision"],
|
| 56 |
+
filename=item["filename"],
|
| 57 |
+
local_dir="/models",
|
| 58 |
+
)
|
| 59 |
+
)
|
| 60 |
+
h = sha256()
|
| 61 |
+
with path.open("rb") as handle:
|
| 62 |
+
for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""):
|
| 63 |
+
h.update(chunk)
|
| 64 |
+
actual = h.hexdigest()
|
| 65 |
+
if actual != item["sha256"]:
|
| 66 |
+
raise SystemExit(
|
| 67 |
+
f"local_model_sha_mismatch file={item['filename']} expected={item['sha256']} actual={actual}"
|
| 68 |
+
)
|
| 69 |
+
print(f"LOCAL_MODEL_PINNED={path.name} SHA256={actual}")
|
| 70 |
+
PY
|
| 71 |
+
|
| 72 |
COPY app.py /app/app.py
|
| 73 |
COPY runtime /app/runtime
|
| 74 |
COPY config /app/config
|
| 75 |
+
COPY scripts/start_local_cpu.sh /app/scripts/start_local_cpu.sh
|
| 76 |
+
RUN chmod +x /app/scripts/start_local_cpu.sh
|
| 77 |
|
| 78 |
EXPOSE 7860
|
| 79 |
|
| 80 |
+
CMD ["/app/scripts/start_local_cpu.sh"]
|
config/model.yaml
CHANGED
|
@@ -1,19 +1,33 @@
|
|
| 1 |
model:
|
| 2 |
topology: one_work_three_role_models
|
| 3 |
constructive:
|
| 4 |
-
model_id:
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
adversarial:
|
| 8 |
-
model_id:
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
evidence_bound:
|
| 12 |
-
model_id:
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
require_trained_domain_model: true
|
| 18 |
allow_untrained_production_model: false
|
| 19 |
zero_gpu: false
|
|
|
|
| 1 |
model:
|
| 2 |
topology: one_work_three_role_models
|
| 3 |
constructive:
|
| 4 |
+
model_id: llm-jp/llm-jp-3-3.7b-instruct3
|
| 5 |
+
class: 4B
|
| 6 |
+
backend: local_llama_cpp
|
| 7 |
+
endpoint: http://127.0.0.1:8081/v1/chat/completions
|
| 8 |
+
gguf_repo: mmnga/llm-jp-3-3.7b-instruct3-gguf
|
| 9 |
+
revision: 7edef5a4f094ec8c1aed1e196c6a544675efbc2f
|
| 10 |
+
gguf_sha256: a4a09d2141717a01b44e7a8dbdb28da8c01e9078c8051367cd6a20f7008ef5a8
|
| 11 |
adversarial:
|
| 12 |
+
model_id: llm-jp/llm-jp-3-3.7b-instruct3
|
| 13 |
+
class: 4B
|
| 14 |
+
backend: local_llama_cpp
|
| 15 |
+
endpoint: http://127.0.0.1:8082/v1/chat/completions
|
| 16 |
+
gguf_repo: mmnga/llm-jp-3-3.7b-instruct3-gguf
|
| 17 |
+
revision: 7edef5a4f094ec8c1aed1e196c6a544675efbc2f
|
| 18 |
+
gguf_sha256: a4a09d2141717a01b44e7a8dbdb28da8c01e9078c8051367cd6a20f7008ef5a8
|
| 19 |
evidence_bound:
|
| 20 |
+
model_id: llm-jp/llm-jp-4-8b-instruct
|
| 21 |
+
class: 8B
|
| 22 |
+
backend: local_llama_cpp
|
| 23 |
+
endpoint: http://127.0.0.1:8083/v1/chat/completions
|
| 24 |
+
gguf_repo: mmnga-o/llm-jp-4-8b-instruct-gguf
|
| 25 |
+
revision: 7ae4da12cee2f109509cb8e1d01cf8a0f1a5fbc1
|
| 26 |
+
gguf_sha256: b6a61b9c8d4e7cb1ae543d8fcf472c9fb9abfc5d48af17f5017ce89c2dc0bd56
|
| 27 |
+
backend_mode: local_cpu_basic_no_inference_provider
|
| 28 |
+
role_adapter_mode: independent_role_processes
|
| 29 |
+
inference_provider_allowed: false
|
| 30 |
+
inference_provider_credit_burn_allowed: false
|
| 31 |
require_trained_domain_model: true
|
| 32 |
allow_untrained_production_model: false
|
| 33 |
zero_gpu: false
|
deployment-manifest.json
CHANGED
|
@@ -1,7 +1,13 @@
|
|
| 1 |
{
|
| 2 |
"schema_version": 5,
|
| 3 |
-
"
|
|
|
|
|
|
|
| 4 |
"kb_bucket_id": "G-ACE/astera-customerai-kb",
|
| 5 |
"kb_build_id": "kb-20260814T042741+0900",
|
| 6 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"schema_version": 5,
|
| 3 |
+
"source_sha": "829d4932ef224f7b02587a37d9948c47459042ac",
|
| 4 |
+
"generated_at": "2026-08-23T07:31:44.635267+00:00",
|
| 5 |
+
"kb_storage": "huggingface_private_bucket_remote_read",
|
| 6 |
"kb_bucket_id": "G-ACE/astera-customerai-kb",
|
| 7 |
"kb_build_id": "kb-20260814T042741+0900",
|
| 8 |
+
"kb_active_pointer": "active.json",
|
| 9 |
+
"kb_embedded_in_space": false,
|
| 10 |
+
"role_topology": "local_cpu_4b_4b_8b",
|
| 11 |
+
"inference_provider": "disabled",
|
| 12 |
+
"deployment_path": "direct_hf_hub_cpu_basic"
|
| 13 |
}
|
pyproject.toml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
name = "astera-customer-ai"
|
| 3 |
version = "0.0.0"
|
| 4 |
requires-python = ">=3.11"
|
| 5 |
-
dependencies = ["fastapi>=0.128,<1", "uvicorn>=0.48,<1", "pydantic>=2.12,<3", "httpx>=0.28,<1", "rapidfuzz==3.14.3", "pyyaml>=6,<7", "huggingface_hub>=1.24,<2"]
|
| 6 |
|
| 7 |
[project.optional-dependencies]
|
| 8 |
training = ["datasets", "transformers", "peft", "trl"]
|
|
|
|
| 2 |
name = "astera-customer-ai"
|
| 3 |
version = "0.0.0"
|
| 4 |
requires-python = ">=3.11"
|
| 5 |
+
dependencies = ["fastapi>=0.128,<1", "uvicorn>=0.48,<1", "pydantic>=2.12,<3", "httpx>=0.28,<1", "rapidfuzz==3.14.3", "pyyaml>=6,<7", "huggingface_hub>=1.24,<2", "hf-xet>=1.1,<2"]
|
| 6 |
|
| 7 |
[project.optional-dependencies]
|
| 8 |
training = ["datasets", "transformers", "peft", "trl"]
|
requirements.txt
CHANGED
|
@@ -5,3 +5,4 @@ httpx>=0.28,<1
|
|
| 5 |
rapidfuzz==3.14.3
|
| 6 |
pyyaml>=6,<7
|
| 7 |
huggingface_hub>=1.24,<2
|
|
|
|
|
|
| 5 |
rapidfuzz==3.14.3
|
| 6 |
pyyaml>=6,<7
|
| 7 |
huggingface_hub>=1.24,<2
|
| 8 |
+
hf-xet>=1.1,<2
|
runtime/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (361 Bytes). View file
|
|
|
runtime/__pycache__/answer_quality.cpython-312.pyc
ADDED
|
Binary file (6.72 kB). View file
|
|
|
runtime/__pycache__/bootstrap.cpython-312.pyc
ADDED
|
Binary file (3.37 kB). View file
|
|
|
runtime/__pycache__/contracts.cpython-312.pyc
ADDED
|
Binary file (4.79 kB). View file
|
|
|
runtime/__pycache__/hf_client.cpython-312.pyc
ADDED
|
Binary file (4.38 kB). View file
|
|
|
runtime/__pycache__/integration.cpython-312.pyc
ADDED
|
Binary file (4.1 kB). View file
|
|
|
runtime/__pycache__/internal_core.cpython-312.pyc
ADDED
|
Binary file (20.1 kB). View file
|
|
|
runtime/__pycache__/japanese_skills.cpython-312.pyc
ADDED
|
Binary file (12.1 kB). View file
|
|
|
runtime/__pycache__/kagrra_bridge.cpython-312.pyc
ADDED
|
Binary file (2.05 kB). View file
|
|
|
runtime/__pycache__/kb_bucket.cpython-312.pyc
ADDED
|
Binary file (10.6 kB). View file
|
|
|
runtime/__pycache__/kb_harvest.cpython-312.pyc
ADDED
|
Binary file (2.44 kB). View file
|
|
|
runtime/__pycache__/kb_search.cpython-312.pyc
ADDED
|
Binary file (23.6 kB). View file
|
|
|
runtime/__pycache__/knowledge.cpython-312.pyc
ADDED
|
Binary file (6.9 kB). View file
|
|
|
runtime/__pycache__/live_state.cpython-312.pyc
ADDED
|
Binary file (2.06 kB). View file
|
|
|
runtime/__pycache__/model.cpython-312.pyc
ADDED
|
Binary file (7.09 kB). View file
|
|
|
runtime/__pycache__/observability.cpython-312.pyc
ADDED
|
Binary file (2.77 kB). View file
|
|
|
runtime/__pycache__/quality.cpython-312.pyc
ADDED
|
Binary file (2.79 kB). View file
|
|
|
runtime/__pycache__/roles.cpython-312.pyc
ADDED
|
Binary file (1.21 kB). View file
|
|
|
runtime/__pycache__/runtime_factory.cpython-312.pyc
ADDED
|
Binary file (4.53 kB). View file
|
|
|
runtime/__pycache__/schemas.cpython-312.pyc
ADDED
|
Binary file (7.45 kB). View file
|
|
|
runtime/__pycache__/search_planner.cpython-312.pyc
ADDED
|
Binary file (3.79 kB). View file
|
|
|
runtime/__pycache__/security.cpython-312.pyc
ADDED
|
Binary file (3.34 kB). View file
|
|
|
runtime/__pycache__/service.cpython-312.pyc
ADDED
|
Binary file (1.23 kB). View file
|
|
|
runtime/__pycache__/shared_head.cpython-312.pyc
ADDED
|
Binary file (9.93 kB). View file
|
|
|
runtime/__pycache__/skill_runtime.cpython-312.pyc
ADDED
|
Binary file (4.01 kB). View file
|
|
|
runtime/__pycache__/startup.cpython-312.pyc
ADDED
|
Binary file (8.03 kB). View file
|
|
|
runtime/__pycache__/state.cpython-312.pyc
ADDED
|
Binary file (17.8 kB). View file
|
|
|
runtime/__pycache__/task_decomposition.cpython-312.pyc
ADDED
|
Binary file (9.84 kB). View file
|
|
|
runtime/__pycache__/v8_bridge.cpython-312.pyc
ADDED
|
Binary file (2.59 kB). View file
|
|
|
runtime/__pycache__/writing_skills.cpython-312.pyc
ADDED
|
Binary file (5.78 kB). View file
|
|
|
runtime/bootstrap.py
CHANGED
|
@@ -3,7 +3,13 @@ from __future__ import annotations
|
|
| 3 |
from dataclasses import dataclass
|
| 4 |
from typing import Iterable, Mapping
|
| 5 |
|
| 6 |
-
from .hf_client import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
from .japanese_skills import JapaneseShortQASkillPack
|
| 8 |
from .runtime_factory import InternalRuntimeDependencies, build_internal_core
|
| 9 |
from .service import CustomerAIWork
|
|
@@ -19,13 +25,15 @@ class RuntimeDependencies:
|
|
| 19 |
kb_generation_id: str | None = None
|
| 20 |
hf_token: str | None = None
|
| 21 |
role_pool: object | None = None
|
| 22 |
-
shared_head: object | None = None
|
| 23 |
max_targeted_retry: int = 1
|
| 24 |
constructive_model_id: str = HF_MODEL_4B
|
| 25 |
adversarial_model_id: str = HF_MODEL_4B
|
| 26 |
evidence_model_id: str = HF_MODEL_8B
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
| 29 |
|
| 30 |
|
| 31 |
def build_work(deps: RuntimeDependencies) -> CustomerAIWork:
|
|
@@ -51,7 +59,9 @@ def build_work(deps: RuntimeDependencies) -> CustomerAIWork:
|
|
| 51 |
constructive_model_id=deps.constructive_model_id,
|
| 52 |
adversarial_model_id=deps.adversarial_model_id,
|
| 53 |
evidence_model_id=deps.evidence_model_id,
|
| 54 |
-
|
|
|
|
|
|
|
| 55 |
timeout_seconds=deps.timeout_seconds,
|
| 56 |
)
|
| 57 |
)
|
|
|
|
| 3 |
from dataclasses import dataclass
|
| 4 |
from typing import Iterable, Mapping
|
| 5 |
|
| 6 |
+
from .hf_client import (
|
| 7 |
+
HF_CHAT_API_ADVERSARIAL,
|
| 8 |
+
HF_CHAT_API_CONSTRUCTIVE,
|
| 9 |
+
HF_CHAT_API_EVIDENCE,
|
| 10 |
+
HF_MODEL_4B,
|
| 11 |
+
HF_MODEL_8B,
|
| 12 |
+
)
|
| 13 |
from .japanese_skills import JapaneseShortQASkillPack
|
| 14 |
from .runtime_factory import InternalRuntimeDependencies, build_internal_core
|
| 15 |
from .service import CustomerAIWork
|
|
|
|
| 25 |
kb_generation_id: str | None = None
|
| 26 |
hf_token: str | None = None
|
| 27 |
role_pool: object | None = None
|
| 28 |
+
shared_head: object | None = None
|
| 29 |
max_targeted_retry: int = 1
|
| 30 |
constructive_model_id: str = HF_MODEL_4B
|
| 31 |
adversarial_model_id: str = HF_MODEL_4B
|
| 32 |
evidence_model_id: str = HF_MODEL_8B
|
| 33 |
+
constructive_api_url: str = HF_CHAT_API_CONSTRUCTIVE
|
| 34 |
+
adversarial_api_url: str = HF_CHAT_API_ADVERSARIAL
|
| 35 |
+
evidence_api_url: str = HF_CHAT_API_EVIDENCE
|
| 36 |
+
timeout_seconds: float = 300.0
|
| 37 |
|
| 38 |
|
| 39 |
def build_work(deps: RuntimeDependencies) -> CustomerAIWork:
|
|
|
|
| 59 |
constructive_model_id=deps.constructive_model_id,
|
| 60 |
adversarial_model_id=deps.adversarial_model_id,
|
| 61 |
evidence_model_id=deps.evidence_model_id,
|
| 62 |
+
constructive_api_url=deps.constructive_api_url,
|
| 63 |
+
adversarial_api_url=deps.adversarial_api_url,
|
| 64 |
+
evidence_api_url=deps.evidence_api_url,
|
| 65 |
timeout_seconds=deps.timeout_seconds,
|
| 66 |
)
|
| 67 |
)
|
runtime/hf_client.py
CHANGED
|
@@ -5,31 +5,47 @@ from collections.abc import Sequence
|
|
| 5 |
|
| 6 |
import httpx
|
| 7 |
|
| 8 |
-
HF_MODEL_4B = "
|
| 9 |
-
HF_MODEL_8B = "
|
| 10 |
HF_ALLOWED_MODELS = frozenset({HF_MODEL_4B, HF_MODEL_8B})
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
|
| 14 |
class HFChatClient:
|
| 15 |
-
"""Role-scoped
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def __init__(
|
| 18 |
self,
|
| 19 |
*,
|
| 20 |
-
token: str,
|
| 21 |
model_id: str,
|
| 22 |
-
api_url: str =
|
| 23 |
-
timeout_seconds: float =
|
| 24 |
client: httpx.AsyncClient | None = None,
|
| 25 |
):
|
| 26 |
if model_id not in HF_ALLOWED_MODELS:
|
| 27 |
raise ValueError(f"model_drift:{model_id}")
|
| 28 |
-
|
| 29 |
-
|
|
|
|
| 30 |
self.model_id = model_id
|
| 31 |
-
self.api_url =
|
| 32 |
-
self._token = token.strip()
|
| 33 |
self._owned = client is None
|
| 34 |
self._client = client or httpx.AsyncClient(timeout=timeout_seconds)
|
| 35 |
|
|
@@ -37,7 +53,7 @@ class HFChatClient:
|
|
| 37 |
self,
|
| 38 |
messages: Sequence[dict[str, str]],
|
| 39 |
*,
|
| 40 |
-
max_tokens: int =
|
| 41 |
) -> dict[str, object]:
|
| 42 |
payload = {
|
| 43 |
"model": self.model_id,
|
|
@@ -45,10 +61,10 @@ class HFChatClient:
|
|
| 45 |
"response_format": {"type": "json_object"},
|
| 46 |
"stream": False,
|
| 47 |
"temperature": 0.1,
|
|
|
|
| 48 |
"max_tokens": max_tokens,
|
| 49 |
}
|
| 50 |
-
|
| 51 |
-
response = await self._client.post(self.api_url, headers=headers, json=payload)
|
| 52 |
response.raise_for_status()
|
| 53 |
body = response.json()
|
| 54 |
choices = body.get("choices") or []
|
|
@@ -57,7 +73,12 @@ class HFChatClient:
|
|
| 57 |
content = choices[0].get("message", {}).get("content")
|
| 58 |
if not isinstance(content, str) or not content.strip():
|
| 59 |
raise RuntimeError("model_empty_content")
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
if not isinstance(decoded, dict):
|
| 62 |
raise RuntimeError("model_non_object_json")
|
| 63 |
return decoded
|
|
|
|
| 5 |
|
| 6 |
import httpx
|
| 7 |
|
| 8 |
+
HF_MODEL_4B = "llm-jp/llm-jp-3-3.7b-instruct3"
|
| 9 |
+
HF_MODEL_8B = "llm-jp/llm-jp-4-8b-instruct"
|
| 10 |
HF_ALLOWED_MODELS = frozenset({HF_MODEL_4B, HF_MODEL_8B})
|
| 11 |
+
|
| 12 |
+
HF_CHAT_API_CONSTRUCTIVE = "http://127.0.0.1:8081/v1/chat/completions"
|
| 13 |
+
HF_CHAT_API_ADVERSARIAL = "http://127.0.0.1:8082/v1/chat/completions"
|
| 14 |
+
HF_CHAT_API_EVIDENCE = "http://127.0.0.1:8083/v1/chat/completions"
|
| 15 |
+
HF_CHAT_API = HF_CHAT_API_CONSTRUCTIVE # compatibility alias only
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _default_local_url(model_id: str) -> str:
|
| 19 |
+
if model_id == HF_MODEL_4B:
|
| 20 |
+
return HF_CHAT_API_CONSTRUCTIVE
|
| 21 |
+
if model_id == HF_MODEL_8B:
|
| 22 |
+
return HF_CHAT_API_EVIDENCE
|
| 23 |
+
raise ValueError(f"model_drift:{model_id}")
|
| 24 |
|
| 25 |
|
| 26 |
class HFChatClient:
|
| 27 |
+
"""Role-scoped OpenAI-compatible client backed only by local llama.cpp.
|
| 28 |
+
|
| 29 |
+
Production rejects non-loopback endpoints so HF Inference Providers cannot
|
| 30 |
+
be used accidentally and cannot consume inference credits.
|
| 31 |
+
"""
|
| 32 |
|
| 33 |
def __init__(
|
| 34 |
self,
|
| 35 |
*,
|
| 36 |
+
token: str = "",
|
| 37 |
model_id: str,
|
| 38 |
+
api_url: str | None = None,
|
| 39 |
+
timeout_seconds: float = 600.0,
|
| 40 |
client: httpx.AsyncClient | None = None,
|
| 41 |
):
|
| 42 |
if model_id not in HF_ALLOWED_MODELS:
|
| 43 |
raise ValueError(f"model_drift:{model_id}")
|
| 44 |
+
resolved_url = (api_url or _default_local_url(model_id)).strip()
|
| 45 |
+
if not resolved_url.startswith(("http://127.0.0.1", "http://localhost")):
|
| 46 |
+
raise ValueError("remote_inference_endpoint_forbidden")
|
| 47 |
self.model_id = model_id
|
| 48 |
+
self.api_url = resolved_url
|
|
|
|
| 49 |
self._owned = client is None
|
| 50 |
self._client = client or httpx.AsyncClient(timeout=timeout_seconds)
|
| 51 |
|
|
|
|
| 53 |
self,
|
| 54 |
messages: Sequence[dict[str, str]],
|
| 55 |
*,
|
| 56 |
+
max_tokens: int = 900,
|
| 57 |
) -> dict[str, object]:
|
| 58 |
payload = {
|
| 59 |
"model": self.model_id,
|
|
|
|
| 61 |
"response_format": {"type": "json_object"},
|
| 62 |
"stream": False,
|
| 63 |
"temperature": 0.1,
|
| 64 |
+
"top_p": 0.9,
|
| 65 |
"max_tokens": max_tokens,
|
| 66 |
}
|
| 67 |
+
response = await self._client.post(self.api_url, json=payload)
|
|
|
|
| 68 |
response.raise_for_status()
|
| 69 |
body = response.json()
|
| 70 |
choices = body.get("choices") or []
|
|
|
|
| 73 |
content = choices[0].get("message", {}).get("content")
|
| 74 |
if not isinstance(content, str) or not content.strip():
|
| 75 |
raise RuntimeError("model_empty_content")
|
| 76 |
+
text = content.strip()
|
| 77 |
+
first = text.find("{")
|
| 78 |
+
last = text.rfind("}")
|
| 79 |
+
if first >= 0 and last >= first:
|
| 80 |
+
text = text[first : last + 1]
|
| 81 |
+
decoded = json.loads(text)
|
| 82 |
if not isinstance(decoded, dict):
|
| 83 |
raise RuntimeError("model_non_object_json")
|
| 84 |
return decoded
|
runtime/model.py
CHANGED
|
@@ -7,6 +7,13 @@ from typing import Protocol
|
|
| 7 |
|
| 8 |
import httpx
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
from .roles import role_rules
|
| 11 |
from .schemas import RoleName, RoleResult, SharedRolePacket
|
| 12 |
|
|
@@ -39,26 +46,76 @@ class ResidentRolePool:
|
|
| 39 |
return await self._workers[role].run(packet)
|
| 40 |
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
class HuggingFaceRoleBackend:
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
async def generate_role(self, role: RoleName, packet: SharedRolePacket) -> RoleResult:
|
| 49 |
-
if role != self.role:
|
|
|
|
| 50 |
prompt = {
|
| 51 |
"role": role.value,
|
| 52 |
"rules": role_rules(role),
|
| 53 |
"packet": packet.model_dump(mode="json"),
|
| 54 |
"required_output_schema": RoleResult.model_json_schema(),
|
| 55 |
-
"constraints": [
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
async with httpx.AsyncClient(timeout=self.timeout_seconds) as client:
|
| 58 |
-
response = await client.post(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
response.raise_for_status()
|
| 60 |
-
payload = response.json()
|
| 61 |
-
|
|
|
|
|
|
|
| 62 |
content = choices[0].get("message", {}).get("content")
|
| 63 |
-
if not isinstance(content, str) or not content.strip():
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
import httpx
|
| 9 |
|
| 10 |
+
from .hf_client import (
|
| 11 |
+
HF_CHAT_API_ADVERSARIAL,
|
| 12 |
+
HF_CHAT_API_CONSTRUCTIVE,
|
| 13 |
+
HF_CHAT_API_EVIDENCE,
|
| 14 |
+
HF_MODEL_4B,
|
| 15 |
+
HF_MODEL_8B,
|
| 16 |
+
)
|
| 17 |
from .roles import role_rules
|
| 18 |
from .schemas import RoleName, RoleResult, SharedRolePacket
|
| 19 |
|
|
|
|
| 46 |
return await self._workers[role].run(packet)
|
| 47 |
|
| 48 |
|
| 49 |
+
_LOCAL_ROLE_CONFIG = {
|
| 50 |
+
RoleName.CONSTRUCTIVE: (HF_MODEL_4B, HF_CHAT_API_CONSTRUCTIVE),
|
| 51 |
+
RoleName.ADVERSARIAL: (HF_MODEL_4B, HF_CHAT_API_ADVERSARIAL),
|
| 52 |
+
RoleName.EVIDENCE_BOUND: (HF_MODEL_8B, HF_CHAT_API_EVIDENCE),
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
class HuggingFaceRoleBackend:
|
| 57 |
+
"""Compatibility backend name retained; inference itself is local-only.
|
| 58 |
+
|
| 59 |
+
No Hugging Face Inference Provider endpoint is accepted. The HF token is
|
| 60 |
+
intentionally ignored for inference and exists only for old constructor compatibility.
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
def __init__(
|
| 64 |
+
self,
|
| 65 |
+
*,
|
| 66 |
+
role: RoleName,
|
| 67 |
+
model_id: str | None = None,
|
| 68 |
+
token: str = "",
|
| 69 |
+
api_url: str | None = None,
|
| 70 |
+
timeout_seconds: float = 600.0,
|
| 71 |
+
):
|
| 72 |
+
expected_model, expected_url = _LOCAL_ROLE_CONFIG[role]
|
| 73 |
+
chosen_model = (model_id or expected_model).strip()
|
| 74 |
+
chosen_url = (api_url or expected_url).strip()
|
| 75 |
+
if chosen_model != expected_model:
|
| 76 |
+
raise ValueError(f"model_drift:{role.value}:{chosen_model}")
|
| 77 |
+
if chosen_url != expected_url:
|
| 78 |
+
raise ValueError(f"remote_or_wrong_endpoint_forbidden:{role.value}:{chosen_url}")
|
| 79 |
+
self.role = role
|
| 80 |
+
self.model_id = chosen_model
|
| 81 |
+
self.api_url = chosen_url
|
| 82 |
+
self.timeout_seconds = timeout_seconds
|
| 83 |
|
| 84 |
async def generate_role(self, role: RoleName, packet: SharedRolePacket) -> RoleResult:
|
| 85 |
+
if role != self.role:
|
| 86 |
+
raise ValueError("backend_role_mismatch")
|
| 87 |
prompt = {
|
| 88 |
"role": role.value,
|
| 89 |
"rules": role_rules(role),
|
| 90 |
"packet": packet.model_dump(mode="json"),
|
| 91 |
"required_output_schema": RoleResult.model_json_schema(),
|
| 92 |
+
"constraints": [
|
| 93 |
+
"Use only supplied packet facts for Astera-specific claims.",
|
| 94 |
+
"Return JSON only.",
|
| 95 |
+
"Do not claim external actions were executed.",
|
| 96 |
+
],
|
| 97 |
}
|
| 98 |
async with httpx.AsyncClient(timeout=self.timeout_seconds) as client:
|
| 99 |
+
response = await client.post(
|
| 100 |
+
self.api_url,
|
| 101 |
+
json={
|
| 102 |
+
"model": self.model_id,
|
| 103 |
+
"messages": [{"role": "user", "content": json.dumps(prompt, ensure_ascii=False)}],
|
| 104 |
+
"response_format": {"type": "json_object"},
|
| 105 |
+
"stream": False,
|
| 106 |
+
},
|
| 107 |
+
)
|
| 108 |
response.raise_for_status()
|
| 109 |
+
payload = response.json()
|
| 110 |
+
choices = payload.get("choices") or []
|
| 111 |
+
if not choices:
|
| 112 |
+
raise RuntimeError("model_empty_choices")
|
| 113 |
content = choices[0].get("message", {}).get("content")
|
| 114 |
+
if not isinstance(content, str) or not content.strip():
|
| 115 |
+
raise RuntimeError("model_empty_content")
|
| 116 |
+
text = content.strip()
|
| 117 |
+
first = text.find("{")
|
| 118 |
+
last = text.rfind("}")
|
| 119 |
+
if first >= 0 and last >= first:
|
| 120 |
+
text = text[first : last + 1]
|
| 121 |
+
return RoleResult.model_validate_json(text)
|
runtime/runtime_factory.py
CHANGED
|
@@ -4,7 +4,14 @@ from dataclasses import dataclass
|
|
| 4 |
|
| 5 |
import httpx
|
| 6 |
|
| 7 |
-
from .hf_client import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
from .integration import DialogueIntegrator
|
| 9 |
from .internal_core import CustomerAIInternalCore
|
| 10 |
from .kb_search import LocalHybridKnowledgeStore
|
|
@@ -32,8 +39,10 @@ class InternalRuntimeDependencies:
|
|
| 32 |
constructive_model_id: str = HF_MODEL_4B
|
| 33 |
adversarial_model_id: str = HF_MODEL_4B
|
| 34 |
evidence_model_id: str = HF_MODEL_8B
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
def _canonical_store(deps: InternalRuntimeDependencies):
|
|
@@ -56,27 +65,25 @@ def _role_pool(deps: InternalRuntimeDependencies):
|
|
| 56 |
raise ValueError("adversarial_model_drift")
|
| 57 |
if deps.evidence_model_id != HF_MODEL_8B:
|
| 58 |
raise ValueError("evidence_model_drift")
|
| 59 |
-
|
| 60 |
-
if not token.strip():
|
| 61 |
-
raise ValueError("hf_token_required")
|
| 62 |
shared_http = httpx.AsyncClient(timeout=deps.timeout_seconds)
|
| 63 |
clients = {
|
| 64 |
RoleName.CONSTRUCTIVE: HFChatClient(
|
| 65 |
-
token=
|
| 66 |
model_id=deps.constructive_model_id,
|
| 67 |
-
api_url=deps.
|
| 68 |
client=shared_http,
|
| 69 |
),
|
| 70 |
RoleName.ADVERSARIAL: HFChatClient(
|
| 71 |
-
token=
|
| 72 |
model_id=deps.adversarial_model_id,
|
| 73 |
-
api_url=deps.
|
| 74 |
client=shared_http,
|
| 75 |
),
|
| 76 |
RoleName.EVIDENCE_BOUND: HFChatClient(
|
| 77 |
-
token=
|
| 78 |
model_id=deps.evidence_model_id,
|
| 79 |
-
api_url=deps.
|
| 80 |
client=shared_http,
|
| 81 |
),
|
| 82 |
}
|
|
|
|
| 4 |
|
| 5 |
import httpx
|
| 6 |
|
| 7 |
+
from .hf_client import (
|
| 8 |
+
HFChatClient,
|
| 9 |
+
HF_CHAT_API_ADVERSARIAL,
|
| 10 |
+
HF_CHAT_API_CONSTRUCTIVE,
|
| 11 |
+
HF_CHAT_API_EVIDENCE,
|
| 12 |
+
HF_MODEL_4B,
|
| 13 |
+
HF_MODEL_8B,
|
| 14 |
+
)
|
| 15 |
from .integration import DialogueIntegrator
|
| 16 |
from .internal_core import CustomerAIInternalCore
|
| 17 |
from .kb_search import LocalHybridKnowledgeStore
|
|
|
|
| 39 |
constructive_model_id: str = HF_MODEL_4B
|
| 40 |
adversarial_model_id: str = HF_MODEL_4B
|
| 41 |
evidence_model_id: str = HF_MODEL_8B
|
| 42 |
+
constructive_api_url: str = HF_CHAT_API_CONSTRUCTIVE
|
| 43 |
+
adversarial_api_url: str = HF_CHAT_API_ADVERSARIAL
|
| 44 |
+
evidence_api_url: str = HF_CHAT_API_EVIDENCE
|
| 45 |
+
timeout_seconds: float = 600.0
|
| 46 |
|
| 47 |
|
| 48 |
def _canonical_store(deps: InternalRuntimeDependencies):
|
|
|
|
| 65 |
raise ValueError("adversarial_model_drift")
|
| 66 |
if deps.evidence_model_id != HF_MODEL_8B:
|
| 67 |
raise ValueError("evidence_model_drift")
|
| 68 |
+
|
|
|
|
|
|
|
| 69 |
shared_http = httpx.AsyncClient(timeout=deps.timeout_seconds)
|
| 70 |
clients = {
|
| 71 |
RoleName.CONSTRUCTIVE: HFChatClient(
|
| 72 |
+
token="",
|
| 73 |
model_id=deps.constructive_model_id,
|
| 74 |
+
api_url=deps.constructive_api_url,
|
| 75 |
client=shared_http,
|
| 76 |
),
|
| 77 |
RoleName.ADVERSARIAL: HFChatClient(
|
| 78 |
+
token="",
|
| 79 |
model_id=deps.adversarial_model_id,
|
| 80 |
+
api_url=deps.adversarial_api_url,
|
| 81 |
client=shared_http,
|
| 82 |
),
|
| 83 |
RoleName.EVIDENCE_BOUND: HFChatClient(
|
| 84 |
+
token="",
|
| 85 |
model_id=deps.evidence_model_id,
|
| 86 |
+
api_url=deps.evidence_api_url,
|
| 87 |
client=shared_http,
|
| 88 |
),
|
| 89 |
}
|
runtime/startup.py
CHANGED
|
@@ -6,7 +6,11 @@ from collections.abc import Iterable, Mapping
|
|
| 6 |
from pathlib import Path
|
| 7 |
|
| 8 |
from .bootstrap import RuntimeDependencies, build_work
|
| 9 |
-
from .hf_client import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
from .kb_bucket import (
|
| 11 |
HF_KB_ACTIVE_POINTER_DEFAULT,
|
| 12 |
HF_KB_BUCKET_DEFAULT,
|
|
@@ -59,10 +63,7 @@ def _load_alias_registry(path_value: str) -> Mapping[str, Iterable[str]]:
|
|
| 59 |
def _production_kb_files(values: Mapping[str, str]) -> tuple[Path, Path | None, Path | None, str]:
|
| 60 |
build_id = _required_value(values, "CUSTOMER_AI_KB_BUILD_ID", "kb_build_id_missing")
|
| 61 |
mount_path = values.get("CUSTOMER_AI_KB_MOUNT_PATH", "").strip() or HF_KB_MOUNT_DEFAULT
|
| 62 |
-
pointer_name = (
|
| 63 |
-
values.get("CUSTOMER_AI_KB_ACTIVE_POINTER", "").strip()
|
| 64 |
-
or HF_KB_ACTIVE_POINTER_DEFAULT
|
| 65 |
-
)
|
| 66 |
bucket_id = values.get("CUSTOMER_AI_KB_BUCKET_ID", "").strip() or HF_KB_BUCKET_DEFAULT
|
| 67 |
token = (values.get("HF_TOKEN", "") or values.get("HF_KEY", "")).strip()
|
| 68 |
|
|
@@ -124,9 +125,9 @@ def create_work_from_environment(
|
|
| 124 |
else:
|
| 125 |
live_provider = EmptyLiveStateProvider()
|
| 126 |
|
|
|
|
|
|
|
| 127 |
token = (values.get("HF_TOKEN", "") or values.get("HF_KEY", "")).strip()
|
| 128 |
-
if role_pool is None and not token:
|
| 129 |
-
raise RuntimeNotReady("hf_token_missing")
|
| 130 |
|
| 131 |
try:
|
| 132 |
fuzzy_threshold = float(values.get("CUSTOMER_AI_JA_FUZZY_THRESHOLD", "90"))
|
|
@@ -142,7 +143,12 @@ def create_work_from_environment(
|
|
| 142 |
kb_generation_id=generation_id,
|
| 143 |
hf_token=token or None,
|
| 144 |
role_pool=role_pool,
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
)
|
| 148 |
)
|
|
|
|
| 6 |
from pathlib import Path
|
| 7 |
|
| 8 |
from .bootstrap import RuntimeDependencies, build_work
|
| 9 |
+
from .hf_client import (
|
| 10 |
+
HF_CHAT_API_ADVERSARIAL,
|
| 11 |
+
HF_CHAT_API_CONSTRUCTIVE,
|
| 12 |
+
HF_CHAT_API_EVIDENCE,
|
| 13 |
+
)
|
| 14 |
from .kb_bucket import (
|
| 15 |
HF_KB_ACTIVE_POINTER_DEFAULT,
|
| 16 |
HF_KB_BUCKET_DEFAULT,
|
|
|
|
| 63 |
def _production_kb_files(values: Mapping[str, str]) -> tuple[Path, Path | None, Path | None, str]:
|
| 64 |
build_id = _required_value(values, "CUSTOMER_AI_KB_BUILD_ID", "kb_build_id_missing")
|
| 65 |
mount_path = values.get("CUSTOMER_AI_KB_MOUNT_PATH", "").strip() or HF_KB_MOUNT_DEFAULT
|
| 66 |
+
pointer_name = values.get("CUSTOMER_AI_KB_ACTIVE_POINTER", "").strip() or HF_KB_ACTIVE_POINTER_DEFAULT
|
|
|
|
|
|
|
|
|
|
| 67 |
bucket_id = values.get("CUSTOMER_AI_KB_BUCKET_ID", "").strip() or HF_KB_BUCKET_DEFAULT
|
| 68 |
token = (values.get("HF_TOKEN", "") or values.get("HF_KEY", "")).strip()
|
| 69 |
|
|
|
|
| 125 |
else:
|
| 126 |
live_provider = EmptyLiveStateProvider()
|
| 127 |
|
| 128 |
+
# The HF token is needed only when the private KB bucket must be read remotely.
|
| 129 |
+
# Local role inference never uses the token.
|
| 130 |
token = (values.get("HF_TOKEN", "") or values.get("HF_KEY", "")).strip()
|
|
|
|
|
|
|
| 131 |
|
| 132 |
try:
|
| 133 |
fuzzy_threshold = float(values.get("CUSTOMER_AI_JA_FUZZY_THRESHOLD", "90"))
|
|
|
|
| 143 |
kb_generation_id=generation_id,
|
| 144 |
hf_token=token or None,
|
| 145 |
role_pool=role_pool,
|
| 146 |
+
constructive_api_url=values.get("CUSTOMER_AI_CONSTRUCTIVE_API_URL", "").strip()
|
| 147 |
+
or HF_CHAT_API_CONSTRUCTIVE,
|
| 148 |
+
adversarial_api_url=values.get("CUSTOMER_AI_ADVERSARIAL_API_URL", "").strip()
|
| 149 |
+
or HF_CHAT_API_ADVERSARIAL,
|
| 150 |
+
evidence_api_url=values.get("CUSTOMER_AI_EVIDENCE_API_URL", "").strip()
|
| 151 |
+
or HF_CHAT_API_EVIDENCE,
|
| 152 |
+
timeout_seconds=float(values.get("CUSTOMER_AI_ROLE_TIMEOUT_SECONDS", "600")),
|
| 153 |
)
|
| 154 |
)
|
scripts/start_local_cpu.sh
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
set -eu
|
| 3 |
+
|
| 4 |
+
MODEL_4B="${CUSTOMER_AI_LOCAL_4B_PATH:-/models/llm-jp-3-3.7b-instruct3-Q4_K_M.gguf}"
|
| 5 |
+
MODEL_8B="${CUSTOMER_AI_LOCAL_8B_PATH:-/models/llm-jp-4-8b-instruct-Q4_K_M.gguf}"
|
| 6 |
+
|
| 7 |
+
for path in "$MODEL_4B" "$MODEL_8B"; do
|
| 8 |
+
if [ ! -s "$path" ]; then
|
| 9 |
+
echo "local_model_missing=$path" >&2
|
| 10 |
+
exit 1
|
| 11 |
+
fi
|
| 12 |
+
done
|
| 13 |
+
|
| 14 |
+
start_server() {
|
| 15 |
+
name="$1"
|
| 16 |
+
model="$2"
|
| 17 |
+
alias="$3"
|
| 18 |
+
port="$4"
|
| 19 |
+
threads="$5"
|
| 20 |
+
|
| 21 |
+
/opt/llama/llama-server \
|
| 22 |
+
--model "$model" \
|
| 23 |
+
--alias "$alias" \
|
| 24 |
+
--host 127.0.0.1 \
|
| 25 |
+
--port "$port" \
|
| 26 |
+
--ctx-size 2048 \
|
| 27 |
+
--threads "$threads" \
|
| 28 |
+
--threads-batch "$threads" \
|
| 29 |
+
--parallel 1 \
|
| 30 |
+
--jinja \
|
| 31 |
+
> "/tmp/${name}.log" 2>&1 &
|
| 32 |
+
echo $!
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
# Required topology: two independent 4B runtimes plus one independent 8B runtime.
|
| 36 |
+
PID_CONSTRUCTIVE=$(start_server constructive "$MODEL_4B" "llm-jp/llm-jp-3-3.7b-instruct3" 8081 1)
|
| 37 |
+
PID_ADVERSARIAL=$(start_server adversarial "$MODEL_4B" "llm-jp/llm-jp-3-3.7b-instruct3" 8082 1)
|
| 38 |
+
PID_EVIDENCE=$(start_server evidence "$MODEL_8B" "llm-jp/llm-jp-4-8b-instruct" 8083 1)
|
| 39 |
+
|
| 40 |
+
check_alive() {
|
| 41 |
+
pid="$1"
|
| 42 |
+
name="$2"
|
| 43 |
+
if ! kill -0 "$pid" 2>/dev/null; then
|
| 44 |
+
echo "local_llama_server_exited=$name" >&2
|
| 45 |
+
tail -200 "/tmp/${name}.log" >&2 || true
|
| 46 |
+
exit 1
|
| 47 |
+
fi
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
ready=0
|
| 51 |
+
i=0
|
| 52 |
+
while [ "$i" -lt 900 ]; do
|
| 53 |
+
check_alive "$PID_CONSTRUCTIVE" constructive
|
| 54 |
+
check_alive "$PID_ADVERSARIAL" adversarial
|
| 55 |
+
check_alive "$PID_EVIDENCE" evidence
|
| 56 |
+
if curl -fsS http://127.0.0.1:8081/health >/dev/null 2>&1 \
|
| 57 |
+
&& curl -fsS http://127.0.0.1:8082/health >/dev/null 2>&1 \
|
| 58 |
+
&& curl -fsS http://127.0.0.1:8083/health >/dev/null 2>&1; then
|
| 59 |
+
ready=1
|
| 60 |
+
break
|
| 61 |
+
fi
|
| 62 |
+
i=$((i + 1))
|
| 63 |
+
sleep 1
|
| 64 |
+
done
|
| 65 |
+
|
| 66 |
+
if [ "$ready" -ne 1 ]; then
|
| 67 |
+
echo "local_role_models_not_ready" >&2
|
| 68 |
+
for name in constructive adversarial evidence; do
|
| 69 |
+
echo "--- ${name} ---" >&2
|
| 70 |
+
tail -100 "/tmp/${name}.log" >&2 || true
|
| 71 |
+
done
|
| 72 |
+
exit 1
|
| 73 |
+
fi
|
| 74 |
+
|
| 75 |
+
echo "LOCAL_ROLE_MODELS_READY=constructive:4B@8081,adversarial:4B@8082,evidence:8B@8083,provider:none"
|
| 76 |
+
exec uvicorn app:app --host 0.0.0.0 --port "${PORT:-7860}"
|