yannsay's picture
Deploy clean to build-small-hackathon org (32B Qwen, step-3 table)
aa1d0aa verified
Raw
History Blame Contribute Delete
1.66 kB
from __future__ import annotations
import os
from pydantic import BaseModel
from core.ports import ChatModel
class ModalChatModel(ChatModel):
"""ChatModel backed by a Modal-hosted vLLM endpoint (OpenAI-compatible API)."""
def __init__(self) -> None:
from langchain_openai import ChatOpenAI # deferred: must follow load_dotenv()
url = os.environ["MODAL_INFERENCE_URL"]
key = os.environ["MODAL_API_KEY"]
model_id = os.environ["MODAL_MODEL_ID"]
self._llm = ChatOpenAI(
base_url=url.rstrip("/") + "/v1",
api_key=key,
model=model_id,
max_tokens=2048,
# temperature=0: deterministic structured calls. The model default (0.6, seen in the
# vLLM generation_config) makes the model ramble to max_tokens → truncated JSON →
# "Could not parse response content as the length limit was reached". This is the
# primary fix for that truncation class.
temperature=0,
)
def structured(self, messages: list[dict], schema: type[BaseModel]) -> BaseModel:
# method="json_schema" is already the langchain_openai default; stated explicitly to
# document that we rely on vLLM's structured-outputs (guided JSON) backend, not tool
# calling. (strict=True is intentionally deferred until verified against the live vLLM
# endpoint — it would gate every structured call in the app.)
return self._llm.with_structured_output(schema, method="json_schema").invoke(messages)
def complete(self, messages: list[dict]) -> str:
return self._llm.invoke(messages).content