LLM & embedding models¶
tdfs4ds decouples three model concerns:
| Setting block | Used by |
|---|---|
INSTRUCT_MODEL_* |
Process documentation, dataset documentation, intent classification, answer synthesis, query optimizer |
EMBEDDING_MODEL_* |
Consumer agent vector index — client-side embeddings (Chroma backend; default for the Teradata backend) |
CHROMA_* |
Where the Chroma vector store persists |
VECTOR_STORE_BACKEND |
'chroma' (default) or 'teradata' — selects where the vector index lives |
TERADATA_VECTOR_STORE_* |
Naming for the Teradata Enterprise Vector Store collections |
TERADATA_AI_* |
Opt-in in-database embeddings via teradatagenai.TeradataAI (Teradata backend only) |
If EMBEDDING_MODEL_* is not set, it falls back to INSTRUCT_MODEL_*.
Supported providers¶
| Provider | INSTRUCT_MODEL_PROVIDER |
Notes |
|---|---|---|
| OpenAI | 'openai' |
INSTRUCT_MODEL_URL optional |
| Azure OpenAI | 'azure' |
INSTRUCT_MODEL_URL required (deployment endpoint) |
| vLLM / TGI | 'vllm' |
OpenAI-compatible HTTP endpoint |
| AWS Bedrock | 'bedrock' (alias 'aws') |
No URL / API key — uses boto3 default credentials (Converse API). With a .../openai/v1 URL it uses Bedrock's OpenAI-compatible endpoint instead, see below |
The same provider keys apply to embeddings via EMBEDDING_MODEL_PROVIDER.
OpenAI-compatible example¶
tdfs4ds.INSTRUCT_MODEL_PROVIDER = 'openai'
tdfs4ds.INSTRUCT_MODEL_MODEL = 'gpt-4o'
tdfs4ds.INSTRUCT_MODEL_API_KEY = os.getenv('OPENAI_API_KEY')
tdfs4ds.EMBEDDING_MODEL_PROVIDER = 'openai'
tdfs4ds.EMBEDDING_MODEL_MODEL = 'text-embedding-3-small'
tdfs4ds.EMBEDDING_MODEL_DIM = 1536
LiteLLM: proxy or SDK?¶
The two LiteLLM topologies need different providers — picking the wrong one is the most common configuration mistake here.
| Your setup | Provider | Needs |
|---|---|---|
A LiteLLM proxy server (an HTTP endpoint, typically :4000) |
'openai-compatible' (or 'vllm') |
nothing extra — the proxy speaks the OpenAI API, so ChatOpenAI talks to it natively |
| The LiteLLM Python SDK, in-process, no proxy | 'litellm' |
pip install langchain-community litellm — neither is a tdfs4ds dependency |
If you are pointing INSTRUCT_MODEL_URL at a URL, you have a proxy — use
'openai-compatible':
tdfs4ds.INSTRUCT_MODEL_PROVIDER = 'openai-compatible'
tdfs4ds.INSTRUCT_MODEL_URL = 'http://litellm.example.svc.cluster.local:4000'
tdfs4ds.INSTRUCT_MODEL_API_KEY = os.getenv('LITELLM_VIRTUAL_KEY')
tdfs4ds.INSTRUCT_MODEL_MODEL = 'us-anthropic-claude-sonnet-4-5-...' # a proxy model name
The 'litellm' provider routes through
langchain_community.chat_models.ChatLiteLLM, which calls the litellm
library directly with provider-prefixed model strings
('bedrock/amazon.nova-pro-v1:0', 'ollama/llama3', …). Recent
langchain-community releases have also dropped ChatLiteLLM, so
'openai-compatible' is the more durable choice whenever a proxy exists.
JSON constraints: which syntax does your endpoint accept?¶
There is no single industry parameter for constrained decoding. Each serving stack named the same idea differently, and a gateway accepts only its own:
| Syntax | Serving stack |
|---|---|
response_format={"type": "json_schema"} |
OpenAI, Azure, Mistral, and gateways speaking their API |
tools=[...] + tool_choice |
anything with tool calling |
response_format={"type": "json_object"} |
OpenAI "JSON mode", widely copied |
extra_body.guided_json |
vLLM / SGLang |
extra_body.nvext.guided_json |
NVIDIA NIM |
extra_body.grammar |
HuggingFace TGI |
extra_body.format |
Ollama |
tdfs4ds asks for typed JSON along two independent paths, and they are controlled by different settings — which is why a problem with one is invisible from the other:
| Path | Used by | Mechanism | Setting |
|---|---|---|---|
langchain with_structured_output |
consumer agent, query optimizer | json_schema -> function_calling -> json_mode |
STRUCTURED_OUTPUT_METHOD |
| provider-branched wire parameter | documentation generator | one row of the table above | INSTRUCT_MODEL_PROVIDER |
The second one matters more than it looks. If the backend rejects your provider's parameter, the documentation path retries once unconstrained and carries on — producing plausible output at strictly lower reliability, with nothing reporting it.
Finding out¶
check_configuration() probes both paths, and prints one row for them:
[ OK ] JSON constraints agent: structured output enforced end to end;
documentation: response_format enforced - the reply is bare JSON
If either reports trouble, it then runs a nine-request wire-level sweep that tries every syntax above and identifies the stack behind the gateway from the accept/reject pattern. A healthy endpoint never pays for that sweep. You can run it on its own at any time:
report = tdfs4ds.probe_json_constraints()
report['engine'] # e.g. 'vLLM / SGLang'
report['recommend'] # e.g. {'STRUCTURED_OUTPUT_METHOD': 'function_calling'}
The common case: a relaying gateway¶
LiteLLM in front of Bedrock or Mistral accepts response_format and does not
enforce it. The model answers with markdown-fenced JSON, the strict parser
rejects it at line 1 column 1, and the consumer agent dies on its very first step
(intent classification). tdfs4ds recovers automatically by falling back to
function_calling, but that costs a failed request on every typed call until the
method is pinned:
Recording the answer¶
check_configuration(pin=True) applies what it discovered to the session and
writes it into your .env via patch_dotenv, stamped with the
endpoint and date it was probed against — so it is not re-derived every session,
and a pin that stops being true is visible rather than silent:
TDFS4DS_STRUCTURED_OUTPUT_METHOD=function_calling # pinned by check_configuration on 2026-09-11, probed against https://gw.example/v1
A pinned method travels into a deployment
STRUCTURED_OUTPUT_METHOD is ordinary config to the AgentOps packager, so a
pinned value ends up in a deployed pod's config.yaml. That is right when
the pod uses the same gateway and wrong when it does not — which is what the
date stamp is for.
TLS: internal certificate authorities¶
A gateway on an internal host often presents a certificate the system CA store does not trust. Two settings cover it, for the instruct model only — embedding clients are never affected:
# Preferred: verification stays ON, anchored to your internal CA
tdfs4ds.INSTRUCT_MODEL_CA_BUNDLE = '/etc/ssl/certs/internal-ca.pem'
# Last resort: disable verification entirely
tdfs4ds.INSTRUCT_MODEL_VERIFY_SSL = False
INSTRUCT_MODEL_VERIFY_SSL=False is the equivalent of
ChatOpenAI(..., http_client=httpx.Client(verify=False)). It works, and it is
sometimes the only option — but the API key then travels over a connection whose
peer was never authenticated, so tdfs4ds logs a warning whenever it is active
and check_configuration() reports TLS verification DISABLED in the instruct
check. Prefer the CA bundle whenever the certificate can be obtained.
With neither set, no custom HTTP client is built at all — identical to the behaviour before these options existed.
What check_configuration() reports¶
A dedicated LLM transport (TLS) check runs before the model is contacted and without sending any credential, so its answer frames everything after it:
[ OK ] LLM transport (TLS) https://api.example.com/v1 - TLSv1.3 -
cert api.example.com, issued by Internal Root CA -
expires Nov 21 01:57:54 2026 GMT (70 days)
It separates failures that look identical in a traceback but have different fixes:
| Reported | Fix |
|---|---|
| untrusted issuer | INSTRUCT_MODEL_CA_BUNDLE — and if the system store does accept the certificate, it says so, because then the bundle is simply the wrong one |
| hostname mismatch | the host in INSTRUCT_MODEL_URL; the certificate's real name is reported |
| expired | renewal on the server — nothing here can work around it |
| expires in under 30 days | nothing yet, which is the point of saying so now |
plain http:// |
acceptable in-cluster; the API key is unencrypted |
When verification is off it still reads the certificate (without validating it) so you can see which CA to obtain and turn verification back on.
Only the instruct model
These settings reach the OpenAI-family clients only. Bedrock and Vertex
secure their transport through their own SDKs, and the embedding client
is deliberately never affected by the instruct model's TLS posture — a
proxy fallback cannot weaken security, but a verification fallback could. If
an embedding endpoint sits behind an internal CA, install that CA in the
system trust store, point EMBEDDING_MODEL_URL at a publicly trusted route,
or use EMBEDDING_MODEL_PROXY.
Error code: 302 and 'str' object has no attribute 'model_dump'
Two symptoms, one cause, and neither is a TLS problem despite appearing in the same environments: something in front of the gateway — an ingress, an SSO login page — answered instead of the model.
They differ only in whether the redirect was followed. httpx rewrites a
redirected POST into a GET, so following it lands on the target (an HTML
page: success status, non-JSON body, which the OpenAI SDK returns as raw text
for LangChain to mis-parse), while not following it surfaces the bare 302.
check_configuration() reports both with the same hint and adds a probe
showing where the URL actually leads — GET … -> 302 -> https://…. Check
that INSTRUCT_MODEL_URL's path is right (the SDK appends
/chat/completions), and if the target is a login page, look for a route
that bypasses the ingress, such as a cluster-local service URL.
vLLM behind LiteLLM example¶
tdfs4ds.INSTRUCT_MODEL_PROVIDER = 'vllm'
tdfs4ds.INSTRUCT_MODEL_URL = 'https://api.example.com/v1'
tdfs4ds.INSTRUCT_MODEL_API_KEY = os.getenv('FS_API_KEY')
tdfs4ds.INSTRUCT_MODEL_MODEL = 'mistral-7b-instruct'
tdfs4ds.EMBEDDING_MODEL_PROVIDER = 'vllm'
tdfs4ds.EMBEDDING_MODEL_URL = 'https://api.example.com/v1/e5'
tdfs4ds.EMBEDDING_MODEL_API_KEY = os.getenv('FS_API_KEY')
tdfs4ds.EMBEDDING_MODEL_MODEL = 'bge-m3'
tdfs4ds.EMBEDDING_MODEL_DIM = 1024
AWS Bedrock example¶
tdfs4ds.INSTRUCT_MODEL_PROVIDER = 'bedrock'
tdfs4ds.INSTRUCT_MODEL_MODEL = 'anthropic.claude-3-sonnet-20240229-v1:0'
# Authentication: boto3 default chain (env vars, ~/.aws/credentials, IAM role)
Bedrock's OpenAI-compatible endpoint¶
Give the provider (aws or bedrock) the endpoint URL and tdfs4ds speaks the OpenAI
protocol to it. Two ways to authenticate; neither needs anything else in the file:
# A) the environment's role - nothing secret on disk
TDFS4DS_INSTRUCT_MODEL_PROVIDER=aws
TDFS4DS_INSTRUCT_MODEL_URL=https://bedrock-runtime.eu-central-1.amazonaws.com/openai/v1
TDFS4DS_INSTRUCT_MODEL_MODEL=mistral.devstral-2-123b
TDFS4DS_INSTRUCT_MODEL_CLIENT_MODE=ambient
# B) a Bedrock API key, sent as the bearer token
TDFS4DS_INSTRUCT_MODEL_PROVIDER=aws
TDFS4DS_INSTRUCT_MODEL_URL=https://bedrock-runtime.eu-central-1.amazonaws.com/openai/v1
TDFS4DS_INSTRUCT_MODEL_MODEL=mistral.devstral-2-123b
TDFS4DS_INSTRUCT_MODEL_API_KEY=...
CLIENT_MODE=ambient(or simply no key) signs every request with the AWS identity of the environment - the standard credential chain: an instance or task role, SSO, a profile, the environment variables. The region comes from the URL.- The route is chosen by the URL alone: a
bedrockconfiguration without a.../openai/v1URL is the Converse client it has always been. - A role needs no key, so
check_configuration()no longer treats a missing one as a gap forbedrock/aws/gcp; the report says how the call authenticated (never a value). - An
AccessDeniedis a permission problem, not a missing key: allowbedrock:InvokeModelon the model (or on its inference profile,eu./us./apac.prefix) for the role. - Embeddings still use the Converse route; set
TDFS4DS_EMBEDDING_MODEL_MODELto a Bedrock embedding model (for exampleamazon.titan-embed-text-v2:0).
Listing available models¶
When the endpoint exposes /v1/models (OpenAI-compatible), discover models
without leaving Python:
from tdfs4ds.agent import list_instruct_models, list_embedding_models
list_instruct_models()
list_embedding_models(sub_paths=['e5', 'code'])
Vector store backend¶
The consumer agent's vector index is backend-agnostic. Pick where it lives via
VECTOR_STORE_BACKEND:
tdfs4ds.VECTOR_STORE_BACKEND = 'chroma' # default — local or remote ChromaDB
# or
tdfs4ds.VECTOR_STORE_BACKEND = 'teradata' # Teradata Enterprise Vector Store
build_vector_index() and search_vector_index() dispatch on this setting;
all callers (chatbot, search/definition skills) are unchanged.
Chroma backend¶
tdfs4ds.CHROMA_MODE = 'local' # persist to disk
tdfs4ds.CHROMA_PATH = './tdfs4ds_chroma'
# OR
tdfs4ds.CHROMA_MODE = 'server' # connect to a Chroma server
tdfs4ds.CHROMA_HOST = 'localhost'
tdfs4ds.CHROMA_PORT = 8000
The Chroma backend holds three collections (objects, sections, columns)
populated by build_vector_index() from the business dictionary. Embeddings
come from the EMBEDDING_MODEL_* (or INSTRUCT_MODEL_*) configuration.
Teradata backend¶
The Teradata Enterprise Vector Store persists the same three collections directly in Vantage as named vector stores:
tdfs4ds.VECTOR_STORE_BACKEND = 'teradata'
tdfs4ds.TERADATA_VECTOR_STORE_PREFIX = 'tdfs4ds' # → tdfs4ds_objects / _sections / _columns
By default the Teradata backend reuses the LangChain EMBEDDING_MODEL_*
setup — your existing vLLM / TEI / LiteLLM / OpenAI configuration keeps
working unchanged because TeradataVectorStore accepts a LangChain Embeddings
object at its embedding= argument.
To opt into in-database embeddings via teradatagenai.TeradataAI, set
TERADATA_AI_API_TYPE:
# Example: AWS Bedrock embeddings produced inside Vantage
tdfs4ds.TERADATA_AI_API_TYPE = 'aws'
tdfs4ds.TERADATA_AI_MODEL_NAME = 'amazon.titan-embed-text-v2:0'
tdfs4ds.TERADATA_AI_REGION = 'us-west-2'
tdfs4ds.TERADATA_AI_ACCESS_KEY = os.getenv('AWS_ACCESS_KEY') # or via .env
# Example: NIM / OpenAI-compatible in-database client
tdfs4ds.TERADATA_AI_API_TYPE = 'nim'
tdfs4ds.TERADATA_AI_MODEL_NAME = 'nvidia/nv-embedqa-e5-v5'
tdfs4ds.TERADATA_AI_API_BASE = 'https://nim.example.com/v1'
Supported TERADATA_AI_API_TYPE values: aws, azure, gcp, nim,
hugging_face, onnx. azure / gcp / hugging_face rely on the standard
provider env vars (e.g. AZURE_OPENAI_*, GOOGLE_APPLICATION_CREDENTIALS).
Incremental updates
The Enterprise Vector Store API does not yet guarantee per-id upsert, so
a non-force_rebuild build will try add_documents and fall back to a
destroy + rebuild if it is not available.
Performance knobs¶
tdfs4ds.INSTRUCT_MODEL_TIMEOUT = 60 # seconds per LLM call
tdfs4ds.INSTRUCT_MODEL_MAX_RETRIES = 3 # transient-error retries
For very small-context models (≤ 16 k tokens), cap the candidate generation length so the query optimizer does not exhaust its output budget on verbose reasoning text: