-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add support for GPT-5 Free-Form Function Calling and Context Free Grammar constraints over tools #2572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
matthewfranglen
wants to merge
89
commits into
pydantic:main
from
matthewfranglen:freeform-and-cfg-tools
Closed
Add support for GPT-5 Free-Form Function Calling and Context Free Grammar constraints over tools #2572
Changes from all commits
Commits
Show all changes
89 commits
Select commit
Hold shift + click to select a range
c713a6e
infer custom tool format from schema
matthewfranglen de3bc18
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen e7ca5ca
update free_form to be a parameter, set parallel_tool_calls
matthewfranglen f0a5cbe
Map the response type
matthewfranglen 68fc7cf
Fix assertion ordering, remove some intermediate variables
matthewfranglen 5d9af16
add free_form output
matthewfranglen 3687580
add context free grammar to free form function calling
matthewfranglen 61f7291
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen 7a869b9
get the grammar working on the output
matthewfranglen 5e8cef9
use FunctionTextFormat object to hold fffc/cfg settings
matthewfranglen 79b519a
add literal text as an option for text_format
matthewfranglen 991c01d
remove parameter added in error
matthewfranglen c70bc1d
address some of the pyright errors
matthewfranglen 9586e6c
remove default value
matthewfranglen 0b47135
drop pedantic check
matthewfranglen 92db07a
update snapshots
matthewfranglen 3e605e0
update docstrings
matthewfranglen ab45262
update snapshots
matthewfranglen 3982f32
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen f3595b3
work on tests
matthewfranglen bef5dec
reviewing some of the new tests
matthewfranglen 21e1a0b
typing
matthewfranglen cfcf7cf
update snapshots
matthewfranglen 1c5c500
more generated tests
matthewfranglen e0017b4
fix up tests for tools.py
matthewfranglen 307b011
add lark
matthewfranglen 131ab91
use find_spec to see if lark resolves
matthewfranglen b386eb6
add runtime validation of syntax
matthewfranglen 01988a5
Can't throw the exception and maintain coverage
matthewfranglen 88b8b28
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen 3a46eea
revert the uv.lock
matthewfranglen c084523
use keyword arguments
matthewfranglen ef1a696
add missing property decorator
matthewfranglen e3f514d
remove deprecated setting
matthewfranglen 0dbcdaf
update snapshot
matthewfranglen 4533df1
get coverage on the tests up to 100%
matthewfranglen fc477bb
review the openai tests
matthewfranglen 4cacf9b
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen 185c929
fiddling with tests
matthewfranglen a81e7b9
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen 4a6e540
add simple test for output tool
matthewfranglen 8714253
remove utc import
matthewfranglen b55c9ab
use older utc
matthewfranglen b347edc
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen 997d2ac
formatting
matthewfranglen afdd6ef
drop NOT_GIVEN import
matthewfranglen 175eee9
formatting
matthewfranglen 19eb167
address linter errors
matthewfranglen 6e259c8
TypeError: Logfire.instrument_pydantic_ai() got an unexpected keyword…
matthewfranglen 742fb91
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen d69daad
remove the condition over the output tools
matthewfranglen d78a5c2
remove redundant line pragma
matthewfranglen dc1c182
move the no cover line
matthewfranglen d1fb3a4
formatting
matthewfranglen 97b4d82
revert version change
matthewfranglen c54f26e
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen e206e3e
start on documentation
matthewfranglen c483fda
test the examples manually, fix errors
matthewfranglen 5265211
no fancy comma
matthewfranglen f337820
fix bad syntax in example
matthewfranglen 2c7367f
Add section on output tool use
matthewfranglen f3a4afd
double to single quotes
matthewfranglen c4665a2
fix the output_tool
matthewfranglen b86d2b1
actually use a lark grammar
matthewfranglen d713c29
Update docs/models/openai.md
matthewfranglen d0c346c
Update pydantic_ai_slim/pydantic_ai/models/openai.py
matthewfranglen 3037e6e
make the introduction to cfg stronger
matthewfranglen 4e2264d
make FunctionTextFormat directly importable from pydantic_ai
matthewfranglen e28836b
use direct import
matthewfranglen b49cd81
add headings
matthewfranglen a7112f4
of course there was an easier way to do this
matthewfranglen 7c96803
quote coding terms
matthewfranglen 5d2b372
free-form -> freeform
matthewfranglen ec057c8
gpt -> GPT or quoted
matthewfranglen c949c83
free form -> freeform
matthewfranglen 36a0759
Merge branch 'main' into freeform-and-cfg-tools
matthewfranglen ae64d6b
fix imports
matthewfranglen f5ca42e
add missing import
matthewfranglen e7bea60
snapshot updates
matthewfranglen 637774f
update regex match over error message
matthewfranglen 9cf0931
add more known model names
matthewfranglen 8c6c976
fix uv.lock, undo some of the changes
matthewfranglen febe88d
another snapshot update related to uv.lock
matthewfranglen 3106219
text_format text -> plain
matthewfranglen 673ef1e
default to an argument name of input
matthewfranglen 2581873
use !r formatting for tool name
matthewfranglen 9ec5b69
link to best practices
matthewfranglen 3927cf0
FunctionTextFormat -> TextFormat, change handling
matthewfranglen 5990d8b
Update a test to check for unknown tool mapping
matthewfranglen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -202,6 +202,131 @@ print(result2.output) | |
| #> This is an excellent joke invented by Samuel Colvin, it needs no explanation. | ||
| ``` | ||
|
|
||
| ### Freeform Function Calling | ||
|
|
||
| GPT‑5 can now send raw text payloads - anything from Python scripts to SQL queries - to your custom tool without wrapping the data in JSON using freeform function calling. This differs from classic structured function calls, giving you greater flexibility when interacting with external runtimes such as: | ||
|
|
||
| * code execution with sandboxes (Python, C++, Java, …) | ||
| * SQL databases | ||
| * Shell environments | ||
| * Configuration generators | ||
|
|
||
| Note that freeform function calling does NOT support parallel tool calling. | ||
|
|
||
| You can enable freeform function calling for a tool using the `text_format` parameter when creating your tool. To use this the tool must take a single string argument (other than the runtime context) and the model must be one of the GPT-5 responses models. For example: | ||
|
|
||
| ```python | ||
| from pydantic_ai import Agent | ||
| from pydantic_ai.models.openai import OpenAIResponsesModel | ||
|
|
||
| model = OpenAIResponsesModel('gpt-5') # (1)! | ||
| agent = Agent(model) | ||
|
|
||
| @agent.tool_plain(text_format='text') # (2)! | ||
| def freeform_tool(sql: str): ... | ||
| ``` | ||
|
|
||
| 1. The GPT-5 family (`gpt-5`, `gpt-5-mini`, `gpt-5-nano`) all support freeform function calling. | ||
| 2. If the tool or model cannot be used with freeform function calling then it will be invoked in the normal way. | ||
|
|
||
| You can read more about this function calling style in the [OpenAI documentation](https://cookbook.openai.com/examples/gpt-5/gpt-5_new_params_and_tools#2-freeform-function-calling). | ||
|
|
||
| #### Context Free Grammar | ||
|
|
||
| A tool that queries an SQL database can only accept valid SQL. The freeform function calling of GPT-5 supports generation of valid SQL for this situation by constraining the generated text using a context free grammar. | ||
|
|
||
| A context‑free grammar is a collection of production rules that define which strings belong to a language. Each rule rewrites a non‑terminal symbol into a sequence of terminals (literal tokens) and/or other non‑terminals, independent of surrounding context—hence context‑free. CFGs can capture the syntax of most programming languages and, in OpenAI custom tools, serve as contracts that force the model to emit only strings that the grammar accepts. | ||
|
|
||
| ##### Regular Expression | ||
|
|
||
| The grammar can be written as either a regular expression: | ||
|
|
||
|
|
||
| ```python | ||
| from pydantic_ai import Agent, FunctionTextFormat | ||
| from pydantic_ai.models.openai import OpenAIResponsesModel | ||
|
|
||
| model = OpenAIResponsesModel('gpt-5') # (1)! | ||
| agent = Agent(model) | ||
|
|
||
| timestamp_grammar_definition = r'^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]) (?:[01]\d|2[0-3]):[0-5]\d$' | ||
|
|
||
| @agent.tool_plain(text_format=FunctionTextFormat(syntax='regex', grammar=timestamp_grammar_definition)) # (2)! | ||
| def timestamp_accepting_tool(timestamp: str): ... | ||
| ``` | ||
|
|
||
| 1. The GPT-5 family (`gpt-5`, `gpt-5-mini`, `gpt-5-nano`) all support freeform function calling with context free grammar constraints. Unfortunately `gpt-5-nano` often struggles with these calls. | ||
| 2. If the tool or model cannot be used with freeform function calling then it will be invoked in the normal way, which may lead to invalid input. | ||
|
|
||
| ##### LARK | ||
|
|
||
| Or as a [LARK](https://lark-parser.readthedocs.io/en/latest/how_to_use.html) grammar: | ||
|
|
||
| ```python | ||
| from pydantic_ai import Agent, FunctionTextFormat | ||
| from pydantic_ai.models.openai import OpenAIResponsesModel | ||
|
|
||
| model = OpenAIResponsesModel('gpt-5') # (1)! | ||
| agent = Agent(model) | ||
|
|
||
| timestamp_grammar_definition = r''' | ||
| start: timestamp | ||
|
|
||
| timestamp: YEAR "-" MONTH "-" DAY " " HOUR ":" MINUTE | ||
|
|
||
| %import common.DIGIT | ||
|
|
||
| YEAR: DIGIT DIGIT DIGIT DIGIT | ||
| MONTH: /(0[1-9]|1[0-2])/ | ||
| DAY: /(0[1-9]|[12]\d|3[01])/ | ||
| HOUR: /([01]\d|2[0-3])/ | ||
| MINUTE: /[0-5]\d/ | ||
| ''' | ||
|
|
||
| @agent.tool_plain(text_format=FunctionTextFormat(syntax='lark', grammar=timestamp_grammar_definition)) # (2)! | ||
| def i_like_iso_dates(date: str): ... | ||
| ``` | ||
|
|
||
| 1. The GPT-5 family (`gpt-5`, `gpt-5-mini`, `gpt-5-nano`) all support freeform function calling with context free grammar constraints. Unfortunately `gpt-5-nano` often struggles with these calls. | ||
| 2. If the tool or model cannot be used with freeform function calling then it will be invoked in the normal way, which may lead to invalid input. | ||
|
|
||
| There is a limit to the grammar complexity that GPT-5 supports, as such it is important to test your grammar. | ||
|
|
||
| Freeform function calling, with or without a context free grammar, can be used with the output tool for the agent: | ||
|
|
||
| ```python | ||
| from pydantic_ai import Agent, FunctionTextFormat | ||
| from pydantic_ai.models.openai import OpenAIResponsesModel | ||
| from pydantic_ai.output import ToolOutput | ||
|
|
||
| sql_grammar_definition = r''' | ||
| start: select_stmt | ||
| select_stmt: "SELECT" select_list "FROM" table ("WHERE" condition ("AND" condition)*)? | ||
| select_list: "*" | column ("," column)* | ||
| table: "users" | "orders" | ||
| column: "id" | "user_id" | "name" | "age" | ||
| condition: column ("=" | ">" | "<") (NUMBER | STRING) | ||
| %import common.NUMBER | ||
| %import common.ESCAPED_STRING -> STRING | ||
| %import common.WS | ||
| %ignore WS | ||
| ''' # (1)! | ||
|
|
||
| output_tool = ToolOutput(str, text_format=FunctionTextFormat(syntax='lark', grammar=sql_grammar_definition)) | ||
| model = OpenAIResponsesModel('gpt-5') | ||
| agent = Agent(model, output_type=output_tool) | ||
| ``` | ||
|
|
||
| 1. An inline SQL grammar definition would be quite extensive and so this simplified version has been written, you can find an example SQL grammar [in the openai example](https://cookbook.openai.com/examples/gpt-5/gpt-5_new_params_and_tools#33-example---sql-dialect--ms-sql-vs-postgresql). There are also example grammars in the [lark repo](https://github.com/lark-parser/lark/blob/master/examples/composition/json.lark). Remember that a simpler grammar that matches your DDL will be easier for GPT-5 to work with and will result in fewer semantically invalid results. | ||
|
|
||
| ##### Best Practices | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to link to OpenAI's docs instead of having to keep this up to date |
||
|
|
||
| You can find recommended best practices in the [OpenAI Cookbook](https://cookbook.openai.com/examples/gpt-5/gpt-5_new_params_and_tools#35-best-practices). | ||
|
|
||
| * [Lark Docs](https://lark-parser.readthedocs.io/en/stable/) | ||
| * [Lark IDE](https://www.lark-parser.org/ide/) | ||
| * [OpenAI Cookbook on CFG](https://cookbook.openai.com/examples/gpt-5/gpt-5_new_params_and_tools#3-contextfree-grammar-cfg) | ||
|
|
||
| ## OpenAI-compatible Models | ||
|
|
||
| Many providers and models are compatible with the OpenAI API, and can be used with `OpenAIChatModel` in Pydantic AI. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's have headings for Regular Expressions and LARK, so they're shown in the ToC on the right
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed in b49cd81