github shcherbak-ai/contextgem v1.4.0

latest release: v1.4.1
3 hours ago

[1.4.0] - 2026-10-26

Added

  • Added ToolCallError, which can be imported from contextgem: the exception that a tool function raises to tell the LLM why a call failed, e.g. that no record has the ID that the LLM passed. Its message is sent to the LLM as the result of the tool call, and the LLM can call the tool again (see "Changed"). See Errors in tool calls.

Changed

  • An exception that a tool function raises, other than ToolCallError, now stops the chat: it is raised from chat() and chat_async() as it is, with a note that names the tool, no further requests are sent, and no fallback LLM is tried. Its message was sent to the LLM, as the result of the tool call, and the chat went on, so that a bug in a tool, e.g. an error of a database, went unnoticed but in the answer, and its message, which can hold anything, reached the LLM. To send an error to the LLM, raise ToolCallError with a message written for it, e.g. raise ToolCallError(f"No order has the ID {order_id!r}"), where the function raised another exception for the LLM. The result of the tool call that the LLM receives for it has "error": "ToolCallError", where it had "error": "ToolExecutionError": change a stand-in for the LLM in your tests that checks it.
  • A sync tool function now runs in a worker thread, as asyncio.to_thread() runs it, where it ran in the event loop of the chat and blocked it: in chat_async(), the other tasks of the event loop of your code now run while it does, and with parallel_tool_calls=True, the sync tool calls of a response now run at the same time, where they ran one after another. No event loop runs in its thread, so that it can use a library that refuses to run where one is running, e.g. the ORM of Django, which raised SynchronousOnlyOperation in a tool. The function no longer runs in the thread of the code that calls chat(): it does not see what that code keeps for its thread, e.g. an open database transaction, while it gets its context variables. A function that changes state that parallel tool calls share must be thread-safe. See Sync and async tool functions.
  • A message sent on a ChatSession while it answers another, e.g. from another task with asyncio.gather() or from another thread, now raises ValueError, before any request is sent. Such turns were answered at the same time: the turn sent second was also sent the message of the first, which had no answer yet, the history kept the two messages one after the other and then the two answers, and the tool calls of one turn were lost, or kept in the history with the answer of the other. Where two messages of a conversation can come at once, e.g. in a server, wait for the answer to one before sending the next, e.g. with a lock for each session: an asyncio.Lock where the messages come from the tasks of one event loop, a threading.Lock where they come from threads.

Fixed

  • The description of a tool no longer splits a first sentence that goes on in the next line of the docstring: the first line was taken as the summary and the rest as the description, which were joined with a blank line, so that the LLM was sent e.g. Looks up an order: its status and\n\ndelivery date.. The first paragraph is now kept together. Only the tool descriptions of such docstrings change.
  • register_tool() now raises TypeError for a bound method or a functools.partial, which cannot be registered, with what to do instead: a function that uses the object, e.g. a closure. A bound method raised AttributeError ('method' object has no attribute '__contextgem_tool__'), and a partial an error on its parameters or on the name of the tool.

Don't miss a new contextgem release

NewReleases is sending notifications on new releases.