SmartWareHouseAI / src /llm_client.py
Pro-Coder's picture
Upload 28 files
f0fae3f verified
Raw
History Blame
4.27 kB
"""
llm_client.py
-------------
Wraps a hosted LLM (via huggingface_hub's InferenceClient, using HF's
serverless Inference Providers) to answer warehouse-operations questions,
grounded with context retrieved from the local knowledge base (simple RAG).
Design notes
------------
* Reads the HF token from the `HF_TOKEN` environment variable, which should
be added as a Space "secret" when deployed (Settings -> Variables and
secrets). The public demo also works without a token: it falls back to a
deterministic, still-useful extractive answer built from the retrieved
knowledge-base passages, so the Space never shows a broken demo.
* The model name is configurable via `LLM_MODEL_ID` (defaults to a small,
fast, freely-hostable instruct model).
"""
import os
import time
from dataclasses import dataclass
from typing import List
from src.retriever import KBRetriever, RetrievedDoc
DEFAULT_MODEL_ID = os.environ.get("LLM_MODEL_ID", "Qwen/Qwen2.5-7B-Instruct")
SYSTEM_PROMPT = (
"You are the Smart Warehouse AI Assistant, a helpful operations copilot "
"for a large automated distribution center (conveyors, AS/RS, AGVs/AMRs, "
"sortation, and a WMS). Answer concisely and practically, in the tone of "
"an experienced warehouse operations engineer. Use the provided CONTEXT "
"when relevant, and say so plainly if the question is outside the "
"context. Prefer short paragraphs or bullet points over long prose."
)
@dataclass
class AssistantResponse:
answer: str
used_llm: bool
sources: List[RetrievedDoc]
latency_s: float
model_id: str
def _extractive_fallback(query: str, sources: List[RetrievedDoc]) -> str:
"""Deterministic answer used when no HF token / API call fails, so the
Space always returns something useful instead of an error."""
if not sources:
return (
"I don't have grounded context for that yet. Try asking about "
"inventory, order status, equipment maintenance, AGV routing, "
"picking strategy, safety incidents, or general warehouse "
"automation concepts."
)
lead = sources[0]
bullets = "\n".join(f"- **{s.title}**: {s.text}" for s in sources)
return (
f"(Offline / no LLM API key configured -- showing retrieved "
f"knowledge instead of a generated answer.)\n\n"
f"Based on **{lead.title}**, here's the relevant information:\n\n{bullets}"
)
def answer_query(
query: str,
retriever: KBRetriever,
k: int = 2,
model_id: str = DEFAULT_MODEL_ID,
max_tokens: int = 350,
) -> AssistantResponse:
start = time.time()
sources = retriever.retrieve(query, k=k)
context_block = "\n\n".join(f"[{s.title}]\n{s.text}" for s in sources)
hf_token = os.environ.get("HF_TOKEN")
if not hf_token:
answer = _extractive_fallback(query, sources)
return AssistantResponse(
answer=answer,
used_llm=False,
sources=sources,
latency_s=time.time() - start,
model_id="extractive-fallback",
)
try:
from huggingface_hub import InferenceClient
client = InferenceClient(model=model_id, token=hf_token)
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{
"role": "user",
"content": f"CONTEXT:\n{context_block}\n\nQUESTION: {query}",
},
]
completion = client.chat_completion(messages=messages, max_tokens=max_tokens, temperature=0.3)
text = completion.choices[0].message.content
return AssistantResponse(
answer=text,
used_llm=True,
sources=sources,
latency_s=time.time() - start,
model_id=model_id,
)
except Exception as e: # noqa: BLE001 -- deliberately broad: any API/network issue -> fallback
answer = _extractive_fallback(query, sources)
answer += f"\n\n_(LLM call failed: {type(e).__name__}. Showing retrieval-only answer.)_"
return AssistantResponse(
answer=answer,
used_llm=False,
sources=sources,
latency_s=time.time() - start,
model_id="extractive-fallback",
)