This note documents the client/runtime patches we used in addition to the downloaded Nemotron Ultra Deep Agents harness profile.
The harness profile itself is registered with:
--harness-profile nemotron-ultra-downloadedThat profile contributes the system-prompt suffix, read_file tool-description override, and middleware stack, including:
NemotronProgressBudgetMiddlewareDomainToolPreferenceMiddlewareFilesystemRequestNudgeMiddlewareConversationTransitionNudgeMiddlewareActionCommitNudgeMiddlewareToolChainCompletionNudgeMiddlewareDomainToolCompletionNudgeMiddlewareReadFileContinuationNoticeMiddlewareToolRetryMiddlewareNemotronToolCallShimModelRateLimitRetryMiddlewareNemotronTextToolCallParserFollowupDisciplineMiddlewareEntityResolutionGuardMiddlewareFinalAnswerGuardMiddleware
The patches below are separate compatibility fixes for the ChatNVIDIA / NVIDIA Inference Hub client path.
The external LC profile runs used a different OpenAI-compatible client/provider path. Our local TME runs used ChatNVIDIA, which had a few behavior differences that affected Deep Agents evals:
- Standard LangChain
AIMessage.tool_callscould be present without equivalent OpenAI-formatadditional_kwargs["tool_calls"], so ChatNVIDIA could fail to send prior assistant tool calls back in the expected shape. ToolMessage.namewas not preserved in the serialized payload.- Non-assistant messages with
content=Nonecould reach the NVIDIA client validators/API in an invalid shape. - The Ultra model factory defaulted to
max_tokens=32768; for this eval path we wanted no explicit output cap from ChatNVIDIA and relied on the model/profile context settings instead. - Transient provider errors from the Inference Hub path, especially
504 Gateway Time-outfrom judge/user-simulator calls, could fail a test before the agent under evaluation did useful work. - The Inference Hub aliases require a LiteLLM virtual key with the
sk-prefix. The launcher now fails fast if the shell is accidentally using a non-sk-NVIDIA/NGC key.
The guarded launcher applies these patches before pytest starts:
from langchain_core.messages import AIMessage, ToolMessage
from langchain_core.messages.utils import _convert_to_openai_tool_calls
import langchain_nvidia_ai_endpoints._utils as nvidia_utils
import langchain_nvidia_ai_endpoints.chat_models as nvidia_chat_models
from tests.evals import nvidiahub_models
original_convert = nvidia_utils.convert_message_to_dict
def patched_convert_message_to_dict(message):
message_dict = original_convert(message)
# Preserve ToolMessage.name for tool-result messages.
if isinstance(message, ToolMessage) and getattr(message, "name", None):
message_dict.setdefault("name", message.name)
# Preserve standard LangChain AIMessage.tool_calls by converting to
# OpenAI-compatible tool_calls when additional_kwargs did not already carry it.
if (
isinstance(message, AIMessage)
and "tool_calls" not in message_dict
and getattr(message, "tool_calls", None)
):
message_dict["tool_calls"] = _convert_to_openai_tool_calls(message.tool_calls)
if message_dict.get("content") == "":
message_dict["content"] = None
# NVIDIA/OpenAI-compatible APIs do not accept null content for non-assistant messages.
if message_dict.get("role") != "assistant" and message_dict.get("content") is None:
message_dict["content"] = "null"
return message_dict
nvidia_utils.convert_message_to_dict = patched_convert_message_to_dict
nvidia_chat_models.convert_message_to_dict = patched_convert_message_to_dict
original_make_model = nvidiahub_models.make_nvidiahub_chat_model
def patched_make_nvidiahub_chat_model(alias):
model = original_make_model(alias)
if alias == "ultra-tme":
model.max_tokens = None
return model
nvidiahub_models.make_nvidiahub_chat_model = patched_make_nvidiahub_chat_modelWe also wrap ChatNVIDIA._generate with a bounded retry for transient provider failures:
from langchain_nvidia_ai_endpoints import ChatNVIDIA
import time
original_generate = ChatNVIDIA._generate
def _is_transient_nvidia_error(exc: BaseException) -> bool:
text = str(exc)
return any(
marker in text
for marker in (
"[504]",
"Gateway Time-out",
"[500]",
"APIConnectionError",
"Connection error",
"No fallback model group found",
"[429]",
"rate limit",
"temporarily unavailable",
)
)
def patched_generate(self, *args, **kwargs):
for attempt in range(1, 5):
try:
return original_generate(self, *args, **kwargs)
except Exception as exc:
if attempt == 4 or not _is_transient_nvidia_error(exc):
raise
time.sleep(min(2 ** (attempt - 1), 8))
ChatNVIDIA._generate = patched_generateWe created a guarded launcher:
scripts/run_ultra_profile_patched.pyThe launcher runs a preflight in the same Python process that will execute pytest. It refuses to run unless all of these are true:
nemotron-ultra-downloadedis registered for the ChatNVIDIA Ultra model keys.- The profile contains the downloaded
read_fileoverride. - The profile middleware includes
ReadFileContinuationNoticeMiddleware,ToolRetryMiddleware,NemotronToolCallShim,NemotronTextToolCallParser,ModelRateLimitRetryMiddleware, andFinalAnswerGuardMiddleware. - ChatNVIDIA serializer patch is installed in both
_utilsandchat_modelsmodules. ToolMessage.namesurvives serialization.- standard
AIMessage.tool_callsserializes to OpenAI-formattool_calls. - non-assistant
content=Noneis converted to"null". - Ultra model construction changes
max_tokensfrom32768toNone. temperature=1.0,top_p=0.95, andmax_input_tokens=256000are present.- the ChatNVIDIA transient retry wrapper is installed with 4 attempts.
LANGSMITH_ENDPOINTis the API base, not the UI root:https://langsmith.prd.astra.nvidia.com/api/v1.- the judge and tau2 user simulator model aliases are set to the intended Inference Hub aliases.
NVIDIA_API_KEYstarts withsk-; ifNVIDIA_INFERENCE_HUB_API_KEYis set, it must also start withsk-.
Cheap verification command:
uv run --group test python scripts/run_ultra_profile_patched.py \
--preflight-only \
--proof-file /tmp/ultra_profile_chatnvidia_preflight.jsonA successful proof starts with:
PROFILE_PATCH_PREFLIGHT_OK
and includes:
{
"chatnvidia_patches": {
"ai_tool_calls_openai_serialized": true,
"generate_retry_attempts": 4,
"generate_retry_installed": true,
"max_input_tokens": 256000,
"non_assistant_none_content_to_null": true,
"serializer_patch_shared": true,
"temperature": 1.0,
"tool_message_name_preserved": true,
"top_p": 0.95,
"ultra_max_tokens_after": null,
"ultra_max_tokens_before": 32768
},
"expected_harness_profile_arg": "nemotron-ultra-downloaded",
"expected_judge_model": "nvidiahub:judge-sonnet-inference-hub",
"expected_model_arg": "nvidiahub:ultra-tme",
"expected_tau2_user_sim_model": "nvidiahub:tau2-user-gpt-4.1-mini",
"environment": {
"DEEPAGENTS_LLM_JUDGE_MODEL": "nvidiahub:judge-sonnet-inference-hub",
"DEEPAGENTS_TAU2_USER_SIM_MODEL": "nvidiahub:tau2-user-gpt-4.1-mini",
"NVIDIA_API_KEY_present": true,
"NVIDIA_API_KEY_startswith_sk": true
},
"status": "ok"
}Use the guarded launcher, not raw pytest:
export LANGSMITH_TRACING=true
export LANGSMITH_ENDPOINT=https://langsmith.prd.astra.nvidia.com/api/v1
export NVIDIA_API_KEY=sk-...
export NVIDIA_INFERENCE_HUB_API_KEY=sk-...
export DEEPAGENTS_LLM_JUDGE_MODEL=nvidiahub:judge-sonnet-inference-hub
export DEEPAGENTS_TAU2_USER_SIM_MODEL=nvidiahub:tau2-user-gpt-4.1-mini
export DEEPAGENTS_NVIDIAHUB_ULTRA_TME_TEMPERATURE=1.0
export DEEPAGENTS_NVIDIAHUB_ULTRA_TME_TOP_P=0.95
export DEEPAGENTS_NVIDIAHUB_ULTRA_TME_MAX_INPUT_TOKENS=256000
export DEEPAGENTS_NVIDIAHUB_JUDGE_SONNET_INFERENCE_HUB_MODEL=aws/anthropic/bedrock-claude-sonnet-4-6
export DEEPAGENTS_NVIDIAHUB_TAU2_USER_GPT_4_1_MINI_MODEL=azure/openai/gpt-4.1-mini
export DEEPAGENTS_EVALS_REPORT_FILE=./report.json
uv run --group test python scripts/run_ultra_profile_patched.py \
--proof-file ./preflight_proof.json \
tests/evals \
--eval-category-exclude memory \
--model nvidiahub:ultra-tme \
--harness-profile nemotron-ultra-downloadedThe launcher validates profile + patches before pytest can make model calls.