Skip to content

Releases: jackmpcollins/magentic

v0.34.1

01 Dec 08:10
Compare
Choose a tag to compare

What's Changed

Full Changelog: v0.34.0...v0.34.1

v0.34.0

30 Nov 09:34
Compare
Choose a tag to compare

What's Changed

Add StreamedResponse and AsyncStreamedResponse to enable parsing responses that contain both text and tool calls. See PR #383 or the new docs (copied below) https://magentic.dev/streaming/#StreamedResponse for more details.

⚡ StreamedResponse

Some LLMs have the ability to generate text output and make tool calls in the same response. This allows them to perform chain-of-thought reasoning or provide additional context to the user. In magentic, the StreamedResponse (or AsyncStreamedResponse) class can be used to request this type of output. This object is an iterable of StreamedStr (or AsyncStreamedStr) and FunctionCall instances.

!!! warning "Consuming StreamedStr"

The StreamedStr object must be iterated over before the next item in the `StreamedResponse` is processed, otherwise the string output will be lost. This is because the `StreamedResponse` and `StreamedStr` share the same underlying generator, so advancing the `StreamedResponse` iterator skips over the `StreamedStr` items. The `StreamedStr` object has internal caching so after iterating over it once the chunks will remain available.

In the example below, we request that the LLM generates a greeting and then calls a function to get the weather for two cities. The StreamedResponse object is then iterated over to print the output, and the StreamedStr and FunctionCall items are processed separately.

from magentic import prompt, FunctionCall, StreamedResponse, StreamedStr


def get_weather(city: str) -> str:
    return f"The weather in {city} is 20°C."


@prompt(
    "Say hello, then get the weather for: {cities}",
    functions=[get_weather],
)
def describe_weather(cities: list[str]) -> StreamedResponse: ...


response = describe_weather(["Cape Town", "San Francisco"])
for item in response:
    if isinstance(item, StreamedStr):
        for chunk in item:
            # print the chunks as they are received
            print(chunk, sep="", end="")
        print()
    if isinstance(item, FunctionCall):
        # print the function call, then call it and print the result
        print(item)
        print(item())

# Hello! I'll get the weather for Cape Town and San Francisco for you.
# FunctionCall(<function get_weather at 0x1109825c0>, 'Cape Town')
# The weather in Cape Town is 20°C.
# FunctionCall(<function get_weather at 0x1109825c0>, 'San Francisco')
# The weather in San Francisco is 20°C.

PRs

Full Changelog: v0.33.0...v0.34.0

v0.33.0

29 Nov 08:41
Compare
Choose a tag to compare

What's Changed

Warning

Breaking change: The prompt-function return type and the output_types argument to ChatModel must now contain FunctionCall or (Async)ParallelFunctionCall if these return types are desired. Previously instances of these types could be returned even if they were not indicated in the output types.

  • Dependency updates
  • Improve development workflows
  • Big internal refactor to prepare for future features. See PR #380 for details.

PRs

Full Changelog: v0.32.0...v0.33.0

v0.32.0

18 Aug 09:16
Compare
Choose a tag to compare

What's Changed

Add support for OpenAI "strict" setting for structured outputs. This guarantees that the generated JSON schema matches that supplied by the user. In magentic, this is set via an extension of pydantic's ConfigDict, and works for pydantic models as well as functions. See the docs for more info https://magentic.dev/structured-outputs/#configdict

For a BaseModel

from magentic import prompt, ConfigDict
from pydantic import BaseModel


class Superhero(BaseModel):
    model_config = ConfigDict(openai_strict=True)

    name: str
    age: int
    power: str
    enemies: list[str]


@prompt("Create a Superhero named {name}.")
def create_superhero(name: str) -> Superhero: ...


create_superhero("Garden Man")

For a function

from typing import Annotated, Literal

from magentic import ConfigDict, with_config
from pydantic import Field


@with_config(ConfigDict(openai_strict=True))
def activate_oven(
    temperature: Annotated[int, Field(description="Temp in Fahrenheit", lt=500)],
    mode: Literal["broil", "bake", "roast"],
) -> str:
    """Turn the oven on with the provided settings."""
    return f"Preheating to {temperature} F with mode {mode}"


@prompt(
    "Do some cooking",
    functions=[
        activate_oven,
        # ...

PRs

Full Changelog: v0.31.0...v0.32.0

v0.31.0

13 Aug 07:29
Compare
Choose a tag to compare

What's Changed

Full Changelog: v0.30.0...v0.31.0

v0.30.0

12 Aug 07:30
Compare
Choose a tag to compare

What's Changed

Warning

Breaking change: StructuredOutputError has been replaced by more specific exceptions StringNotAllowedError and ToolSchemaParseError in PR #288

🤖 ♻️ LLM-Assisted retries has been added. When enabled, this sends incorrectly formatted output back to the LLM along with the error message to have the LLM fix its mistakes. This can be used to enforce more complex validation on output schemas using pydantic validators.

For example, placing an arbitrary constraint on a string field

from typing import Annotated

from magentic import prompt
from pydantic import AfterValidator, BaseModel


def assert_is_ireland(v: str) -> str:
    if v != "Ireland":
        raise ValueError("Country must be Ireland")
    return v


class Country(BaseModel):
    name: Annotated[str, AfterValidator(assert_is_ireland)]
    capital: str


@prompt(
    "Return a country",
    max_retries=3,
)
def get_country() -> Country: ...


get_country()
# 05:13:55.607 Calling prompt-function get_country
# 05:13:55.622   LLM-assisted retries enabled. Max 3
# 05:13:55.627     Chat Completion with 'gpt-4o' [LLM]
# 05:13:56.309     streaming response from 'gpt-4o' took 0.11s [LLM]
# 05:13:56.310     Retrying Chat Completion. Attempt 1.
# 05:13:56.322     Chat Completion with 'gpt-4o' [LLM]
# 05:13:57.456     streaming response from 'gpt-4o' took 0.00s [LLM]
#
# Country(name='Ireland', capital='Dublin')

See the new docs page on Retrying for more info.

PRs

Full Changelog: v0.29.0...v0.30.0

v0.29.0

08 Aug 06:29
Compare
Choose a tag to compare

What's Changed

This means Message objects can be used anywhere pydantic models can, including in prompt-functions. The new AnyMessage type simplifies this. For example

from magentic import AnyMessage, prompt

@prompt("Create an example of few-shot prompting for a chatbot")
def make_few_shot_prompt() -> list[AnyMessage]: ...

make_few_shot_prompt()
# [SystemMessage('You are a helpful and knowledgeable assistant.'),
#  UserMessage('What’s the weather like today?'),
#  AssistantMessage[Any]('The weather today is sunny with a high of 75°F (24°C).'),
#  UserMessage('Can you explain the theory of relativity in simple terms?'),
#  AssistantMessage[Any]('Sure! The theory of relativity, developed by Albert Einstein,  ...]

Dependabot

Full Changelog: v0.28.1...v0.29.0

v0.28.1

29 Jul 06:26
Compare
Choose a tag to compare

What's Changed

Full Changelog: v0.28.0...v0.28.1

v0.28.0

15 Jul 06:59
Compare
Choose a tag to compare

What's Changed

🪵 🔥 Logfire / OpenTelemetry now supported!

This makes it much easier to follow what tool calls are being made by the LLM both as printed output locally and in Logfire or another monitoring service. It also lets you see the raw requests being sent to OpenAI/Anthropic so you can more easily debug issues.

All it takes to get set up is

pip install logfire
import logfire

logfire.configure(send_to_logfire=False)  # Or True to use the Logfire service
logfire.instrument_openai()  # optional, to trace OpenAI API calls
# logfire.instrument_anthropic()  # optional, to trace Anthropic API calls

Check out the new docs page: https://magentic.dev/logging-and-tracing/

PRs

Full Changelog: v0.27.0...v0.28.0

v0.27.0

20 Jun 07:09
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v0.26.0...v0.27.0