[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-10726":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":17,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":18,"rankGlobal":10,"rankLanguage":10,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":20,"hasPages":22,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":37,"readmeContent":38,"aiSummary":39,"trendingCount":16,"starSnapshotCount":16,"syncStatus":40,"lastSyncTime":41,"discoverSource":42},10726,"magentic","jackmpcollins\u002Fmagentic","jackmpcollins","Seamlessly integrate LLMs as Python functions","https:\u002F\u002Fmagentic.dev\u002F",null,"Python",2411,127,10,40,0,6,28.32,"MIT License",false,"main",true,[24,25,26,27,28,29,30,31,5,32,33,34,35,36],"agent","agentic","ai","chatbot","chatgpt","gpt","llm","magenta","magnetic","openai","openai-api","prompt","pydantic","2026-06-12 02:02:25","# magentic\n\nSeamlessly integrate Large Language Models into Python code. Use the `@prompt` and `@chatprompt` decorators to create functions that return structured output from an LLM. Combine LLM queries and tool use with traditional Python code to build complex agentic systems.\n\n## Features\n\n- [Structured Outputs] using pydantic models and built-in python types.\n- [Streaming] of structured outputs and function calls, to use them while being generated.\n- [LLM-Assisted Retries] to improve LLM adherence to complex output schemas.\n- [Observability] using OpenTelemetry, with native [Pydantic Logfire integration].\n- [Type Annotations] to work nicely with linters and IDEs.\n- [Configuration] options for multiple LLM providers including OpenAI, Anthropic, and Ollama.\n- Many more features: [Chat Prompting], [Parallel Function Calling], [Vision], [Formatting], [Asyncio]...\n\n## Installation\n\n```sh\npip install magentic\n```\n\nor using uv\n\n```sh\nuv add magentic\n```\n\nConfigure your OpenAI API key by setting the `OPENAI_API_KEY` environment variable. To configure a different LLM provider see [Configuration] for more.\n\n## Usage\n\n### @prompt\n\nThe `@prompt` decorator allows you to define a template for a Large Language Model (LLM) prompt as a Python function. When this function is called, the arguments are inserted into the template, then this prompt is sent to an LLM which generates the function output.\n\n```python\nfrom magentic import prompt\n\n\n@prompt('Add more \"dude\"ness to: {phrase}')\ndef dudeify(phrase: str) -> str: ...  # No function body as this is never executed\n\n\ndudeify(\"Hello, how are you?\")\n# \"Hey, dude! What's up? How's it going, my man?\"\n```\n\nThe `@prompt` decorator will respect the return type annotation of the decorated function. This can be [any type supported by pydantic](https:\u002F\u002Fdocs.pydantic.dev\u002Flatest\u002Fusage\u002Ftypes\u002Ftypes\u002F) including a `pydantic` model.\n\n```python\nfrom magentic import prompt\nfrom pydantic import BaseModel\n\n\nclass Superhero(BaseModel):\n    name: str\n    age: int\n    power: str\n    enemies: list[str]\n\n\n@prompt(\"Create a Superhero named {name}.\")\ndef create_superhero(name: str) -> Superhero: ...\n\n\ncreate_superhero(\"Garden Man\")\n# Superhero(name='Garden Man', age=30, power='Control over plants', enemies=['Pollution Man', 'Concrete Woman'])\n```\n\nSee [Structured Outputs] for more.\n\n### @chatprompt\n\nThe `@chatprompt` decorator works just like `@prompt` but allows you to pass chat messages as a template rather than a single text prompt. This can be used to provide a system message or for few-shot prompting where you provide example responses to guide the model's output. Format fields denoted by curly braces `{example}` will be filled in all messages (except `FunctionResultMessage`).\n\n```python\nfrom magentic import chatprompt, AssistantMessage, SystemMessage, UserMessage\nfrom pydantic import BaseModel\n\n\nclass Quote(BaseModel):\n    quote: str\n    character: str\n\n\n@chatprompt(\n    SystemMessage(\"You are a movie buff.\"),\n    UserMessage(\"What is your favorite quote from Harry Potter?\"),\n    AssistantMessage(\n        Quote(\n            quote=\"It does not do to dwell on dreams and forget to live.\",\n            character=\"Albus Dumbledore\",\n        )\n    ),\n    UserMessage(\"What is your favorite quote from {movie}?\"),\n)\ndef get_movie_quote(movie: str) -> Quote: ...\n\n\nget_movie_quote(\"Iron Man\")\n# Quote(quote='I am Iron Man.', character='Tony Stark')\n```\n\nSee [Chat Prompting] for more.\n\n### FunctionCall\n\nAn LLM can also decide to call functions. In this case the `@prompt`-decorated function returns a `FunctionCall` object which can be called to execute the function using the arguments provided by the LLM.\n\n```python\nfrom typing import Literal\n\nfrom magentic import prompt, FunctionCall\n\n\ndef search_twitter(query: str, category: Literal[\"latest\", \"people\"]) -> str:\n    \"\"\"Searches Twitter for a query.\"\"\"\n    print(f\"Searching Twitter for {query!r} in category {category!r}\")\n    return \"\u003Ctwitter results>\"\n\n\ndef search_youtube(query: str, channel: str = \"all\") -> str:\n    \"\"\"Searches YouTube for a query.\"\"\"\n    print(f\"Searching YouTube for {query!r} in channel {channel!r}\")\n    return \"\u003Cyoutube results>\"\n\n\n@prompt(\n    \"Use the appropriate search function to answer: {question}\",\n    functions=[search_twitter, search_youtube],\n)\ndef perform_search(question: str) -> FunctionCall[str]: ...\n\n\noutput = perform_search(\"What is the latest news on LLMs?\")\nprint(output)\n# > FunctionCall(\u003Cfunction search_twitter at 0x10c367d00>, 'LLMs', 'latest')\noutput()\n# > Searching Twitter for 'Large Language Models news' in category 'latest'\n# '\u003Ctwitter results>'\n```\n\nSee [Function Calling] for more.\n\n### @prompt_chain\n\nSometimes the LLM requires making one or more function calls to generate a final answer. The `@prompt_chain` decorator will resolve `FunctionCall` objects automatically and pass the output back to the LLM to continue until the final answer is reached.\n\nIn the following example, when `describe_weather` is called the LLM first calls the `get_current_weather` function, then uses the result of this to formulate its final answer which gets returned.\n\n```python\nfrom magentic import prompt_chain\n\n\ndef get_current_weather(location, unit=\"fahrenheit\"):\n    \"\"\"Get the current weather in a given location\"\"\"\n    # Pretend to query an API\n    return {\"temperature\": \"72\", \"forecast\": [\"sunny\", \"windy\"]}\n\n\n@prompt_chain(\n    \"What's the weather like in {city}?\",\n    functions=[get_current_weather],\n)\ndef describe_weather(city: str) -> str: ...\n\n\ndescribe_weather(\"Boston\")\n# 'The current weather in Boston is 72°F and it is sunny and windy.'\n```\n\nLLM-powered functions created using `@prompt`, `@chatprompt` and `@prompt_chain` can be supplied as `functions` to other `@prompt`\u002F`@prompt_chain` decorators, just like regular python functions. This enables increasingly complex LLM-powered functionality, while allowing individual components to be tested and improved in isolation.\n\n\u003C!-- Links -->\n\n[Structured Outputs]: https:\u002F\u002Fmagentic.dev\u002Fstructured-outputs\n[Chat Prompting]: https:\u002F\u002Fmagentic.dev\u002Fchat-prompting\n[Function Calling]: https:\u002F\u002Fmagentic.dev\u002Ffunction-calling\n[Parallel Function Calling]: https:\u002F\u002Fmagentic.dev\u002Ffunction-calling\u002F#parallelfunctioncall\n[Observability]: https:\u002F\u002Fmagentic.dev\u002Flogging-and-tracing\n[Pydantic Logfire integration]: https:\u002F\u002Flogfire.pydantic.dev\u002Fdocs\u002Fintegrations\u002Fthird-party\u002Fmagentic\u002F\n[Formatting]: https:\u002F\u002Fmagentic.dev\u002Fformatting\n[Asyncio]: https:\u002F\u002Fmagentic.dev\u002Fasyncio\n[Streaming]: https:\u002F\u002Fmagentic.dev\u002Fstreaming\n[Vision]: https:\u002F\u002Fmagentic.dev\u002Fvision\n[LLM-Assisted Retries]: https:\u002F\u002Fmagentic.dev\u002Fretrying.md\n[Configuration]: https:\u002F\u002Fmagentic.dev\u002Fconfiguration\n[Type Annotations]: https:\u002F\u002Fmagentic.dev\u002Ftype-checking\n\n\n### Streaming\n\nThe `StreamedStr` (and `AsyncStreamedStr`) class can be used to stream the output of the LLM. This allows you to process the text while it is being generated, rather than receiving the whole output at once.\n\n```python\nfrom magentic import prompt, StreamedStr\n\n\n@prompt(\"Tell me about {country}\")\ndef describe_country(country: str) -> StreamedStr: ...\n\n\n# Print the chunks while they are being received\nfor chunk in describe_country(\"Brazil\"):\n    print(chunk, end=\"\")\n# 'Brazil, officially known as the Federative Republic of Brazil, is ...'\n```\n\nMultiple `StreamedStr` can be created at the same time to stream LLM outputs concurrently. In the below example, generating the description for multiple countries takes approximately the same amount of time as for a single country.\n\n```python\nfrom time import time\n\ncountries = [\"Australia\", \"Brazil\", \"Chile\"]\n\n\n# Generate the descriptions one at a time\nstart_time = time()\nfor country in countries:\n    # Converting `StreamedStr` to `str` blocks until the LLM output is fully generated\n    description = str(describe_country(country))\n    print(f\"{time() - start_time:.2f}s : {country} - {len(description)} chars\")\n\n# 22.72s : Australia - 2130 chars\n# 41.63s : Brazil - 1884 chars\n# 74.31s : Chile - 2968 chars\n\n\n# Generate the descriptions concurrently by creating the StreamedStrs at the same time\nstart_time = time()\nstreamed_strs = [describe_country(country) for country in countries]\nfor country, streamed_str in zip(countries, streamed_strs):\n    description = str(streamed_str)\n    print(f\"{time() - start_time:.2f}s : {country} - {len(description)} chars\")\n\n# 22.79s : Australia - 2147 chars\n# 23.64s : Brazil - 2202 chars\n# 24.67s : Chile - 2186 chars\n```\n\n### Object Streaming\n\nStructured outputs can also be streamed from the LLM by using the return type annotation `Iterable` (or `AsyncIterable`). This allows each item to be processed while the next one is being generated.\n\n```python\nfrom collections.abc import Iterable\nfrom time import time\n\nfrom magentic import prompt\nfrom pydantic import BaseModel\n\n\nclass Superhero(BaseModel):\n    name: str\n    age: int\n    power: str\n    enemies: list[str]\n\n\n@prompt(\"Create a Superhero team named {name}.\")\ndef create_superhero_team(name: str) -> Iterable[Superhero]: ...\n\n\nstart_time = time()\nfor hero in create_superhero_team(\"The Food Dudes\"):\n    print(f\"{time() - start_time:.2f}s : {hero}\")\n\n# 2.23s : name='Pizza Man' age=30 power='Can shoot pizza slices from his hands' enemies=['The Hungry Horde', 'The Junk Food Gang']\n# 4.03s : name='Captain Carrot' age=35 power='Super strength and agility from eating carrots' enemies=['The Sugar Squad', 'The Greasy Gang']\n# 6.05s : name='Ice Cream Girl' age=25 power='Can create ice cream out of thin air' enemies=['The Hot Sauce Squad', 'The Healthy Eaters']\n```\n\nSee [Streaming] for more.\n\n### Asyncio\n\nAsynchronous functions \u002F coroutines can be used to concurrently query the LLM. This can greatly increase the overall speed of generation, and also allow other asynchronous code to run while waiting on LLM output. In the below example, the LLM generates a description for each US president while it is waiting on the next one in the list. Measuring the characters generated per second shows that this example achieves a 7x speedup over serial processing.\n\n```python\nimport asyncio\nfrom time import time\nfrom typing import AsyncIterable\n\nfrom magentic import prompt\n\n\n@prompt(\"List ten presidents of the United States\")\nasync def iter_presidents() -> AsyncIterable[str]: ...\n\n\n@prompt(\"Tell me more about {topic}\")\nasync def tell_me_more_about(topic: str) -> str: ...\n\n\n# For each president listed, generate a description concurrently\nstart_time = time()\ntasks = []\nasync for president in await iter_presidents():\n    # Use asyncio.create_task to schedule the coroutine for execution before awaiting it\n    # This way descriptions will start being generated while the list of presidents is still being generated\n    task = asyncio.create_task(tell_me_more_about(president))\n    tasks.append(task)\n\ndescriptions = await asyncio.gather(*tasks)\n\n# Measure the characters per second\ntotal_chars = sum(len(desc) for desc in descriptions)\ntime_elapsed = time() - start_time\nprint(total_chars, time_elapsed, total_chars \u002F time_elapsed)\n# 24575 28.70 856.07\n\n\n# Measure the characters per second to describe a single president\nstart_time = time()\nout = await tell_me_more_about(\"George Washington\")\ntime_elapsed = time() - start_time\nprint(len(out), time_elapsed, len(out) \u002F time_elapsed)\n# 2206 18.72 117.78\n```\n\nSee [Asyncio] for more.\n\n### Additional Features\n\n- The `functions` argument to `@prompt` can contain async\u002Fcoroutine functions. When the corresponding `FunctionCall` objects are called the result must be awaited.\n- The `Annotated` type annotation can be used to provide descriptions and other metadata for function parameters. See [the pydantic documentation on using `Field` to describe function arguments](https:\u002F\u002Fdocs.pydantic.dev\u002Flatest\u002Fusage\u002Fvalidation_decorator\u002F#using-field-to-describe-function-arguments).\n- The `@prompt` and `@prompt_chain` decorators also accept a `model` argument. You can pass an instance of `OpenaiChatModel` to use GPT4 or configure a different temperature. See below.\n- Register other types to use as return type annotations in `@prompt` functions by following [the example notebook for a Pandas DataFrame](examples\u002Fcustom_function_schemas\u002Fregister_dataframe_function_schema.ipynb).\n\n## Backend\u002FLLM Configuration\n\nMagentic supports multiple LLM providers or \"backends\". This roughly refers to which Python package is used to interact with the LLM API. The following backends are supported.\n\n### OpenAI\n\nThe default backend, using the `openai` Python package and supports all features of magentic.\n\nNo additional installation is required. Just import the `OpenaiChatModel` class from `magentic`.\n\n```python\nfrom magentic import OpenaiChatModel\n\nmodel = OpenaiChatModel(\"gpt-4o\")\n```\n\n#### Ollama via OpenAI\n\nOllama supports an OpenAI-compatible API, which allows you to use Ollama models via the OpenAI backend.\n\nFirst, install ollama from [ollama.com](https:\u002F\u002Follama.com\u002F). Then, pull the model you want to use.\n\n```sh\nollama pull llama3.2\n```\n\nThen, specify the model name and `base_url` when creating the `OpenaiChatModel` instance.\n\n```python\nfrom magentic import OpenaiChatModel\n\nmodel = OpenaiChatModel(\"llama3.2\", base_url=\"http:\u002F\u002Flocalhost:11434\u002Fv1\u002F\")\n```\n\n#### Other OpenAI-compatible APIs\n\nWhen using the `openai` backend, setting the `MAGENTIC_OPENAI_BASE_URL` environment variable or using `OpenaiChatModel(..., base_url=\"http:\u002F\u002Flocalhost:8080\")` in code allows you to use `magentic` with any OpenAI-compatible API e.g. [Azure OpenAI Service](https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fai-services\u002Fopenai\u002Fquickstart?tabs=command-line&pivots=programming-language-python#create-a-new-python-application), [LiteLLM OpenAI Proxy Server](https:\u002F\u002Fdocs.litellm.ai\u002Fdocs\u002Fproxy_server), [LocalAI](https:\u002F\u002Flocalai.io\u002Fhowtos\u002Feasy-request-openai\u002F). Note that if the API does not support tool calls then you will not be able to create prompt-functions that return Python objects, but other features of `magentic` will still work.\n\nTo use Azure with the openai backend you will need to set the `MAGENTIC_OPENAI_API_TYPE` environment variable to \"azure\" or use `OpenaiChatModel(..., api_type=\"azure\")`, and also set the environment variables needed by the openai package to access Azure. See https:\u002F\u002Fgithub.com\u002Fopenai\u002Fopenai-python#microsoft-azure-openai\n\n### Anthropic\n\nThis uses the `anthropic` Python package and supports all features of magentic.\n\nInstall the `magentic` package with the `anthropic` extra, or install the `anthropic` package directly.\n\n```sh\npip install \"magentic[anthropic]\"\n```\n\nThen import the `AnthropicChatModel` class.\n\n```python\nfrom magentic.chat_model.anthropic_chat_model import AnthropicChatModel\n\nmodel = AnthropicChatModel(\"claude-3-5-sonnet-latest\")\n```\n\n### LiteLLM\n\nThis uses the `litellm` Python package to enable querying LLMs from [many different providers](https:\u002F\u002Fdocs.litellm.ai\u002Fdocs\u002Fproviders). Note: some models may not support all features of `magentic` e.g. function calling\u002Fstructured output and streaming.\n\nInstall the `magentic` package with the `litellm` extra, or install the `litellm` package directly.\n\n```sh\npip install \"magentic[litellm]\"\n```\n\nThen import the `LitellmChatModel` class.\n\n```python\nfrom magentic.chat_model.litellm_chat_model import LitellmChatModel\n\nmodel = LitellmChatModel(\"gpt-4o\")\n```\n\n### Mistral\n\nThis uses the `openai` Python package with some small modifications to make the API queries compatible with the Mistral API. It supports all features of magentic. However tool calls (including structured outputs) are not streamed so are received all at once.\n\nNote: a future version of magentic might switch to using the `mistral` Python package.\n\nNo additional installation is required. Just import the `MistralChatModel` class.\n\n```python\nfrom magentic.chat_model.mistral_chat_model import MistralChatModel\n\nmodel = MistralChatModel(\"mistral-large-latest\")\n```\n\n## Configure a Backend\n\nThe default `ChatModel` used by `magentic` (in `@prompt`, `@chatprompt`, etc.) can be configured in several ways. When a prompt-function or chatprompt-function is called, the `ChatModel` to use follows this order of preference\n\n1. The `ChatModel` instance provided as the `model` argument to the magentic decorator\n1. The current chat model context, created using `with MyChatModel:`\n1. The global `ChatModel` created from environment variables and the default settings in [src\u002Fmagentic\u002Fsettings.py](https:\u002F\u002Fgithub.com\u002Fjackmpcollins\u002Fmagentic\u002Fblob\u002Fmain\u002Fsrc\u002Fmagentic\u002Fsettings.py)\n\nThe following code snippet demonstrates this behavior:\n\n```python\nfrom magentic import OpenaiChatModel, prompt\nfrom magentic.chat_model.anthropic_chat_model import AnthropicChatModel\n\n\n@prompt(\"Say hello\")\ndef say_hello() -> str: ...\n\n\n@prompt(\n    \"Say hello\",\n    model=AnthropicChatModel(\"claude-3-5-sonnet-latest\"),\n)\ndef say_hello_anthropic() -> str: ...\n\n\nsay_hello()  # Uses env vars or default settings\n\nwith OpenaiChatModel(\"gpt-4o-mini\", temperature=1):\n    say_hello()  # Uses openai with gpt-4o-mini and temperature=1 due to context manager\n    say_hello_anthropic()  # Uses Anthropic claude-3-5-sonnet-latest because explicitly configured\n```\n\nThe following environment variables can be set.\n\n| Environment Variable           | Description                              | Example                      |\n| ------------------------------ | ---------------------------------------- | ---------------------------- |\n| MAGENTIC_BACKEND               | The package to use as the LLM backend    | anthropic \u002F openai \u002F litellm |\n| MAGENTIC_ANTHROPIC_MODEL       | Anthropic model                          | claude-3-haiku-20240307      |\n| MAGENTIC_ANTHROPIC_API_KEY     | Anthropic API key to be used by magentic | sk-...                       |\n| MAGENTIC_ANTHROPIC_BASE_URL    | Base URL for an Anthropic-compatible API | http:\u002F\u002Flocalhost:8080        |\n| MAGENTIC_ANTHROPIC_MAX_TOKENS  | Max number of generated tokens           | 1024                         |\n| MAGENTIC_ANTHROPIC_TEMPERATURE | Temperature                              | 0.5                          |\n| MAGENTIC_LITELLM_MODEL         | LiteLLM model                            | claude-2                     |\n| MAGENTIC_LITELLM_API_BASE      | The base url to query                    | http:\u002F\u002Flocalhost:11434       |\n| MAGENTIC_LITELLM_MAX_TOKENS    | LiteLLM max number of generated tokens   | 1024                         |\n| MAGENTIC_LITELLM_TEMPERATURE   | LiteLLM temperature                      | 0.5                          |\n| MAGENTIC_MISTRAL_MODEL         | Mistral model                            | mistral-large-latest         |\n| MAGENTIC_MISTRAL_API_KEY       | Mistral API key to be used by magentic   | XEG...                       |\n| MAGENTIC_MISTRAL_BASE_URL      | Base URL for an Mistral-compatible API   | http:\u002F\u002Flocalhost:8080        |\n| MAGENTIC_MISTRAL_MAX_TOKENS    | Max number of generated tokens           | 1024                         |\n| MAGENTIC_MISTRAL_SEED          | Seed for deterministic sampling          | 42                           |\n| MAGENTIC_MISTRAL_TEMPERATURE   | Temperature                              | 0.5                          |\n| MAGENTIC_OPENAI_MODEL          | OpenAI model                             | gpt-4                        |\n| MAGENTIC_OPENAI_API_KEY        | OpenAI API key to be used by magentic    | sk-...                       |\n| MAGENTIC_OPENAI_API_TYPE       | Allowed options: \"openai\", \"azure\"       | azure                        |\n| MAGENTIC_OPENAI_BASE_URL       | Base URL for an OpenAI-compatible API    | http:\u002F\u002Flocalhost:8080        |\n| MAGENTIC_OPENAI_MAX_TOKENS     | OpenAI max number of generated tokens    | 1024                         |\n| MAGENTIC_OPENAI_SEED           | Seed for deterministic sampling          | 42                           |\n| MAGENTIC_OPENAI_TEMPERATURE    | OpenAI temperature                       | 0.5                          |\n\n## Type Checking\n\nMany type checkers will raise warnings or errors for functions with the `@prompt` decorator due to the function having no body or return value. There are several ways to deal with these.\n\n1. Disable the check globally for the type checker. For example in mypy by disabling error code `empty-body`.\n   ```toml\n   # pyproject.toml\n   [tool.mypy]\n   disable_error_code = [\"empty-body\"]\n   ```\n1. Make the function body `...` (this does not satisfy mypy) or `raise`.\n   ```python\n   @prompt(\"Choose a color\")\n   def random_color() -> str: ...\n   ```\n1. Use comment `# type: ignore[empty-body]` on each function. In this case you can add a docstring instead of `...`.\n   ```python\n   @prompt(\"Choose a color\")\n   def random_color() -> str:  # type: ignore[empty-body]\n       \"\"\"Returns a random color.\"\"\"\n   ```\n","Magentic 是一个用于将大型语言模型无缝集成到Python代码中的库。它通过`@prompt`和`@chatprompt`装饰器，允许开发者创建能够从LLM获取结构化输出的函数，并结合传统Python代码构建复杂的代理系统。该项目支持使用Pydantic模型定义结构化输出、流式处理、基于LLM的帮助重试机制以及OpenTelemetry的可观测性等功能。适用于需要在应用程序中灵活调用AI能力以增强用户体验或自动化流程的各种场景，如聊天机器人开发、智能助手设计等。",2,"2026-06-11 03:29:53","top_topic"]