Changelog

[UNRELEASED]

New features

  • New ChatBedrock() gives full access to AWS Bedrock’s model catalog — Nova, Llama, Mistral, DeepSeek, Qwen, plus the GPT-5 family, Grok 4.3, and Gemma 4 — not just Claude, none of which were previously available through chatlas. It replaces ChatBedrockAnthropic() as the recommended entrypoint; the right request format ("converse", "responses", or "messages") is picked automatically from the model name, or set api explicitly.

Improvements

  • Updated default models to match the latest generation:
    • Anthropic / BedrockAnthropic / Posit: claude-sonnet-5
    • OpenAI / Completions / OpenRouter: gpt-5.6-terra
  • Echoing turns in the console and notebooks got a round of display improvements:
    • Reasoning/thinking content now actually shows up — it used to silently disappear, since it was wrapped in literal <thinking> tags that a markdown renderer treated as an HTML block and dropped. It renders in a collapsible “Thinking” panel (a <details> block in notebooks) that stays open while streaming and collapses once done, and is capped to the most recent lines when long. (#361)
    • Long tool results no longer flood the screen — they collapse/truncate with a clear count of what’s hidden, scrolling internally in notebooks beyond a bounded height. (#361)
    • Images from models or tools now render as compact thumbnails instead of raw base64 data.
    • Web search, fetch, and citation activity is now visible too, grouped into a “Searched the web” / “Read the web” panel that marks which sources were actually cited. (#256)
    • All of these size limits are tunable via Chat.set_echo_options() (tool_result_max_lines, tool_result_max_height, thinking_max_lines, image_max_lines, web_activity_max_sources), and can be turned off entirely with None.
  • Registering a built-in tool (tool_web_search(), tool_web_fetch()) with a provider that can’t run it now fails immediately with a clear error naming the tool and provider, instead of silently no-op’ing or dying deep inside a later request. (#367)

Bug fixes

  • echo="all" no longer displays tool results twice — once in full as part of the user turn, and again on their own.
  • Chat.set_echo_options(css_styles=) now actually applies in notebooks.
  • Tool names and argument names are now HTML-escaped in notebook/shiny rendering, closing an HTML-injection hole.
  • A tool that reports progress by yielding more than once, or an MCP server that answers a call with several content parts (text plus an image, say), no longer breaks the request.
  • register_mcp_tools_stdio_async() and register_mcp_tools_http_stream_async() no longer fail when an MCP server leaves some tool annotations unset.
  • ContentCitation, ContentToolRequestFetch, and ContentToolResponseFetch now actually render, instead of silently vanishing due to a markdown link-reference parsing quirk.
  • ChatAnthropic() (and ChatBedrockAnthropic()) now bill refusal-fallback turns at the correct (serving model’s) rate rather than the originally requested model’s, mirroring ellmer’s equivalent fix.

Breaking changes

  • ChatGithub() is now defunct: it always raises RuntimeError. GitHub Models was retired on 2026-07-30, so the underlying API no longer works. Use ChatGoogle() (offers a free tier) or ChatPosit() (offers a free trial) instead.

[0.20.0] - 2026-07-29

New features

  • New content_document_file() and content_document_url() prepare plain text, Markdown, CSV, code, and (on ChatOpenAI()) binary office files like .docx/.xlsx/.doc/.xls/.rtf/.odt for chat input, returning a new ContentDocument type. Previously the only way to attach a non-PDF file was to read and string-interpolate it yourself, which lost the filename and OpenAI’s spreadsheet parsing. Provider support varies:
    • ChatOpenAI() (Responses API) accepts every type above.
    • ChatGoogle() accepts text-ish types; the binary office formats raise.
    • ChatAnthropic() accepts documents it can treat as plain text; binary formats raise.
    • ChatOpenAICompletions() sends the document as-is; OpenAI’s own Chat Completions endpoint only accepts application/pdf and rejects the rest, though other OpenAI-compatible backends may accept more. Use ChatOpenAI() against OpenAI proper.
    Like content_pdf_url(), content_document_url() doesn’t download up front: ChatOpenAI() references the URL directly, and other providers fetch lazily.
  • ImageContentTypes (and so content_image_file()/content_image_url()) now accepts image/heic and image/heif, which ChatGoogle() supports natively. Other providers raise a clear error rather than sending a format they’ll reject. Resizing HEIC/HEIF images requires the optional pillow-heif package; without it, pass resize="none".
  • content_pdf_url() no longer downloads the PDF’s bytes up front. Anthropic and ChatOpenAI() (the Responses API) can reference the URL directly, so the bytes are only downloaded – and cached – if the target provider actually needs them (ChatGoogle(), ChatOpenAICompletions()). This reduces bandwidth and request payload size, which matters given Anthropic’s 32 MB and OpenAI’s 50 MB request limits. Accordingly, ContentPDF.data is now Optional[bytes] (it’s None when only a url is set); a validator requires at least one of data/url. content_pdf_url() also now takes the filename from the URL’s last path segment when it ends in .pdf (e.g. apples.pdf), rather than always generating file_001.pdf; URLs without a usable name still fall back to the generated one.
  • Chat gains a .files accessor for uploading files to a provider once and referencing them across turns without re-sending bytes, plus listing, fetching metadata, downloading, and deleting them. Supported for OpenAI, Anthropic, and Google Gemini. A new ContentUploaded type represents the reference and can be constructed directly to point at a file uploaded out-of-band (e.g. a Vertex gs:// URI). For Google, upload() waits for Gemini to finish processing large media (video, audio) before returning, since the API rejects references to files that aren’t yet ACTIVE.
  • Web search and fetch results now surface their citations across all three providers (OpenAI, Anthropic, Google), both progressively during streaming and on the final turn. ContentCitation nests a typed source (a Source subclass — WebSource today, carrying url/title) instead of flat url/title fields, and carries grounded_span (the answer-side span it grounds) plus cited_quote (the source-side quote, populated for ChatAnthropic() web search). source is optional — a citation can ground answer text with no resolvable link. ContentCitation, Source, and WebSource are exported from chatlas.types. A future file/document/RAG source becomes another Source subclass without breaking ContentCitation.source; note that ContentToolResponseSearch.sources is typed narrowly as list[WebSource] and would need widening at that point.
    • When streaming with content="all", ContentCitation objects are emitted as citations arrive — interleaved with text for OpenAI and Anthropic, at stream-end for Google. Its position in the stream (relative to surrounding text) is the placement signal for rendering footnote markers.
    • On the final turn, ContentCitation items appear in the turn’s contents list after the ContentText they ground, in the order the provider reported them. Since a turn’s text arrives as one accumulated ContentText, position no longer narrows a citation to a span within it — use grounded_span for that.
  • batch_chat() now supports ChatGoogle() (Gemini Developer API batch jobs). Batch is also now documented as supported for ChatGroq(), which already worked via its OpenAI-compatible provider. (Vertex AI is not supported, since its batch API requires GCS bucket URIs instead of inline requests.)
  • ChatOllama() gains a reasoning_effort parameter to enable extended “thinking” for models that support it (e.g. qwen3, gpt-oss).
  • Chat.token_count() gained an include= argument: "new" (default) counts just the given input, while "complete" estimates the total tokens for the next request, including history and system prompt where the provider supports it.

Improvements

  • ChatGoogle() and ChatVertex() now default to gemini-3.5-flash instead of the older gemini-2.5-flash.
  • ChatGroq() now defaults to openai/gpt-oss-20b instead of llama-3.1-8b-instant.
  • Built-in web search and fetch content (ContentToolRequestSearch/ContentToolResponseSearch and ContentToolRequestFetch/ContentToolResponseFetch) is now also emitted while streaming with content="all", for ChatOpenAI(), ChatAnthropic(), and ChatGoogle(). Previously it appeared only on the completed turn, so a UI had no way to show search activity until the whole response had arrived.
  • ContentToolResponseFetch gained a normalized status field ("success", "error", or None when the provider doesn’t report an outcome). Providers’ finer-grained reasons (Anthropic’s url_not_allowed, Google’s PAYWALL, …) aren’t aligned across providers, so they stay available in extra.
  • ChatOpenAI().token_count() now uses OpenAI’s token-counting endpoint for accurate, tool-aware counts instead of a local tiktoken estimate.

Changes

  • MCP support now requires mcp>=2.0.0. The 2.0 release of the mcp SDK renamed its model fields (and removed mcp.server.fastmcp.FastMCP), so older mcp versions are no longer compatible. This only affects users of the optional mcp extra (i.e., register_mcp_tools_*()).
  • Turn.finish_reason is now normalized to a consistent set of values ("success", "tool_use", "max_tokens", "content_filter", "context_window", "stop_sequence") across most providers, so you no longer need provider-specific logic to check why a turn ended. Previously each provider surfaced its own raw string (e.g. Anthropic’s "end_turn"/"tool_use" vs. OpenAI Completions’ "stop"/"tool_calls" vs. Google’s "STOP"/"SAFETY"), so the same outcome could require different checks depending on which Chat*() you used. Reasons chatlas doesn’t yet recognize still pass through unchanged.

Bug fixes

  • ChatGoogle() no longer errors when mixing custom tools and built-in tools (e.g. tool_web_search()) on Gemini 3+ models.
  • Turns containing web search/fetch content can now be passed to a different provider (e.g. ChatAnthropic().set_turns(openai_chat.get_turns())). Previously this raised ValueError: Unsupported content type on ChatOpenAI(), and ChatAnthropic() forwarded the other provider’s raw payload as if it were its own, producing an invalid request. Each provider now replays only the built-in tool content it produced and drops the rest.
  • ChatOpenAI() web search open_page actions now surface as ContentToolRequestFetch (with the URL) rather than a ContentToolRequestSearch whose “query” was the URL, so renderers no longer show “searched for: https://…”. Relatedly, a search action that reports only the plural queries field no longer falls through to the literal string "web search".
  • ChatGoogle() now records its built-in web search and URL-context work in the assistant turn, as ContentToolRequestSearch/ContentToolResponseSearch for grounded searches and ContentToolRequestFetch/ContentToolResponseFetch for fetched URLs. Previously tool_web_search() and tool_web_fetch() worked but reported nothing about what was searched or fetched, unlike ChatAnthropic() and ChatOpenAI(). Google’s raw grounding_metadata/url_metadata is kept on each item’s extra.
  • .chat_structured() now explains itself when the response is cut short. Previously, extracting a data model large enough to hit the model’s output limit failed with a bare JSONDecodeError pointing at a column number in the truncated JSON, giving no hint that max_tokens was the problem (#315). It now raises a ValueError naming max_tokens and suggesting you raise it. Responses truncated by the context window, or stopped by the provider’s content filter, are reported the same way. Plain .chat() warns instead of erroring, since a partial response is still usable there — previously it returned truncated text with no indication anything was missing.
  • Streaming two adjacent pieces of same-typed content that define no merge behavior (e.g. two tool requests) no longer raises TypeError. They are now appended as separate content instead.

Breaking changes

  • ContentToolResponseSearch.urls (a list[str]) has been replaced by .sources (a list[WebSource]), each carrying the result’s url and title. Code reading .urls should switch to [s.url for s in x.sources].
  • The Provider abstract base class changed shape, which affects third-party Provider subclasses (not users of the built-in Chat*() functions): stream_text() was removed, and stream_content() both returns a Sequence[Content] (subsuming what stream_text() did) and takes a second completion argument holding the merged-so-far completion. Implementations needing state across chunks should read it from completion rather than storing it on self, since one provider instance is shared across forked chats.
  • Provider.token_count()/token_count_async() now take a turns: list[Turn] argument instead of *args: Content | str (affects custom Provider subclasses only).

[0.19.2] - 2026-07-08

New features

  • The .app() method now includes latest shinychat features like history, file attachments, etc.

[0.19.1] - 2026-07-01

New features

  • Added ChatPosit() for chatting via the Posit AI gateway. (#323)

[0.19.0] - 2026-06-15

New features

  • chatlas is now instrumented with OpenTelemetry (OTel) out of the box, making it much easier to see how your app behaves in production — where time goes, how many tokens you’re spending, which tools run, and where things fail. Without writing any tracing code, you get spans that capture the full structure of a conversation as one connected trace: an invoke_agent span over the whole chat loop, a chat span per model call, and an execute_tool span per tool invocation, with attributes (token usage, response model/ID, tool errors) that follow the OTel GenAI semantic conventions. Because chatlas keeps its spans active during each call, HTTP spans from provider instrumentors and any spans your own tools emit nest underneath automatically. Point it at any OTel-compatible backend (Logfire, Datadog, Honeycomb, Jaeger, …); message content is omitted by default and opt-in via OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true. See the monitoring guide to get started. (#310)
  • Chat gains a model property to get (or set) the model after the chat is created. Setting it does not validate the model name.
  • ChatGoogle()’s reasoning parameter now accepts a string thinking level ("minimal", "low", "medium", or "high") in addition to an integer token budget.
  • ChatAnthropic()’s reasoning parameter now accepts a string effort level ("low", "medium", "high", "xhigh", or "max") to enable Claude’s adaptive thinking, in addition to an integer token budget.

Bug fixes

  • OpenAI-compatible providers (e.g., ChatOllama() with models like qwen3) now capture thinking content returned in a reasoning field, not just reasoning_content. Previously this thinking content was silently dropped.

[0.18.1] - 2026-05-21

Improvements

  • Content.tagify() implementations (ContentToolRequest, ContentToolResult, ContentThinking) now annotate their return type as htmltools.Tagified and fully tagify their output, complying with htmltools 0.7.0’s tightened Tagifiable contract. Embedding these contents inside another .tagify() recursion no longer trips the new boundary check in htmltools 0.7.0. (#311)

Bug fixes

  • ContentPDF is now exported from chatlas.types, matching all other Content subclasses. (#312)

[0.18.0] - 2026-05-12

New features

  • New StreamController class for cooperative stream cancellation. Pass a controller to .stream() or .stream_async() and call controller.cancel() to stop the stream cleanly (e.g., from a Shiny “stop generating” button). The partial response is preserved in conversation history. (#279)

Improvements

  • ChatAnthropic() and ChatBedrockAnthropic() now use Anthropic’s native structured outputs API for Claude 4.5+ models, enabling streaming with data_model. Older models fall back to the tool-based approach. A new structured_output_mode parameter ("auto", "native", or "tool") lets you override the auto-detection. (#263)
  • When a stream is interrupted (closed early, cancelled, or errors), the accumulated content is now saved as a partial AssistantTurn so conversation state isn’t lost. Partial turns display [interrupted] (or the cancellation reason) in the Chat repr and are excluded from token/cost accounting. (#279)
  • ChatBedrockAnthropic() now defaults to cache="5m", enabling prompt caching by default — matching ChatAnthropic()’s behavior. (#308)
  • ChatOpenAI() now warns when base_url points to a non-OpenAI host, guiding users to ChatOpenAICompletions() for third-party backends like vLLM, Ollama, and LiteLLM. (#285)

Bug fixes

  • Fixed thinking content being silently dropped during streaming for completions-based providers (DeepSeek, Groq, OpenRouter, etc.). The streaming path was returning finalized ContentThinking objects instead of ContentThinkingDelta fragments, which the TurnAccumulator didn’t recognize. (#301)
  • Fixed model_dump(mode="json") failing on Turns containing bytes fields (e.g., ContentPDF.data, thought_signature in ContentToolRequest/ContentThinking extras). Bytes values are now base64-encoded during serialization and decoded on validation, so JSON round-trips work correctly.
  • batch_chat(), batch_chat_text(), and batch_chat_structured() now correctly return None when wait=False and the job is still incomplete. Previously they returned [], making it impossible to distinguish “all requests failed” from “job not done yet”. (#306)
  • ChatDatabricks() (and other ChatOpenAICompletions() providers) no longer fail with HTTP 400 when the conversation history contains empty assistant content, which can occur during tool calling. (#305)

[0.17.0] - 2026-05-11

New features

  • ChatOpenAICompletions() (and providers built on it like ChatDeepSeek, ChatOpenRouter, etc.) now extracts reasoning_content from model responses as ContentThinking objects. A new preserve_thinking parameter controls whether reasoning content is sent back to the API in multi-turn conversations; it defaults to False but is set to True for ChatDeepSeek (required for V4 tool-calling) and ChatOpenRouter (recommended for quality). (#295)

Improvements

  • .stream() and .stream_async() now handle thinking content differently by mode. With content="text", thinking is suppressed entirely. With content="all", thinking fragments are yielded as ContentThinkingDelta objects with a phase property ("start", "body", or "end") that communicates block boundaries to downstream consumers without injecting synthetic strings into the stream. (#299, #297, #294)
  • Updated default models across all providers to current generation: (#292)
    • Anthropic: claude-sonnet-4-6
    • Bedrock: us.anthropic.claude-sonnet-4-6
    • Snowflake: claude-sonnet-4-6
    • Databricks: databricks-claude-sonnet-4-6
    • OpenAI / Completions / OpenRouter / Portkey: gpt-5.4
    • GitHub: gpt-5
    • Deepseek: deepseek-v4-flash
    • Perplexity: sonar
  • Updated token pricing data from LiteLLM. (#292)
  • ChatBedrockAnthropic() gains a reasoning parameter for extended thinking, matching the existing parameter on ChatAnthropic(). (#286)

[0.16.0] - 2026-04-16

New features

  • New ChatLMStudio() provider for chatting with local models via LM Studio. (#280)
  • The .stream() and .stream_async() methods now yield ContentThinking objects (instead of plain strings) for thinking/reasoning content when content="all". This allows downstream packages like shinychat to provide specific UI for thinking content. (#276)
  • Built-in tools (tool_web_search(), tool_web_fetch()) now include description and annotations properties, making their metadata consistent with user-defined tools created by Tool(). (#278)

Bug fixes

  • Fixed OpenAI streaming crash (AttributeError: 'NoneType' object has no attribute 'output') caused by a new response.rate_limits.updated event emitted after response.completed. (#282)
  • Fixed tool calling with Google thinking models (e.g., gemini-3-flash-preview) failing with a 400 INVALID_ARGUMENT error about a missing thought_signature. The signature is now preserved and forwarded in subsequent turns. (#274)
  • OpenAI’s web_search_call no longer errors on non-search action types like open_page and find_in_page. (#277)

[0.15.2] – 2026-02-27

Bug fixes

  • Fixed compatibility with rich >= 14.3.0 and Anthropic SDK v0.82+. (#269)

[0.15.1] – 2026-01-22

New features

  • .stream() and .stream_async() now support a data_model parameter for structured data extraction while streaming. (#262)
  • .to_solver() now supports a data_model parameter for structured data extraction in evals. When provided, the solver uses .chat_structured() instead of .chat() and outputs JSON-serialized data. (#264)

Bug fixes

  • Fixed ContentToolResult with an error not being JSON serializable. When a tool call failed, calling .get_turns() followed by .model_dump_json() would raise a PydanticSerializationError. (#267)

[0.15.0] - 2026-01-06

New features

  • ChatOpenAI(), ChatAnthropic(), and ChatGoogle() gain a new reasoning parameter to easily opt-into, and fully customize, reasoning capabilities. (#202, #260)
    • A new ContentThinking content type was added and captures the “thinking” portion of a reasoning model. (#192)
  • Added “built-in” web search and URL fetch tools tool_web_search() and tool_web_fetch():
    • tool_web_search() is supported by OpenAI, Claude (Anthropic), and Google (Gemini).
    • tool_web_fetch() is supported by Claude (requires beta header) and Google.
    • New content types ContentToolRequestSearch, ContentToolResponseSearch, ContentToolRequestFetch, and ContentToolResponseFetch capture web tool interactions.
  • Added ToolBuiltIn class to assist with specifying provider-specific built-in tools. This enables provider-specific functionality like OpenAI’s image generation to be registered and used as tools. Built-in tools pass raw provider definitions directly to the API rather than wrapping Python functions. (#214)
  • ChatOpenAI() and ChatAzureOpenAI() gain a new service_tier parameter to request a specific service tier (e.g., "flex" for slower/cheaper or "priority" for faster/more expensive). (#204)
  • ChatAuto() now accepts "claude" as an alias for "anthropic", reflecting Anthropic’s rebranding of developer tools under the Claude name. (#239)

Changes

  • repr() now generally gives the same result as str() for many classes (Chat, Turn, Content, etc). This leads to a more human-readable result (and is closer to the result that gets echoed by .chat()). (#245)
  • The Chat.get_cost() method’s options parameter was renamed to include. (#244)
  • When supplying a model to .register_tool(tool_func, model=ToolModel), the defaults for the model must match the tool_func defaults. Previously, if tool_func had defaults, but ToolModel didn’t, those defaults would get silently ignored. (#253)

Improvements

  • Chat and Turn now have a _repr_markdown_ method and an overall improved repr() experience. (#245)
  • ChatSnowflake() now sets the application config parameter for partner identification. Defaults to "py_chatlas" but can be overridden via the SF_PARTNER environment variable. (#209)

Bug fixes

  • Fixed structured data extraction with ChatAnthropic() failing for Pydantic models containing nested types (e.g., list[NestedModel]). The issue was that $defs (containing nested type definitions) was incorrectly placed inside the schema, breaking JSON $ref pointer references. (#100)
  • Fixed MCP tools failing with OpenAI providers due to strict mode schema validation. OpenAI’s strict mode rejects standard JSON Schema features like format: "uri" and requires all properties in the required array. MCP tools now set strict=false to use standard JSON Schema conventions. (#255)
  • Fixed MCP tools not working with ChatGoogle(). (#257)
  • Tool functions parameters that are typing.Annotated with a pydantic.Field (e.g., def add(x: Annotated[int, Field(description="First number")])) are now handled correctly. (#251)

[0.14.0] - 2025-12-09

New features

  • ChatOpenAI() (and ChatAzureOpenAI()) gain access to latest models, built-in tools, etc. as a result of moving to the new Responses API. (#192)
  • Added new family of functions (parallel_chat(), parallel_chat_text(), and parallel_chat_structured()) for submitting multiple prompts at once with some basic rate limiting toggles. (#188)
  • Tools can now return image or PDF content types, with content_image_file() or content_pdf_file() (#231).
    • As a result, the experimental ContentToolResultImage and ContentToolResultResource were removed since this new support for generally supporting ContentImage and ContentPDF renders those content types redundant.
  • Added support for systematic evaluation via Inspect AI. This includes:
    • A new .export_eval() method for exporting conversation history as an Inspect eval dataset sample. This supports multi-turn conversations, tool calls, images, PDFs, and structured data.
    • A new .to_solver() method for translating chat instances into Inspect solvers that can be used with Inspect’s evaluation framework.
    • A new Turn.to_inspect_messages() method for converting turns to Inspect’s message format.
    • Comprehensive documentation in the Evals guide.
  • ChatAnthropic() and ChatBedrockAnthropic() gain new cache parameter to control caching. For ChatAnthropic(), it defaults to "5m", which should (on average) reduce the cost of your chats. For ChatBedrockAnthropic(), it defaults to "none", since caching isn’t guaranteed to be widely supported (#215)
  • Added rudimentary support for a new ContentThinking type. (#192)

Changes

  • ChatOpenAI() (and ChatAzureOpenAI()) move from OpenAI’s Completions API to Responses API. If this happens to break behavior, change ChatOpenAI() -> ChatOpenAICompletions() (or ChatAzureOpenAI() -> ChatAzureOpenAICompletions()). (#192)
  • The Turn class is now a base class with three specialized subclasses: UserTurn, AssistantTurn, and SystemTurn. Use these new classes to construct turns by hand. (#224)
  • The .set_model_params() method no longer accepts kwargs. Instead, use the new chat.kwargs_chat attribute to set chat input parameters that persist across the chat session. (#212)
  • Provider implementations now require an additional .value_tokens() method. Previously, it was assumed that token info was logged and attached to the Turn as part of the .value_turn() method. The logging and attaching is now handled automatically. (#194)

Improvements

  • ChatAnthropic() and ChatBedrockAnthropic() now default to Claude Sonnet 4.5.
  • ChatGroq() now defaults to llama-3.1-8b-instant.
  • Chat.chat(), Chat.stream(), and related methods now automatically complete dangling tool requests when a chat is interrupted during a tool call loop, allowing the conversation to be resumed without causing API errors (#230).
  • content_pdf_file() and content_pdf_url() now include relevant filename information. (#199)

Bug fixes

  • .set_model_params() now works correctly for .*_async() methods. (#198)
  • .chat_structured() results are now included correctly into the multi-turn conversation history. (#203)
  • ChatAnthropic() now drops empty assistant turns to avoid API errors when tools return side-effect only results. (#226)

[0.13.2] - 2025-10-02

Improvements

  • ContentToolResult’s .get_model_value() method now calls .to_json(orient="record") (instead of .to_json()) when relevant. As a result, if a tool call returns a Pandas DataFrame (or similar), the model now receives a less confusing (and smaller) JSON format. (#183)

Bug fixes

  • ChatAzureOpenAI() and ChatDatabricks() now work as expected when a OPENAI_API_KEY environment variable isn’t present. (#185)

[0.13.1] - 2025-09-18

Bug fixes

  • ChatGithub() once again uses the appropriate base_url when generating reponses (problem introduced in v0.11.0). (#182)

[0.13.0] - 2025-09-10

New features

  • Added support for submitting multiple chats in one batch. With batch submission, results can take up to 24 hours to complete, but in return you pay ~50% less than usual. For more, see the reference for batch_chat(), batch_chat_text(), batch_chat_structured() and batch_chat_completed(). (#177)
  • The Chat class gains new .chat_structured() (and .chat_structured_async()) methods. These methods supersede the now deprecated .extract_data() (and .extract_data_async()). The only difference is that the new methods return a BaseModel instance (instead of a dict()), leading to a better type hinting/checking experience. (#175)
  • The .get_turns() method gains a tool_result_role parameter. Set tool_result_role="assistant" to collect tool result content (plus the surrounding assistant turn contents) into a single assistant turn. This is convenient for display purposes and more generally if you want the tool calling loop to be contained in a single turn. (#179)

Improvements

  • The .app() method now:
    • Enables bookmarking by default (i.e., chat session survives page reload). (#179)
    • Correctly renders pre-existing turns that contain tool calls. (#179)

[0.12.0] - 2025-09-08

Breaking changes

  • ChatAuto()’s first (optional) positional parameter has changed from system_prompt to provider_model, and system_prompt is now a keyword parameter. As a result, you may need to change ChatAuto("[system prompt]") -> ChatAuto(system_prompt="[system prompt]"). In addition, the provider and model keyword arguments are now deprecated, but continue to work with a warning, as are the previous CHATLAS_CHAT_PROVIDER and CHATLAS_CHAT_MODEL environment variables. (#159)

New features

  • ChatAuto()’s new provider_model takes both provider and model in a single string in the format "{provider}/{model}", e.g. "openai/gpt-5". If not provided, ChatAuto() looks for the CHATLAS_CHAT_PROVIDER_MODEL environment variable, defaulting to "openai" if neither are provided. Unlike previous versions of ChatAuto(), the environment variables are now used only if function arguments are not provided. In other words, if provider_model is given, the CHATLAS_CHAT_PROVIDER_MODEL environment variable is ignored. Similarly, CHATLAS_CHAT_ARGS are only used if no kwargs are provided. This improves interactive use cases, makes it easier to introduce application-specific environment variables, and puts more control in the hands of the developer. (#159)
  • The .register_tool() method now:
    • Accepts a Tool instance as input. This is primarily useful for binding things like annotations to the Tool in one place, and registering it in another. (#172)
    • Supports function parameter names that start with an underscore. (#174)
  • The ToolAnnotations type gains an extra key field – providing a place for providing additional information that other consumers of tool annotations (e.g., shinychat) may make use of.

Bug fixes

  • ChatAuto() now supports recently added providers such as ChatCloudflare(), ChatDeepseek(), ChatHuggingFace(), etc. (#159)

[0.11.1] - 2025-08-29

New features

  • .register_tool() gains a name parameter (useful for overriding the name of the function). (#162)

Bug fixes

  • ContentToolRequest is (once again) serializable to/from JSON via Pydantic. (#164)
  • .register_tool(model=model) no longer unexpectedly errors when model contains pydantic.Field(alias='_my_alias'). (#161)

Changes

  • .register_tool(annotations=annotations) drops support for mcp.types.ToolAnnotations() and instead expects a dictionary of the same info. (#164)

[0.11.0] - 2025-08-26

New features

  • The Chat class gains a new .list_models() method for obtaining a list of model ids/names, pricing info, and more. (#155)
  • Chat’s .register_tool() method gains an annotations parameter, which is useful for describing the tool and its behavior. This information is attached to ContentToolRequest() and ContentToolResult() (via the .request parameter) objects when tool calls occur. To include these objects in streaming content, make sure to set .stream(content="all"). (#156)

Improvements

  • Tools registered via MCP (e.g., .register_mcp_tools_http_stream_async()) now automatically pick up on tool annotations. (#156)

Changes

[0.10.0] - 2025-08-19

New features

  • Added ChatCloudflare() for chatting via Cloudflare AI. (#150)
  • Added ChatDeepSeek() for chatting via DeepSeek. (#147)
  • Added ChatOpenRouter() for chatting via Open Router. (#148)
  • Added ChatHuggingFace() for chatting via Hugging Face. (#144)
  • Added ChatMistral() for chatting via Mistral AI. (#145)
  • Added ChatPortkey() for chatting via Portkey AI. (#143)

Changes

  • ChatAnthropic() and ChatBedrockAnthropic() now default to Claude Sonnet 4.0.

Bug fixes

  • Fixed an issue where chatting with some models was leading to KeyError: 'cached_input'. (#149)

[0.9.2] - 2025-08-08

Improvements

  • Chat.get_cost() now covers many more models and also takes cached tokens into account. (#133)
  • Avoid erroring when tool calls occur with recent versions of openai (> v1.99.5). (#141)

[0.9.1] - 2025-07-09

Bug fixes

  • Fixed an issue where .chat() wasn’t streaming output properly in (the latest build of) Positron’s Jupyter notebook. (#131)

  • Needless warnings and errors are no longer thrown when model pricing info is unavailable. (#132)

[0.9.0] - 2025-07-02

New features

  • Chat gains a handful of new methods:
    • .register_mcp_tools_http_stream_async() and .register_mcp_tools_stdio_async(): for registering tools from a MCP server. (#39)
    • .get_tools() and .set_tools(): for fine-grained control over registered tools. (#39)
    • .set_model_params(): for setting common LLM parameters in a model-agnostic fashion. (#127)
    • .get_cost(): to get the estimated cost of the chat. Only popular models are supported, but you can also supply your own token prices. (#106)
    • .add_turn(): to add Turn(s) to the current chat history. (#126)
  • Tool functions passed to .register_tool() can now yield numerous results. (#39)
  • A ContentToolResultImage content class was added for returning images from tools. It is currently only works with ChatAnthropic. (#39)
  • A Tool can now be constructed from a pre-existing tool schema (via a new __init__ method). (#39)
  • The Chat.app() method gains a host parameter. (#122)
  • ChatGithub() now supports the more standard GITHUB_TOKEN environment variable for storing the API key. (#123)

Changes

Breaking Changes

  • Chat constructors (ChatOpenAI(), ChatAnthropic(), etc) no longer have a turns keyword parameter. Use the .set_turns() method instead to set the (initial) chat history. (#126)
  • Chat’s .tokens() methods have been removed in favor of .get_tokens() which returns both cumulative tokens in the turn and discrete tokens. (#106)

Other Changes

  • Tool’s constructor no longer takes a function as input. Use the new .from_func() method instead to create a Tool from a function. (#39)
  • .register_tool() now throws an exception when the tool has the same name as an already registered tool. Set the new force parameter to True to force the registration. (#39)

Improvements

  • ChatGoogle() and ChatVertex() now default to Gemini 2.5 (instead of 2.0). (#125)
  • ChatOpenAI() and ChatGithub() now default to GPT 4.1 (instead of 4o). (#115)
  • ChatAnthropic() now supports content_image_url(). (#112)
  • HTML styling improvements for ContentToolResult and ContentToolRequest. (#39)
  • Chat’s representation now includes cost information if it can be calculated. (#106)
  • token_usage() includes cost if it can be calculated. (#106)

Bug fixes

  • Fixed an issue where httpx client customization (e.g., ChatOpenAI(kwargs = {"http_client": httpx.Client()})) wasn’t working as expected (#108)

Developer APIs

  • The base Provider class now includes a name and model property. In order for them to work properly, provider implementations should pass a name and model along to the __init__() method. (#106)
  • Provider implementations must implement two new abstract methods: translate_model_params() and supported_model_params().

[0.8.1] - 2025-05-30

  • Fixed @overload definitions for .stream() and .stream_async().

[0.8.0] - 2025-05-30

New features

  • New .on_tool_request() and .on_tool_result() methods register callbacks that fire when a tool is requested or produces a result. These callbacks can be used to implement custom logging or other actions when tools are called, without modifying the tool function (#101).
  • New ToolRejectError exception can be thrown from tool request/result callbacks or from within a tool function itself to prevent the tool from executing. Moreover, this exception will provide some context for the the LLM to know that the tool didn’t produce a result because it was rejected. (#101)

Improvements

  • The CHATLAS_LOG environment variable now enables logs for the relevant model provider. It now also supports a level of debug in addition to info. (#97)
  • ChatSnowflake() now supports tool calling. (#98)
  • Chat instances can now be deep copied, which is useful for forking the chat session. (#96)

Changes

  • ChatDatabricks()’s model now defaults to databricks-claude-3-7-sonnet instead of databricks-dbrx-instruct. (#95)
  • ChatSnowflake()’s model now defaults to claude-3-7-sonnet instead of llama3.1-70b. (#98)

Bug fixes

  • Fixed an issue where ChatDatabricks() with an Anthropic model wasn’t handling empty-string responses gracefully. (#95)

[0.7.1] - 2025-05-10

  • Added openai as a hard dependency, making installation easier for a wide range of use cases. (#91)

[0.7.0] - 2025-04-22

New features

  • Added ChatDatabricks(), for chatting with Databrick’s foundation models. (#82)
  • .stream() and .stream_async() gain a content argument. Set this to "all" to include ContentToolResult/ContentToolRequest objects in the stream. (#75)
  • ContentToolResult/ContentToolRequest are now exported to chatlas namespace. (#75)
  • ContentToolResult/ContentToolRequest gain a .tagify() method so they render sensibly in a Shiny app. (#75)
  • A tool can now return a ContentToolResult. This is useful for:
    • Specifying the format used for sending the tool result to the chat model (model_format). (#87)
    • Custom rendering of the tool result (by overriding relevant methods in a subclass). (#75)
  • Chat gains a new .current_display property. When a .chat() or .stream() is currently active, this property returns an object with a .echo() method (to echo new content to the display). This is primarily useful for displaying custom content during a tool call. (#79)

Improvements

  • When a tool call ends in failure, a warning is now raised and the stacktrace is printed. (#79)
  • Several improvements to ChatSnowflake():
    • .extract_data() is now supported.
    • async methods are now supported. (#81)
    • Fixed an issue with more than one session being active at once. (#83)
  • ChatAnthropic() no longer chokes after receiving an output that consists only of whitespace. (#86)
  • orjson is now used for JSON loading and dumping. (#87)

Changes

  • The echo argument of the .chat() method defaults to a new value of "output". As a result, tool requests and results are now echoed by default. To revert to the previous behavior, set echo="text". (#78)
  • Tool results are now dumped to JSON by default before being sent to the model. To revert to the previous behavior, have the tool return a ContentToolResult with model_format="str". (#87)

Breaking changes

  • The .export() method’s include argument has been renamed to content (to match .stream()). (#75)

[0.6.1] - 2025-04-03

Bug fixes

  • Fixed a missing dependency on the requests package.

[0.6.0] - 2025-04-01

New features

  • New content_pdf_file() and content_pdf_url() allow you to upload PDFs to supported models. (#74)

Improvements

  • Turn and Content now inherit from pydantic.BaseModel to provide easier saving to and loading from JSON. (#72)

[0.5.0] - 2025-03-18

New features

  • Added a ChatSnowflake() class to interact with Snowflake Cortex LLM. (#54)
  • Added a ChatAuto() class, allowing for configuration of chat providers and models via environment variables. (#38, thanks @mconflitti-pbc)

Improvements

  • Updated ChatAnthropic()’s model default to "claude-3-7-sonnet-latest". (#62)
  • The version is now accessible as chatlas.__version__. (#64)
  • All provider-specific Chat subclasses now have an associated extras in chatlas. For example, ChatOpenAI has chatlas[openai], ChatPerplexity has chatlas[perplexity], ChatBedrockAnthropic has chatlas[bedrock-anthropic], and so forth for the other Chat classes. (#66)

Bug fixes

  • Fixed an issue with content getting duplicated when it overflows in a Live() console. (#71)
  • Fix an issue with tool calls not working with ChatVertex(). (#61)

[0.4.0] - 2025-02-19

New features

  • Added a ChatVertex() class to interact with Google Cloud’s Vertex AI. (#50)
  • Added .app(*, echo=) support. This allows for chatlas to change the echo behavior when running the Shiny app. (#31)

Improvements

  • Migrated ChatGoogle()’s underlying python SDK from google-generative to google-genai. As a result, streaming tools are now working properly. (#50)

Bug fixes

  • Fixed a bug where synchronous chat tools would not work properly when used in a _async() context. (#56)
  • Fix broken Chat’s Shiny app when .app(*, stream=True) by using async chat tools. (#31)
  • Update formatting of exported markdown to use repr() instead of str() when exporting tool call results. (#30)

[0.3.0] - 2024-12-20

New features

  • Chat’s .tokens() method gains a values argument. Set it to "discrete" to get a result that can be summed to determine the token cost of submitting the current turns. The default ("cumulative"), remains the same (the result can be summed to determine the overall token cost of the conversation).
  • Chat gains a .token_count() method to help estimate token cost of new input. (#23)

Bug fixes

  • ChatOllama no longer fails when a OPENAI_API_KEY environment variable is not set.
  • ChatOpenAI now correctly includes the relevant detail on ContentImageRemote() input.
  • ChatGoogle now correctly logs its token_usage(). (#23)

[0.2.0] - 2024-12-11

First stable release of chatlas, see the website to learn more https://posit-dev.github.io/chatlas/