Skip to content

tdfs4ds.diagnostics

tdfs4ds.diagnostics

Configuration self-check for the AI-facing parts of tdfs4ds.

tdfs4ds.check_configuration() exercises each configured piece — Vector Store authentication, the Vector Store service itself, the instruct (LLM) model, and the embedding model — and reports, per check, whether it works. When something is missing or wrong it returns a concrete fix hint, in the same spirit as the prescriptive error messages used elsewhere in the package (_NOT_CONFIGURED_MSG, ACC_6881_HINT, logger_safe('error', …)): the point is to name the setting to change, not to hand back a traceback.

Design rules:

  • Never raises. Every check is individually wrapped; a check that blows up is reported as failed with its error text, and the remaining checks still run.
  • Never reveals a secret. Only environment-variable names, URLs, model names and booleans about presence ever reach the result or the printed output. Tokens, keys and passwords do not.
  • Reuses, never reimplements. The clients are built by the very same functions the rest of the package uses (build_llm, get_embeddings, get_vector_store_embedding, configure_vector_store_auth), so a check that passes means the real code path works — not merely that a parallel reimplementation of it does.

The Teradata database connection itself is not opened here: it is external to the platform and opened by the caller (in-cluster, typically tdml.create_context(host=…, logmech='JWT', logdata=…)). When a session is open, two checks use it (since 0.3.1.222): temporal (VALIDTIME) tables work for the user, and the session query band can be set and DBQL read back. Where a check needs a live session, a missing one surfaces as that check's hint.

logger module-attribute

logger = getLogger('tdfs4ds')

OK module-attribute

OK = 'ok'

FAILED module-attribute

FAILED = 'failed'

SKIPPED module-attribute

SKIPPED = 'skipped'

_STATUS_ICON module-attribute

_STATUS_ICON = {OK: '✅', FAILED: '❌', SKIPPED: '⏭️'}

_ASCII_ICON module-attribute

_ASCII_ICON = {OK: '[ OK ]', FAILED: '[FAIL]', SKIPPED: '[SKIP]'}

_HELLO module-attribute

_HELLO = 'hello world'

_MAX_ECHO module-attribute

_MAX_ECHO = 200

_LOAD_CONFIG_HINT module-attribute

_LOAD_CONFIG_HINT = "Call tdfs4ds.load_config() — these variables are set in the environment but never reached the module configuration. tdfs4ds reads TDFS4DS_* into tdfs4ds.<ATTR> once, during `import tdfs4ds`; anything exported (or loaded from a .env) AFTER the import is not picked up automatically. This also happens when the .env sits outside the process's working directory — pass tdfs4ds.load_config(dotenv_path='/path/to/.env'). Note the Vector Store auth variables are exempt: they are read live from os.environ, which is why that check can pass while these do not."

_UNSET module-attribute

_UNSET = object()

APPLIED module-attribute

APPLIED = 'applied'

UNLOADED module-attribute

UNLOADED = 'unloaded'

DIVERGED module-attribute

DIVERGED = 'diverged'

_AUTH_FALLBACK_HINT module-attribute

_AUTH_FALLBACK_HINT = "teradataml.set_auth_token() was reached but rejected the configuration. Re-check the values behind the variables above (URL reachable? token still valid — an injected OAuth/JWT access token expires?), and see the connect()/setup() log for the 'Vector Store auth ... failed' warning line, which carries the underlying reason."

_CREDENTIAL_REJECTED_HINT module-attribute

_CREDENTIAL_REJECTED_HINT = 'The service is reachable and enabled — it answered, then the DATABASE refused the credential it presented for you (SQLState 28000). Do NOT go looking at licensing. The usual cause is an EXPIRED token: teradataml stores a ready-made JWT without validating it (the validation call is commented out in its own source), so the auth check above and the health probe both pass with a stale one — this stage is the first that actually uses it. Re-read the current token into TDFS4DS_VECTOR_STORE_AUTH_AUTH_TOKEN and re-run setup()/connect(). If the credential is fresh, check the Basic username/password, then that the database user may log on the way the service logs on for it (set_auth_token requires the privilege to log in with a NULL password — see GRANT LOGON).'

_SERVICE_HINTS module-attribute

_SERVICE_HINTS = [(('28000', 'userid, password or account is invalid', 'invalid authorization'), _CREDENTIAL_REJECTED_HINT), (('502', '503', '504', 'bad gateway', 'gateway time'), 'Transient gateway error — the Vector Store REST gateway returned a raw 5xx. This usually clears within seconds; retry. tdfs4ds already retries this class internally (_retry_if_transient_gateway_error).'), (('no scheme supplied', 'invalid url', 'none/api'), "The Vector Store client has no base URL — session auth was never established. Fix the 'Vector Store auth' check above first (TDFS4DS_VECTOR_STORE_AUTH_BASE_URL + one credential group); everything else here depends on it."), (('not enabled', 'not licensed', '404', '<html'), 'The Vector Store REST endpoint answered, but not as a Vector Store service. Check that the Enterprise Vector Store feature is licensed and activated on this Vantage instance, and that BASE_URL points at the Vector Store service (in-cluster this is typically http://vectorstore.vectorstore.svc.cluster.local:8000).'), (('teradatagenai.vector_store', 'teradatagenai.collection', 'teradatagenai.'), "'teradatagenai' imports, but without its vector_store.collection module — so the build being imported is NOT the one you think. Check the origin reported above against the version you expect: in a notebook, `!pip freeze` resolves pip through PATH while the kernel imports from its own sys.executable, so they routinely disagree. Install into the running interpreter (`%pip install -U teradatagenai langchain-teradata` in a notebook, note the % not !), and check for a stray teradatagenai.py/directory shadowing it in the working directory."), (('teradatagenai',), "The 'teradatagenai' package is missing. Install it with `pip install teradatagenai` (it normally arrives transitively with 'langchain-teradata')."), (('connection', 'timed out', 'timeout', 'name or service not known', 'getaddrinfo'), 'Cannot reach TDFS4DS_VECTOR_STORE_AUTH_BASE_URL. Check the URL and that this process can route to it — a cluster-local service name only resolves from inside the cluster.')]

_SERVICE_FALLBACK_HINT module-attribute

_SERVICE_FALLBACK_HINT = "CollectionManager().health() failed. Verify TDFS4DS_VECTOR_STORE_AUTH_BASE_URL points at a running Vector Store service and that the auth check above passed — or set tdfs4ds.VECTOR_STORE_BACKEND = 'chroma' to use the bundled local index instead."

_REDIRECT_TRIGGERS module-attribute

_REDIRECT_TRIGGERS = ('error code: 301', 'error code: 302', 'error code: 303', 'error code: 307', 'error code: 308', 'model_dump', "'str' object has no attribute")

_REDIRECT_HINT module-attribute

_REDIRECT_HINT = "The endpoint did not answer as an OpenAI-compatible API: it either redirected (3xx) or returned a SUCCESS status with a non-JSON body, which the OpenAI SDK hands back as raw text for LangChain to choke on. Both mean something in front of the gateway — an ingress, an SSO login page, an error page — replied instead of the model. This is NOT a TLS or model problem. Check INSTRUCT_MODEL_URL's path (the SDK appends /chat/completions to it) and the redirect target reported above; if that target is a login page, the ingress wants its own credentials and a route that bypasses it (a cluster-local service URL) is the fix."

_INSTRUCT_HINTS module-attribute

_INSTRUCT_HINTS = [(_REDIRECT_TRIGGERS, _REDIRECT_HINT), (('certificate', 'certificate_verify_failed', 'ssl:', 'sslerror', 'self signed', 'self-signed'), "TLS verification failed — the endpoint's certificate is not trusted by the system CA store, which is normal for an internal host. Set tdfs4ds.INSTRUCT_MODEL_CA_BUNDLE to the issuing CA's PEM file (verification stays on), or as a last resort INSTRUCT_MODEL_VERIFY_SSL=False, which sends the API key over an unauthenticated connection."), (('401', '403', 'unauthorized', 'forbidden', 'invalid api key', 'incorrect api key'), 'The endpoint rejected the credential. Check tdfs4ds.INSTRUCT_MODEL_API_KEY (or TDFS4DS_INSTRUCT_MODEL_API_KEY) — for Bedrock this is the bearer token, and AWS credentials additionally come from AWS_PROFILE / ~/.aws / the IAM role.'), (('404', 'model not found', 'does not exist', 'unknown model', 'no such model'), 'The provider does not recognise tdfs4ds.INSTRUCT_MODEL_MODEL. Call tdfs4ds.list_instruct_models() to see what this endpoint actually serves.'), (('api_key client option', 'missing credentials', 'openai_api_key'), "No API key reached the client. For an OpenAI-compatible gateway set tdfs4ds.INSTRUCT_MODEL_API_KEY; if the gateway checks no key at all it still needs a non-empty value, because the OpenAI SDK refuses to build a client without one — write EMPTY (vLLM's convention). Note that 'none', 'null' and an empty string all load as Python None and will fail this way."), (('422', 'unprocessable', 'guided_json', 'response_format'), "Structured-output rejection (HTTP 422). A LiteLLM/Mistral-style gateway does not accept vLLM's guided_json. Set tdfs4ds.INSTRUCT_MODEL_PROVIDER = 'mistralai' (or 'openai-compatible') rather than 'vllm' for such a gateway."), (('the tool', 'at tool', 'tool name', 'toolspec', 'tools.0', 'invalid tool'), "The endpoint rejected the structured-output TOOL NAME, not the model's ability to produce typed output. On the function_calling path the Pydantic schema's class name is sent as the tool name, and Bedrock-backed routes require it to start with a letter and contain only letters, digits and underscores — a leading underscore is refused. Rename the schema class passed to with_structured_output() so it is letter-initial."), (('proxy', 'socks', 'tunnel'), "The outbound proxy failed. Check tdfs4ds.INSTRUCT_MODEL_PROXY — the scheme selects the protocol ('http://host:3128' or 'socks5://host:1080'); socks5 needs the httpx[socks] extra."), (('connection', 'timed out', 'timeout', 'name or service not known', 'getaddrinfo', 'connect call failed', 'max retries'), 'Cannot reach the model endpoint. Check tdfs4ds.INSTRUCT_MODEL_URL, and set tdfs4ds.INSTRUCT_MODEL_PROXY if outbound traffic must go through a proxy.'), (('region', 'could not connect to the endpoint url', 'no region'), "Region not resolved. Bedrock needs tdfs4ds.INSTRUCT_MODEL_REGION (e.g. 'us-east-1'); GCP Vertex needs INSTRUCT_MODEL_GCP_PROJECT + INSTRUCT_MODEL_GCP_LOCATION."), (('credential', 'could not automatically determine', 'default credentials'), 'Cloud credentials were not found. Bedrock: AWS_PROFILE / ~/.aws/credentials / IAM role. GCP: GOOGLE_APPLICATION_CREDENTIALS, or `gcloud auth application-default login`.'), (('langchain_community', 'langchain-community', 'chatlitellm'), "If tdfs4ds.INSTRUCT_MODEL_URL points at a LiteLLM PROXY (a server, typically :4000), set INSTRUCT_MODEL_PROVIDER='openai-compatible' instead of 'litellm' and keep the same URL, key and model: the proxy speaks the OpenAI API, so ChatOpenAI talks to it natively with no extra packages. The 'litellm' provider is for the other topology — the LiteLLM Python SDK in-process, no proxy, model strings like 'bedrock/amazon.nova-pro-v1:0' — and needs `pip install langchain-community litellm`, neither of which tdfs4ds depends on (recent langchain-community releases have also dropped ChatLiteLLM)."), (('no module named', 'import'), 'A provider package is missing — install the LangChain integration for tdfs4ds.INSTRUCT_MODEL_PROVIDER (openai/vllm/openai-compatible → langchain-openai, bedrock → langchain-aws, gcp → langchain-google-vertexai, mistralai → langchain-mistralai, litellm → langchain-community + litellm).')]

_INSTRUCT_FALLBACK_HINT module-attribute

_INSTRUCT_FALLBACK_HINT = 'Re-check tdfs4ds.INSTRUCT_MODEL_PROVIDER / _MODEL / _URL / _API_KEY (and _REGION for Bedrock, _GCP_PROJECT + _GCP_LOCATION for Vertex). Without a working instruct model, documentation, query optimization and the consumer agent are all unavailable.'

_EMBEDDING_HINTS module-attribute

_EMBEDDING_HINTS = [(('input should be a valid string',), 'The known tiktoken-array bug: langchain_openai sent token arrays instead of strings. Build embeddings only via tdfs4ds.agent.embedding.get_embeddings(), which sets check_embedding_ctx_length=False for non-OpenAI endpoints (TEI / vLLM / LiteLLM).'), (('401', '403', 'unauthorized', 'forbidden', 'invalid api key'), 'The endpoint rejected the credential. Check tdfs4ds.EMBEDDING_MODEL_API_KEY — it falls back to INSTRUCT_MODEL_API_KEY when unset, which is often not the right key for a separate embedding endpoint.'), (('404', 'model not found', 'does not exist', 'unknown model'), 'The provider does not recognise tdfs4ds.EMBEDDING_MODEL_MODEL. Call tdfs4ds.list_embedding_models() to see what this endpoint serves.'), (('teradatagenai',), "tdfs4ds.TERADATA_AI_API_TYPE is set but 'teradatagenai' is not installed. Either `pip install teradatagenai`, or unset TERADATA_AI_API_TYPE to fall back to LangChain embeddings."), (("passed to argument 'api_type'", 'invalid value(s)'), "The 'teradatagenai' being imported does not accept this TERADATA_AI_API_TYPE; its permitted values are listed in the error above. If that list is missing 'td_hosted' (and 'litellm'/'guardrails'), the imported build is older than it appears — compare the origin reported above with `pip freeze`, since in a notebook `!pip` and the kernel resolve to different environments. Install into the running interpreter (`%pip install -U teradatagenai`, % not !). Otherwise pick a listed value and supply that provider's credentials."), (('api_type', 'model_name', 'access_key', 'region'), "TeradataAI rejected its arguments. For api_type='td_hosted' only api_type and model_name are needed (no key, no region); 'aws' additionally needs TERADATA_AI_REGION + TERADATA_AI_ACCESS_KEY, 'nim' needs TERADATA_AI_API_BASE."), (('auth', 'token', 'set_auth_token'), "In-database embedding authenticates through the active Teradata session. Make sure tdml.create_context() succeeded and the 'Vector Store auth' check above passed."), (('certificate', 'certificate_verify_failed', 'ssl:', 'sslerror', 'self signed', 'self-signed'), "TLS verification failed for the EMBEDDING endpoint. Note the asymmetry: INSTRUCT_MODEL_VERIFY_SSL / _CA_BUNDLE are instruct-only by design, so there is no embedding equivalent to set - an embedding client is deliberately never affected by the instruct model's TLS posture. The fixes are to install the issuing CA in the system trust store, to point tdfs4ds.EMBEDDING_MODEL_URL at a route with a publicly trusted certificate, or to route it through tdfs4ds.EMBEDDING_MODEL_PROXY."), (('connection', 'timed out', 'timeout', 'getaddrinfo', 'max retries'), 'Cannot reach the embedding endpoint. Check tdfs4ds.EMBEDDING_MODEL_URL (it falls back to INSTRUCT_MODEL_URL) and tdfs4ds.EMBEDDING_MODEL_PROXY.')]

_EMBEDDING_FALLBACK_HINT module-attribute

_EMBEDDING_FALLBACK_HINT = 'Re-check tdfs4ds.EMBEDDING_MODEL_PROVIDER / _MODEL / _URL / _API_KEY / _DIM. Each falls back to the matching INSTRUCT_MODEL_* value when unset, which is a common source of surprise when the two models live behind different endpoints.'

_EXPIRED_TOKEN_HINT module-attribute

_EXPIRED_TOKEN_HINT = "This is a bearer token, and it has a lifetime — typically about two hours on AI Studio. Nothing is misconfigured: mint a fresh one and write it back into %s. In a notebook that is `%%gen_token` followed by `tdfs4ds.generate_ai_studio_env('.env', token=user_token)`, which refreshes every component that authenticates with it. If you would rather the file kept working, `auth='password'` does not expire."

_VECTOR_STORE_MIN_VERSION module-attribute

_VECTOR_STORE_MIN_VERSION = (2, 1, 11)

_OLD_SERVICE_HINT module-attribute

_OLD_SERVICE_HINT = "The Teradata Vector Store SERVICE (not the teradatagenai client package) is version {found}; tdfs4ds needs {minimum} or newer. This is a server-side component of the Vantage system — upgrading it is a platform/admin action, not a pip install, so ask whoever administers this instance to update the Vector Store service. Expect indexing to misbehave until then (build_vector_index may fail or silently index nothing) even though the service is up and your credential is accepted. To keep working meanwhile, set tdfs4ds.VECTOR_STORE_BACKEND = 'chroma' for a local index."

_AWS_DENIED module-attribute

_AWS_DENIED = ('accessdenied', 'access denied', 'not authorized to perform', 'is not authorized', 'explicit deny', 'forbidden')

_AWS_NO_IDENTITY module-attribute

_AWS_NO_IDENTITY = ('unable to locate credentials', 'nocredentialserror', 'no credentials', 'could not automatically determine', 'unrecognizedclientexception', 'invalidsignatureexception', 'security token included in the request is invalid', 'expiredtoken', 'signature we calculated does not match')

_TRANSPORT_EXPIRY_DAYS module-attribute

_TRANSPORT_EXPIRY_DAYS = 30

_TRANSPORT_HINTS module-attribute

_TRANSPORT_HINTS = [(('mismatch',), 'The certificate is valid but was issued for a different host, so the name in tdfs4ds.INSTRUCT_MODEL_URL is not the one the endpoint answers for. Use the hostname the certificate names (reported above) rather than an IP address or an internal alias.'), (('expired',), "The endpoint's certificate has EXPIRED. Nothing in tdfs4ds can work around that -- it has to be renewed on the server. Until it is, tdfs4ds.INSTRUCT_MODEL_VERIFY_SSL=False lets calls through, at the cost of sending the API key to an unauthenticated peer."), (('untrusted',), "The certificate is not trusted by the CA store in use, which is normal for an internal host. Set tdfs4ds.INSTRUCT_MODEL_CA_BUNDLE to the issuing CA's PEM file -- verification stays ON, which is the difference between this and INSTRUCT_MODEL_VERIFY_SSL=False."), (('unreachable',), 'Nothing answered on the TLS port. Check the host and port in tdfs4ds.INSTRUCT_MODEL_URL, and whether this machine has egress to it (in-cluster a service URL usually works where the public ingress does not).')]

_PROBE_SCHEMA module-attribute

_PROBE_SCHEMA = {'type': 'object', 'properties': {'colour': {'type': 'string'}, 'count': {'type': 'integer'}}, 'required': ['colour', 'count']}

_PROBE_QUESTION module-attribute

_PROBE_QUESTION = 'The box is blue and holds 7 items. Reply with JSON: the colour and the count.'

_MCP_DEFAULT_QUESTION module-attribute

_MCP_DEFAULT_QUESTION = 'List the databases available. Answer with the database names only.'

_LANGCHAIN_PACKAGES module-attribute

_LANGCHAIN_PACKAGES = ('langchain-core', 'langgraph', 'langchain-mcp-adapters', 'langchain-openai')

_MCP_HINTS module-attribute

_MCP_HINTS = [(("no module named 'langchain", "no module named 'langgraph"), 'The langchain packages of this Python do not match (typically langchain-core 0.x under 1.x packages; tdfs4ds needs langchain-core 1.x). Run `%pip install -U langchain-core langgraph langchain-mcp-adapters langchain-openai` in the Python that runs the agent, then restart it.'), (('8017', 'userid, password', 'password or account', 'logon', 'not authorized'), "The MCP server reached Teradata and the DATABASE rejected a logon (Error 8017) — so the URL, TLS and the Authorization header are all fine. WHICH credential was refused depends on what was sent, and the check's detail line above says which. With BASIC, it forwarded tdfs4ds.MCP_USERNAME/_PASSWORD (these take precedence over TDFS4DS_TD_USERNAME/_PASSWORD); compare them against a credential you know works, since a value that merely LOOKS right can be a leftover '<set-this>' placeholder, or carry a trailing .env comment or stray whitespace. With CUSTOM HEADERS (a Bearer token), the token was ACCEPTED — an invalid one is refused earlier and differently, as 'Invalid JWT token' — so this 8017 is the SERVER's own database logon on your behalf. tdfs4ds sent no password, so no tdfs4ds setting will fix it: the gateway itself has to be able to log the token's identity on to Teradata. Try Basic with a known-good password to see whether the server accepts that route instead. Either way, a credential that works with tdml.create_context(logmech='TD2') can still fail here if the server logs on with a different mechanism (LDAP), since the same name and password are then checked against a different directory."), (('401', '403', 'unauthorized', 'forbidden', 'authentication'), 'The MCP server rejected the credentials. For the Enterprise edition set tdfs4ds.MCP_SERVER_EDITION=\'enterprise\' (which sends HTTP Basic built from TDFS4DS_TD_USERNAME/_PASSWORD) and override with TDFS4DS_MCP_USERNAME/_PASSWORD if the MCP account differs from the database one. For any other scheme set TDFS4DS_MCP_SERVER_HEADERS to the exact headers, e.g. {"Authorization": "Bearer ..."}.'), (('certificate', 'ssl', 'tls', 'self signed', 'self-signed', 'certificate_verify_failed'), "TLS verification failed — the server's certificate is not trusted by the system CA store, which is normal for an internal host. Point tdfs4ds.MCP_SERVER_CA_BUNDLE at your internal CA's PEM file. (Verification is never disabled; supplying the trust anchor is the fix.)"), (('connect', 'getaddrinfo', 'name or service not known', 'refused', 'unreachable', 'max retries'), 'Cannot reach tdfs4ds.MCP_SERVER_URL. Check the URL and that this process can route to it — a cluster-local service name only resolves from inside the cluster.'), (('timeout', 'timed out'), 'The MCP server did not answer in time. Raise tdfs4ds.MCP_TIMEOUT, or check whether the server is healthy.'), (('404', '405', '406', 'not found', 'not acceptable', 'method not allowed', 'text/event-stream', 'missing session', 'session id'), "The endpoint answered, but not as an MCP endpoint for this transport. Switch tdfs4ds.MCP_SERVER_TRANSPORT between 'streamable_http' and 'sse' (a server usually serves one of them, sometimes on different paths) and check the URL path."), (('langchain_mcp_adapters', 'no module named'), 'The MCP client library is missing: `pip install langchain-mcp-adapters`.')]

_MCP_FALLBACK_HINT module-attribute

_MCP_FALLBACK_HINT = "Check tdfs4ds.MCP_SERVER_URL, MCP_SERVER_TRANSPORT ('streamable_http' or 'sse') and MCP_SERVER_EDITION. MCP is optional — the consumer agent works without it, minus the data-query skills."

_MCP_TOOLCALL_HINTS module-attribute

_MCP_TOOLCALL_HINTS = [(('contextwindowexceeded', 'context window', 'context length', 'maximum context', 'too many tokens', 'prompt is too long'), "The tool SCHEMAS alone overflow the model's context window before your question is even considered — the Enterprise MCP edition exposes ~132 tools, several times what the Community one does. tdfs4ds caps how many are bound via tdfs4ds.MCP_MAX_TOOLS (keeping the ones most relevant to the question); lower it, or configure an instruct model with a larger context window. This is NOT a tool-calling support problem."), (('tool', 'function', 'tool_choice', 'tools', '400', 'unsupported'), 'The MCP server is reachable and its tools were listed, so this is the MODEL side: the configured instruct model (or the gateway in front of it) does not support tool calling. Check tdfs4ds.INSTRUCT_MODEL_MODEL — behind a LiteLLM proxy, tool support depends on the underlying model, not on the proxy.'), (('401', '403', 'unauthorized', 'invalid api key'), 'The model endpoint rejected the credential during the tool probe — check tdfs4ds.INSTRUCT_MODEL_API_KEY. (The MCP server itself answered fine.)')]

_MCP_TOOLCALL_FALLBACK_HINT module-attribute

_MCP_TOOLCALL_FALLBACK_HINT = 'The MCP tools were listed successfully but the model could not complete a tool-calling round trip. That isolates the problem to the model side, not to MCP connectivity or authentication.'

_NO_CONTEXT_HINT module-attribute

_NO_CONTEXT_HINT = 'Open the database session first — tdml.create_context(...) or tdfs4ds.connect(...) — then run the check again.'

_BROKEN_SESSION_HINT module-attribute

_BROKEN_SESSION_HINT = "The database never answered: the error comes from this client's session, which is broken (typically concurrent use of teradataml's single connection, or a reconnect under it), so this check says nothing about the feature. Reconnect (tdfs4ds.reconnect_teradata(), or the app's Connect page) and run the check again."

_BROKEN_SESSION_MARKERS module-attribute

_BROKEN_SESSION_MARKERS = ("object has no attribute 'cursor'", 'is not a valid connection pool handle')

_CHECKS module-attribute

_CHECKS = (_check_config_loaded, _check_database_temporal, _check_database_query_band, _check_vector_store_auth, _check_vector_store_service, _check_llm_transport, _check_instruct_model, _check_json_constraints, _check_embedding_model, _check_mcp_server, _check_tracing)

_SELFTEST_HINT module-attribute

_SELFTEST_HINT = "The Vector Store answered every read-only probe but failed on the write path. The per-step breakdown is under this check's 'selftest' key, and a standalone reproducer containing NO credentials is at report['checks'][...]['selftest']['repro'] — write it to a file and attach it to a support ticket. If the service version above is below the supported minimum, upgrade it first: that is a platform/admin action."

_SELFTEST_OPT_IN_HINT module-attribute

_SELFTEST_OPT_IN_HINT = 'Run it with tdfs4ds.check_configuration(selftest=True), or on its own with tdfs4ds.selftest_vector_store(). It is opt-in because it is the only check that WRITES: it creates a throwaway table and vector-store collection, embeds a few sentences, retrieves them, changes one, refreshes, and drops both again. The checks above are all read-only, and a read-only probe cannot see the write-path failures that actually break indexing.'

_ASCII_TRANSLITERATION module-attribute

_ASCII_TRANSLITERATION = maketrans({'—': '-', '–': '-', '…': '...', '→': '->', '’': "'", '‘': "'", '“': '"', '”': '"'})

_result

_result(name, label, status, detail='', hint=None, error=None, elapsed_s=None)

_flatten_exception

_flatten_exception(exc)

Render an exception, unwrapping ExceptionGroup down to its leaves.

An ExceptionGroup stringifies as "unhandled errors in a TaskGroup (1 sub-exception)" — a count, not a cause — and the real error is only reachable through .exceptions. Anything running under an anyio task group (the MCP client, notably) raises one, so without this every distinct failure — expired certificate, 401, wrong transport, DNS — collapses into the same uninformative string and no hint can match it.

Leaves are rendered as Type: message so the class name is available for matching too (SSLCertVerificationError carries the diagnosis even when its message does not).

Group detection is duck-typed rather than isinstance(exc, BaseExceptionGroup): that builtin only exists from Python 3.11, and on older interpreters anyio raises the exceptiongroup backport's own class instead.

_short

_short(text, limit=_MAX_ECHO)

First line of text, truncated — errors are often multi-page.

Exceptions are flattened first, so a task-group wrapper never hides the cause from the caller or from :func:_match_hint.

_match_hint

_match_hint(error_text, table, fallback=None)

Return the first hint in table whose trigger substrings appear in error_text.

table is a list of (triggers, hint) where triggers is a tuple of lowercase substrings — any one matching is enough. An unrecognised error still gets fallback, so a failure is never reported without direction.

_backend

_backend()

_package_origin

_package_origin(package)

Report where package actually imports from, and under which interpreter.

This is the evidence that separates "installed but too old" from "a different environment than the one you inspected" — two failures that look identical in the traceback and have opposite fixes. Confirmed live: a notebook reported teradatagenai==20.0.0.7 via !pip freeze (a version that does support td_hosted and ships vector_store.collection) while its kernel imported an older build lacking both, because !pip resolves through PATH and the kernel through its own sys.executable.

_is_empty

_is_empty(value)

Is value an attribute that was never populated?

Deliberately NOT not value: False and 0 are legitimate, loaded configuration values — every _BOOL_VARS member set to false (DISPLAY_LOGS, DEBUG_MODE, USE_VOLATILE_TABLE, BUILD_DATASET_AT_UPLOAD, INSTRUCT_MODEL_VERIFY_SSL) plus EMBEDDING_BATCH_DELAY=0 and SCHEDULING_MAX_WORKERS=0. A truthiness test reports all of those as never applied, which is how this function came to exist.

_env_attr_status

_env_attr_status()

Yield (env_var_name, status) for every TDFS4DS_* set in the environment, comparing it against the module attribute it should have populated.

Status is one of :data:APPLIED, :data:UNLOADED or :data:DIVERGED, and the comparison is against the coerced expected value — _coerce is what load_config itself applies, so 'false' is compared as False rather than as a non-empty string.

A variable whose coercion fails is skipped: nothing can be said about what the attribute should have become.

_unloaded_env_vars

_unloaded_env_vars(*attrs)

Return the TDFS4DS_* variable names that are set in the environment but whose module attribute is still empty.

This is the load-ordering trap: module attributes are a snapshot taken at import, so a variable exported afterwards is invisible to every check that reads tdfs4ds.<ATTR> — while the Vector Store auth check, which reads os.environ directly, still sees it. Reporting "not configured" for a variable the user demonstrably set would send them looking in the wrong place.

With no attrs, scans every variable config.py accepts.

_diverged_env_vars

_diverged_env_vars()

Return the TDFS4DS_* names whose environment value differs from a module attribute that does hold something.

Distinct from :func:_unloaded_env_vars, and reported without failing the run: a variable can legitimately differ because the user overrode the attribute in-session on purpose. But it also catches the quieter half of the load-ordering trap — an attribute sitting at its default (say VECTOR_STORE_BACKEND='chroma', or INSTRUCT_MODEL_VERIFY_SSL=True) while the environment asks for something else is not "unset", so nothing else would notice.

Names only, never values: this set includes credential variables.

_check_config_loaded

_check_config_loaded(live)

Catch the load-ordering trap before any check blames a missing setting.

Runs first, because every check below reads tdfs4ds.<ATTR>: if the environment never made it into the module config, they all report "not configured" about variables the user did set.

_token_state

_token_state(token)

(state, note) for a bearer token, never raising.

Thin wrapper so every check words expiry the same way and none of them has to import the reader. See :mod:tdfs4ds.utils.tokens for why the signature is not verified.

_check_vector_store_auth

_check_vector_store_auth(live)

_check_vector_store_service

_check_vector_store_service(live)

_health_version

_health_version(health)

Pull the service version out of a health() payload, tolerating shape.

health(return_type='json') has been observed as a mapping with a version key and as a one-row list of such mappings (it also renders as a one-row table of status/platform/commit/version). Anything unrecognised returns None, which is reported as "not verified" rather than failing the check — a diagnostic must not invent a problem out of a payload shape it does not recognise.

_parse_version

_parse_version(text)

'2.0.5' -> (2, 0, 5). None when text holds no leading numeric version, so an unparseable value never fails the check.

_version_str

_version_str(parts)

_collection_count

_collection_count(collections)

Describe the list() result without assuming its shape.

The return type varies with the client build and return_type; the count is a nicety, the point of the call is that it did not raise.

_safe_url

_safe_url(url)

Strip credentials and query string from url before it is displayed.

An SSO redirect carries tokens in its query string, and a base URL can carry userinfo — neither belongs in a report.

_is_normalisation

_is_normalisation(target, location)

True when a redirect only tidies the URL rather than diverting it.

A gateway commonly 301s /v1 to /v1/, and http to https. Calling that "interception" would raise an alarm on a perfectly healthy endpoint — which matters now that this probe runs on every check rather than only after an error had already implicated a redirect.

Only scheme, host and path are compared, each normalised for a trailing slash. A same-host redirect to a DIFFERENT path (/chat/completions -> /login) is not normalisation and is still reported.

_redirect_probe

_redirect_probe(timeout=10.0)

Describe what actually answers at INSTRUCT_MODEL_URL, or None.

Sends no credential: the point is to see whether something in front of the gateway intercepts the request, and an unauthenticated GET is enough for that. Redirects are deliberately NOT followed, so the Location is visible rather than the page it leads to.

Honours the configured proxy and TLS settings, so it exercises the same path the model call took. Never raises — a probe that crashes the diagnostic would be worse than no probe.

_instruct_failure

_instruct_failure(name, label, described, exc, live)

Build the FAILED result for an instruct-model error.

The redirect probe used to run from here, gated on this error's hint being the redirect one. It no longer does: _check_llm_transport runs it unconditionally and earlier, so by the time this is reached the report already says whether something intercepted the request — and running it again would ask the same question twice per failing run.

_bedrock_auth_hint

_bedrock_auth_hint(text)

A hint that tells a ROLE user what to fix, instead of asking for a key.

The generic 401/403 hint says "check INSTRUCT_MODEL_API_KEY", which is wrong advice for the very setup this exists for: an IAM role has no key to check. Only fires for the Bedrock routes, so every other provider's message is unchanged. None otherwise.

_certificate_summary

_certificate_summary(cert)

Pull subject CN, issuer CN and expiry out of a parsed certificate dict.

getpeercert() returns this shape only for a certificate the handshake actually validated, which is why the untrusted path below has to work harder to say anything at all.

_peek_certificate

_peek_certificate(host, port, timeout)

Read an UNTRUSTED certificate's identity, best effort.

Needed because the useful sentence for an internal endpoint is "issued by , which the system store does not carry" -- and once verification has failed, getpeercert() returns nothing.

ssl._ssl._test_decode_cert is the only certificate parser in the standard library (the public surface exposes PEM/DER conversion but no parse), so this uses it deliberately and defensively: it is private, and every failure here degrades to reporting the verification error alone. Naming the issuer is a convenience, never the diagnosis.

_tls_probe

_tls_probe(url, timeout=10.0)

Describe what TLS the instruct endpoint presents. Never raises.

Uses the configured trust (INSTRUCT_MODEL_CA_BUNDLE, or the system store) so the verdict is about the connection tdfs4ds really makes, not an idealised one.

Returns a dict whose status is one of:

ok Verified. Carries the certificate identity and days to expiry -- the thing nothing in this package reported before, and the one failure that is entirely predictable in advance. insecure Handshake succeeded with verification switched off. untrusted / mismatch / expired Verification failed, split by cause because the fixes differ: a trust anchor, the URL's host, or a renewal. unreachable Nothing answered on the TLS port. plain http:// -- no TLS at all. skipped Not probed; reason says why (no URL, or a proxy is in the path).

_check_llm_transport

_check_llm_transport(live)

What the network does to an instruct-model call, before any credential.

Runs FIRST among the model checks so its answer frames theirs: a TLS failure, an expiring certificate or an ingress answering instead of the gateway all surface here as themselves, rather than as a puzzling model error two checks later.

Deliberately credential-free. It also absorbs the redirect probe, which used to run only when an error message had already matched the redirect hint -- and which silently discarded TLS errors, so the one thing it could have reported about a certificate never appeared.

Scoped to the instruct endpoint. INSTRUCT_MODEL_VERIFY_SSL / _CA_BUNDLE reach only the OpenAI-family clients; Bedrock and Vertex constrain and secure their transport through their own SDKs.

_resolve_proxy_name

_resolve_proxy_name()

Name the variable a configured proxy came from, never its URL.

A proxy URL can carry credentials in its userinfo, and the variable name is what the reader needs in order to change it.

_check_instruct_model

_check_instruct_model(live)

_probe_documentation_constraint

_probe_documentation_constraint(llm)

Exercise the documentation path's structured output, once.

This path is entirely separate from the consumer agent's. It branches on INSTRUCT_MODEL_PROVIDER to choose a wire parameter — guided_json for vllm, response_format for the OpenAI family, format= for Ollama — and, if the backend refuses that parameter, retries once unconstrained. So a provider whose syntax this endpoint rejects leaves documentation running on prompt-guided JSON, at strictly lower reliability, with nothing anywhere reporting it. Nothing probed it until now.

Returns {"status", "mechanism", "detail", "error"} where status is enforced / relayed / degraded / unconstrained / error.

_pin_settings

_pin_settings(recommend, pin)

Apply recommend to the session and, when asked, to a .env.

Nothing is changed unless pin asks for it. A diagnostic that silently rewrote configuration would be a trap: INSTRUCT_MODEL_PROVIDER in particular flows on into export_config and into a deployed pod's config.yaml, so a reader running a check would be shipping a value they never chose.

Never raises: a check that cannot record its finding must still report it.

_tool_calling_verdict

_tool_calling_verdict(agent, docs, sweep)

What is actually known about TOOL CALLING on this endpoint.

Returns a short sentence, or None when nothing measured it — an unprobed capability must not be reported as either working or broken.

Evidence in order of directness: the wire sweep asked for a tool call explicitly; failing that, either path having used tool calling is proof; the agent's fallback list is the weakest but still real.

_check_json_constraints

_check_json_constraints(live, pin=False)

Can this endpoint return typed JSON, and by which syntax?

Answering a plain prompt proves nothing about it. The failure this exists for happens only on the typed path: the consumer agent's very first step is intent classification, and it dies on a gateway that relays the requested response format without enforcing it while every other check stays green. Same split as the MCP check's two stages — listing tools proves nothing about calling them.

Two independent paths are probed, because tdfs4ds has two, keyed on different settings:

  1. the agent + query optimizer path, langchain's with_structured_output (STRUCTURED_OUTPUT_METHOD);
  2. the documentation path, a provider-branched wire parameter (INSTRUCT_MODEL_PROVIDER), whose silent unconstrained fallback is exactly the kind of degradation a green report used to hide.

Only if one of those reports trouble does it run the nine-request wire-level sweep (:func:tdfs4ds.probe_json_constraints), which identifies the serving stack from the accept/reject pattern. A healthy endpoint pays nothing for it.

_embedding_hint

_embedding_hint(text, provider, model)

The embedding hint, plus the one mistake a Bedrock user makes by default.

EMBEDDING_MODEL_MODEL defaults to an OpenAI model name, and the embedding provider falls back to the instruct provider - so choosing Bedrock for the LLM silently asks Bedrock for text-embedding-3-small.

_check_embedding_model

_check_embedding_model(live)

langchain_mismatch_hint

langchain_mismatch_hint(error_text) -> Optional[str]

The fix for No module named 'langchain_core.messages.content' and the like: the langchain packages of this Python are from different generations (a 0.x langchain-core under 1.x packages). Names the versions installed; None when the error is not a missing langchain module.

_run_coroutine

_run_coroutine(factory, timeout)

Run an async factory to completion from sync code, notebook included.

Mirrors the bridge in skill_mcp_tools: a worker thread owns the event loop, so this works whether or not the caller already has one running (Jupyter always does).

_check_mcp_server

_check_mcp_server(live, mcp_question=None)

_mcp_tool_probe

_mcp_tool_probe(question, timeout)

Ask question through a minimal agent over the MCP tools.

Returns (answer, tools_called).

Deliberately NOT routed through consumer_agent: that agent answers business questions about the feature store, so a server-administration question would test its classifier rather than the MCP tool path, and could fail for reasons unrelated to MCP. This wants the narrowest circuit — model → tool call → answer — built with the same create_react_agent the real skill uses.

_db_context

_db_context()

The active teradataml context, or None (never raises).

_db_sql

_db_sql(sql)

_db_user

_db_user()

_never_reached_database

_never_reached_database(exc) -> bool

The failure is a broken client session (matched on the messages seen live, never on the absence of a database marker: an unknown error stays the feature's).

_check_database_temporal

_check_database_temporal(live)

Temporal (VALIDTIME) tables work for this user, in this session.

Every catalog and storage table of tdfs4ds is a VALIDTIME table. Proven with a session-only volatile table (no perm space, nothing left behind): create it, insert a row, read it CURRENT and NONSEQUENCED, drop it.

_check_database_query_band

_check_database_query_band(live)

The session query band can be set, and DBQL can be read back.

run() tags every query of a process execution with a query band so the follow-up rows can be joined to DBQL (tdfs4ds.followup_resource_usage). That needs (1) SET QUERY_BAND — checked by setting a pair, reading it back and restoring the session band exactly; (2) SELECT on DBC.QryLogV; (3) DBQL logging for this user. Only (1) can fail the check: without (2) or (3) runs work, the join is just empty — reported with the grant / logging statement to ask for.

_check_tracing

_check_tracing(live)

Agent tracing (Opik): off, or on and able to build a tracer.

Never sends anything and never shows a setting's value: it reports whether opik is importable and whether its own configuration loads, and to which kind of target (cloud / self-hosted) it would send.

_check_vector_store_selftest

_check_vector_store_selftest(live, requested=False)

Wrap :func:selftest_vector_store as a check row.

Always present in the report, even when not requested. An earlier version appended the row only when selftest=True, which made the feature undiscoverable: a passing report gave the reader no way to learn that everything it just verified was read-only, nor that a deeper check existed. Opt-in is about not writing without being asked — not about hiding.

Deliberately thin otherwise: the self-test owns its own never-raises and cleanup contracts, and its detailed report travels intact under selftest rather than being flattened into one line.

check_configuration

check_configuration(*, live=True, display=True, mcp_question=None, selftest=False, pin=False, progress=None)

Validate the tdfs4ds AI configuration and report what to fix.

Runs eight checks, in dependency order (plus an opt-in ninth, selftest):

  1. Config load — every check below reads tdfs4ds.<ATTR>, which config.py populates once during import tdfs4ds. A variable exported after that import (or a .env outside the working directory) never reaches the module config, so this reports which TDFS4DS_* variables are set in the environment but not applied — otherwise every check below blames a setting the user did make. 0b. Temporal (VALIDTIME) — with an open database session: a session-only VALIDTIME table is created, written, read CURRENT and NONSEQUENCED, and dropped. Every tdfs4ds table is temporal. 0c. Query band & DBQL — the session query band can be set (and the caller's band is restored), and DBQL can be read with logging active for this user: what followup_resource_usage needs to join process executions to their resource usage. Only a refused SET QUERY_BAND fails; missing DBQL access or logging is reported with the statement to ask the DBA for.
  2. Vector Store auth — resolves and applies teradataml.set_auth_token from the TDFS4DS_VECTOR_STORE_AUTH_* variables (JWT / PAT / Basic / OAuth). Skipped when nothing is configured and the backend is not 'teradata'.
  3. Vector Store service — CollectionManager().health() (liveness), then .list() (the first call that actually spends the credential), then the service version against :data:_VECTOR_STORE_MIN_VERSION: an older service is up and accepts the credential, and only misbehaves once something writes, so the version is the one place that is visible in advance. Only when VECTOR_STORE_BACKEND='teradata'.
  4. LLM transport (TLS) — credential-free, and before the model on purpose: completes a TLS handshake with INSTRUCT_MODEL_URL using the configured trust, reports the certificate's identity and days to expiry, separates a hostname mismatch from an untrusted issuer from an expired certificate (three different fixes), and says whether something in front of the gateway answers instead of it. Instruct-only, because INSTRUCT_MODEL_VERIFY_SSL / _CA_BUNDLE are.
  5. Instruct model — builds the client with build_llm() and sends a hello-world prompt: can it answer at all.
  6. JSON constraints — typed output, which a plain reply proves nothing about. Probes both paths, because they are independent: the agent / query-optimizer path (STRUCTURED_OUTPUT_METHOD) and the documentation path (INSTRUCT_MODEL_PROVIDER), whose silent retry unconstrained is invisible to every other check. Only if one reports trouble does it run the nine-request wire-level sweep that identifies the serving stack; the full sweep travels under the check's json_sweep key. See pin to record the answer.
  7. Embedding model — with the Teradata backend and TERADATA_AI_API_TYPE set, checks only that TeradataAI(...) instantiates (in-database embedding needs nothing more from us); otherwise embeds a hello-world string and verifies the returned vector length against EMBEDDING_MODEL_DIM.
  8. MCP server (when MCP_SERVER_URL is set) — two stages: list the tools (proving reachability, auth and TLS), then drive them with a minimal agent (proving the instruct model can actually call tools, which listing them does not). A failure in the second stage isolates the problem to the model side.

  9. Vector Store self-test (selftest=True only) — the checks above are all read-only, and every Vector Store failure this package has hit in the field is invisible to a read-only probe. This one writes: it embeds a handful of sentences into a throwaway collection, retrieves them, changes one, refreshes, retrieves again, and cleans up — see :func:tdfs4ds.selftest_vector_store, whose full per-step report and support reproducer are returned under the check's selftest key.

The Teradata database connection is not opened here — it is external to the platform and owned by the caller; the two database checks are skipped (with a hint) when no session is open.

Parameters

live : bool, default True Make the outbound calls (auth handshake, health probe, one LLM prompt, one embedding). False builds the clients and resolves the configuration but calls nothing — for CI or an offline sanity pass. display : bool, default True Print an aligned summary, with the fix hint under each check that did not pass. mcp_question : str, optional Question sent to the MCP tool-calling probe. Defaults to asking for the list of available databases — answerable by both the Community and Enterprise editions. The agent's tool guards apply to a custom question too, so a probe reaching for feature-store internals is refused exactly as it would be in the agent. selftest : bool, default False Run the write-path Vector Store self-test as a ninth check. Off by default because it is the only part of this function that creates and drops database objects; everything else is read-only. Ignored when live=False or the backend is not 'teradata'. pin : bool or str, default False Record what the JSON-constraints check discovers. False reports the setting and changes nothing. True applies it to the session and writes it into the first .env on config.py's search path via :func:tdfs4ds.patch_dotenv, stamped with the endpoint and date it was probed against — so the value is not re-derived every session, and a stale pin is visible rather than silent. A string pins into that file instead.

Worth knowing before using it: ``STRUCTURED_OUTPUT_METHOD`` is
classified as ordinary config by the AgentOps packager, so a pinned
value travels into a deployed pod's ``config.yaml`` — right when the
pod shares this gateway, wrong when it does not.
Returns

dict {"ok": bool, "version": str, "live": bool, "checks": [...]} where each check is {"name", "label", "status", "detail", "hint", "error"} and status is 'ok' / 'failed' / 'skipped'. ok is True when no check failed (a skipped check does not fail the run).

progress(event, data) (optional) is called with "start" and {"name", "label", "index", "total"} before each check, then with "result" and the check's row once it is done, so a UI can show each row as it arrives instead of waiting for the slowest check. A failing hook is logged and ignored: the report never depends on it.

Notes

Never raises, and never returns or prints a secret — only variable names, URLs, model names and presence flags.

Examples

import tdfs4ds report = tdfs4ds.check_configuration() # doctest: +SKIP report["ok"] # doctest: +SKIP True tdfs4ds.check_configuration(live=False) # doctest: +SKIP

_stream_handles_unicode

_stream_handles_unicode(stream=None)

True when stream can encode the emoji + typography this module emits.

_print_report

_print_report(report, stream=None, title=None)

Print the summary, degrading rather than raising on any console.

Displaying a report must never be the thing that breaks a diagnostic run. A console that cannot encode the emoji/typography (a legacy Windows code page) gets ASCII markers and transliterated punctuation instead of a UnicodeEncodeError; anything still un-encodable after that — odd bytes echoed back inside a provider's error text, say — is replaced, not raised.

title names the report; it defaults to this module's own, so existing callers are unaffected. It exists so the AgentOps deployment checks (:mod:tdfs4ds.agentops.preflight, :mod:tdfs4ds.agentops.smoke) render through this formatter instead of growing a second copy of it.

_format_report

_format_report(report, icons=None, title=None)

Render report as an aligned plain-text summary (also used by the UI).