Skip to content

v1.3.1

Choose a tag to compare

@cryxnet cryxnet released this 11 Oct 09:33
· 56 commits to main since this release

A patch release for 1.3.0. Upgrade with pip install -U promptise.

The main fix: in 1.3.0, when the model left out an optional parameter, MCP tools sent it as null, so servers that validate input — every MCPcast-generated server, and any tool with a typed optional parameter — rejected the call. Tools now send only the arguments the model gave, invalid arguments come back to the model as a correctable error, and the model is offered each parameter's constraints.

Fixed

  • MCP tools sent null for every optional parameter the model left out -- LangChain's tool input parsing (langchain-core 1.x) fills in every field with a default, and the agent's tool schema gives an optional parameter without a schema default the default None, so the MCP tool sent {"limit": null, ...} for each one. A server that validates its input rejected the call (Input validation error: None is not of type 'integer'): every MCPcast-generated server, and any tool whose schema has an optional, non-nullable parameter. Only the arguments the model gave are sent now, at every level of nesting, and the server applies its own defaults. An explicit null is sent only where the schema allows null; a null for an optional parameter that cannot be null counts as not given (logged at debug level). The same applies to MCPcast's readiness run.
  • Invalid tool arguments failed silently -- a call whose arguments did not match the tool's schema (including an explicit null for an optional, non-nullable parameter) failed before it was sent, with a raw pydantic error for the model and nothing in trace_tools or the observability timeline. It now raises promptise.tools.ToolArgumentError (a ToolException, code INVALID_ARGUMENTS): the model gets each problem by argument path (- items.0.qty: Input should be a valid integer (got "two")) and is told to correct the call, trace_tools and the timeline show the call and the error, and a tool.error event carries the code. Behind an approval gate, invalid arguments are reported the same way and never reach a reviewer.
  • Nested tool arguments were model reprs downstream -- nested objects reached trace_tools, the observability timeline, ApprovalRequest.arguments / raw_arguments, approval.* events and adaptive-strategy denial lessons as generated model instances (Args_create_order_items_Item_2(sku='A', ...)), so reviewers saw reprs and ApprovalRule.argument_contains / predicates matched them instead of the data. They are plain JSON now (dicts and lists, the fields the model set). Python tools behind the approval gate, guardrail scanning or tracing still receive validated values (model instances for model parameters) and their own defaults.
  • Free-form MCP tool arguments were dropped -- a tool whose input schema has no properties but allows any keys (additionalProperties) was called with {}, whatever the model passed.
  • Redaction missed current OpenAI and Anthropic API keys, and OTel spans exported error text unredacted -- the credential pattern behind observability and event redaction matched only sk- followed by letters and digits, so sk-proj-…, sk-svcacct-… and sk-ant-… keys (which contain - and _) passed through. It now matches them. OTelMiddleware put the raw exception message on spans (mcp.error.message, the status description and the recorded exception, including its traceback); all three now go through the same redaction, and the span's own automatic exception recording is turned off so nothing is exported unredacted.
  • trace_tools=True printed every cross-agent delegation twice -- each ask_agent_<name> (and broadcast_to_agents) call and its result were printed, and recorded by an observer, once by the delegation hooks and again by the wrapper that traces non-MCP tools, although the peer ran once. Cross-agent tools now report only through their delegation hooks, so each delegation prints once, with the delegation's own arguments (and, for a broadcast, the per-peer results).
  • A failed astream_with_tools() run was not logged -- the consumer gets a generic ErrorEvent and the cause was dropped at every log level (1.2.1 logged it). The failure is logged again as one ERROR line on promptise.agent (the level the engine logs a failed run at) with the error type, message, agent id, session id and invocation id. The message goes through the observability redaction (API keys, tokens, URL credentials, card numbers, emails) and is truncated; a guardrail violation is summarised, not quoted. astream() and ainvoke() raise the failure to the caller and are unchanged.
  • request_more_tools() without arguments enabled the whole tool catalogue -- with semantic tool selection on a large server, the next model call could be offered hundreds of tools and fail on OpenAI's 128-tool limit. An argument-less call now returns the next semantic_top_k tools most relevant to the conversation that the model was not offered yet (each further call pages on). One call returns at most 32 tools (a longer tool_names list is cut, and the reply says how many were left out), and a model call is offered at most 100 indexed tools.
  • optimize_tools stripped more than descriptions from tool schemas -- schema minification (on at every level: "minimal", "standard", "semantic") rebuilt each tool's argument model from field types and defaults only. It dropped the constraints shown to the model (enum values a Literal cannot hold, pattern, minLength/maxLength, minimum/maximum, format, minItems/maxItems, ...) and the model config, so a free-form tool (no properties, additionalProperties) lost its "takes any keys" setting and was called with {} again. Minification now removes only descriptions and keeps types, required and optional fields and their defaults, nullability, constraints and other field settings, the model config, and nested models wherever they sit (Optional[list[Model]], dict[str, Model]). Optional parameters the model leaves out are still not sent, null handling and plain-JSON nested arguments work as without optimization, and max_schema_depth still flattens deeper objects to dict, keeping the field's own constraints and nullability.
  • Top-level parameter constraints and free-form schemas did not reach the model -- LangChain builds the schema it offers the model (BaseTool.tool_call_schema) by copying each field's type, default and description, which drops json_schema_extra and the model config. Constraints on nested objects reached the model, those on top-level MCP tool parameters did not, and a free-form MCP tool was offered as a tool without parameters ({"properties": {}}), with or without optimize_tools. MCP tools now offer their argument schema as is, and the approval gate, guardrail scanning and tool tracing wrappers offer what the tool they wrap offers.

Documentation

  • MCP server OpenTelemetry example -- the docs showed OTelMiddleware(endpoint=...), which raises TypeError: the middleware takes service_name, tracer_provider and meter_provider, and the exporter (and its endpoint) is configured on the provider. The examples now build an OTLP TracerProvider and run as written; the span attribute is mcp.status (not mcp.tool.status). The docs also said OTelMiddleware and PrometheusMiddleware do nothing without their packages; they raise ImportError, whose message now names the extra (pip install "promptise[all]") as well as the packages. The PrometheusMiddleware docstring no longer promises a /metrics route the server does not add.

Full changelog: https://github.com/promptise-com/foundry/blob/main/CHANGELOG.md