diff --git a/docs/agents.md b/docs/agents.md index b99b0df8e5..5381471826 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -479,7 +479,6 @@ def print_schema(messages: list[Message], info: AgentInfo) -> ModelAnyResponse: print(tool.parameters_json_schema) """ { - 'description': 'Get me foobar.', 'properties': { 'a': {'description': 'apple pie', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'banana cake', 'title': 'B', 'type': 'string'}, @@ -540,9 +539,8 @@ print(test_model.agent_model_function_tools) [ ToolDefinition( name='foobar', - description='', + description='This is a Foobar', parameters_json_schema={ - 'description': 'This is a Foobar', 'properties': { 'x': {'title': 'X', 'type': 'integer'}, 'y': {'title': 'Y', 'type': 'string'}, diff --git a/pydantic_ai_slim/pydantic_ai/_pydantic.py b/pydantic_ai_slim/pydantic_ai/_pydantic.py index ce85363689..92cd2b4d7a 100644 --- a/pydantic_ai_slim/pydantic_ai/_pydantic.py +++ b/pydantic_ai_slim/pydantic_ai/_pydantic.py @@ -138,14 +138,14 @@ def function_schema(function: Callable[..., Any], takes_ctx: bool) -> FunctionSc json_schema = GenerateJsonSchema().generate(schema) # workaround for https://github.com/pydantic/pydantic/issues/10785 - # if we build a custom TypeDict schema (matches when `single_arg_name` is None), we manually set + # if we build a custom TypeDict schema (matches when `single_arg_name is None`), we manually set # `additionalProperties` in the JSON Schema if single_arg_name is None: json_schema['additionalProperties'] = bool(var_kwargs_schema) - - # instead of passing `description` through in core_schema, we just add it here - if description: - json_schema = {'description': description} | json_schema + elif not description: + # if the tool description is not set, and we have a single parameter, take the description from that + # and set it on the tool + description = json_schema.pop('description', None) return FunctionSchema( description=description, diff --git a/tests/test_tools.py b/tests/test_tools.py index df24e46b0f..1b47d35987 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from typing import Annotated, Any, Callable, Union +import pydantic_core import pytest from inline_snapshot import snapshot from pydantic import BaseModel, Field @@ -73,7 +74,7 @@ async def google_style_docstring(foo: int, bar: str) -> str: # pragma: no cover async def get_json_schema(_messages: list[Message], info: AgentInfo) -> ModelAnyResponse: assert len(info.function_tools) == 1 r = info.function_tools[0] - return ModelTextResponse(json.dumps(r.parameters_json_schema)) + return ModelTextResponse(pydantic_core.to_json(r).decode()) def test_docstring_google(set_event_loop: None): @@ -84,18 +85,25 @@ def test_docstring_google(set_event_loop: None): json_schema = json.loads(result.data) assert json_schema == snapshot( { + 'name': 'google_style_docstring', 'description': 'Do foobar stuff, a lot.', - 'additionalProperties': False, - 'properties': { - 'foo': {'description': 'The foo thing.', 'title': 'Foo', 'type': 'integer'}, - 'bar': {'description': 'The bar thing.', 'title': 'Bar', 'type': 'string'}, + 'parameters_json_schema': { + 'properties': { + 'foo': {'description': 'The foo thing.', 'title': 'Foo', 'type': 'integer'}, + 'bar': {'description': 'The bar thing.', 'title': 'Bar', 'type': 'string'}, + }, + 'required': ['foo', 'bar'], + 'type': 'object', + 'additionalProperties': False, }, - 'required': ['foo', 'bar'], - 'type': 'object', + 'outer_typed_dict_key': None, } ) - # description should be the first key - assert next(iter(json_schema)) == 'description' + keys = list(json_schema.keys()) + # name should be the first key + assert keys[0] == 'name' + # description should be the second key + assert keys[1] == 'description' def sphinx_style_docstring(foo: int, /) -> str: # pragma: no cover @@ -115,13 +123,15 @@ def test_docstring_sphinx(set_event_loop: None): json_schema = json.loads(result.data) assert json_schema == snapshot( { + 'name': 'sphinx_style_docstring', 'description': 'Sphinx style docstring.', - 'additionalProperties': False, - 'properties': { - 'foo': {'description': 'The foo thing.', 'title': 'Foo', 'type': 'integer'}, + 'parameters_json_schema': { + 'properties': {'foo': {'description': 'The foo thing.', 'title': 'Foo', 'type': 'integer'}}, + 'required': ['foo'], + 'type': 'object', + 'additionalProperties': False, }, - 'required': ['foo'], - 'type': 'object', + 'outer_typed_dict_key': None, } ) @@ -147,14 +157,18 @@ def test_docstring_numpy(set_event_loop: None): json_schema = json.loads(result.data) assert json_schema == snapshot( { + 'name': 'numpy_style_docstring', 'description': 'Numpy style docstring.', - 'additionalProperties': False, - 'properties': { - 'foo': {'description': 'The foo thing.', 'title': 'Foo', 'type': 'integer'}, - 'bar': {'description': 'The bar thing.', 'title': 'Bar', 'type': 'string'}, + 'parameters_json_schema': { + 'properties': { + 'foo': {'description': 'The foo thing.', 'title': 'Foo', 'type': 'integer'}, + 'bar': {'description': 'The bar thing.', 'title': 'Bar', 'type': 'string'}, + }, + 'required': ['foo', 'bar'], + 'type': 'object', + 'additionalProperties': False, }, - 'required': ['foo', 'bar'], - 'type': 'object', + 'outer_typed_dict_key': None, } ) @@ -172,10 +186,10 @@ def test_docstring_unknown(set_event_loop: None): json_schema = json.loads(result.data) assert json_schema == snapshot( { + 'name': 'unknown_docstring', 'description': 'Unknown style docstring.', - 'additionalProperties': True, - 'properties': {}, - 'type': 'object', + 'parameters_json_schema': {'properties': {}, 'type': 'object', 'additionalProperties': True}, + 'outer_typed_dict_key': None, } ) @@ -201,13 +215,16 @@ def test_docstring_google_no_body(set_event_loop: None): json_schema = json.loads(result.data) assert json_schema == snapshot( { - 'additionalProperties': False, + 'name': 'google_style_docstring_no_body', 'description': '', 'parameters_json_schema': { 'properties': { - 'foo': {'description': 'The foo thing.', 'title': 'Foo', 'type': 'integer'}, - 'bar': {'description': 'from fields', 'title': 'Bar', 'type': 'string'}, + 'foo': {'description': 'The foo thing.', 'title': 'Foo', 'type': 'integer'}, + 'bar': {'description': 'from fields', 'title': 'Bar', 'type': 'string'}, + }, + 'required': ['foo', 'bar'], + 'type': 'object', + 'additionalProperties': False, }, - 'required': ['foo', 'bar'], - 'type': 'object', + 'outer_typed_dict_key': None } ) @@ -228,10 +245,18 @@ def takes_just_model(model: Foo) -> str: json_schema = json.loads(result.data) assert json_schema == snapshot( { - 'title': 'Foo', - 'properties': {'x': {'title': 'X', 'type': 'integer'}, 'y': {'title': 'Y', 'type': 'string'}}, - 'required': ['x', 'y'], - 'type': 'object', + 'name': 'takes_just_model', + 'description': None, + 'parameters_json_schema': { + 'properties': { + 'x': {'title': 'X', 'type': 'integer'}, + 'y': {'title': 'Y', 'type': 'string'}, + }, + 'required': ['x', 'y'], + 'title': 'Foo', + 'type': 'object', + }, + 'outer_typed_dict_key': None } ) @@ -250,24 +275,29 @@ def takes_just_model(model: Foo, z: int) -> str: json_schema = json.loads(result.data) assert json_schema == snapshot( { - '$defs': { - 'Foo': { - 'properties': { - 'x': {'title': 'X', 'type': 'integer'}, - 'y': {'title': 'Y', 'type': 'string'}, - }, - 'required': ['x', 'y'], - 'title': 'Foo', - 'type': 'object', - } - }, - 'additionalProperties': False, - 'properties': { - 'model': {'$ref': '#/$defs/Foo'}, - 'z': {'title': 'Z', 'type': 'integer'}, + 'name': 'takes_just_model', + 'description': '', + 'parameters_json_schema': { + '$defs': { + 'Foo': { + 'properties': { + 'x': {'title': 'X', 'type': 'integer'}, + 'y': {'title': 'Y', 'type': 'string'}, + }, + 'required': ['x', 'y'], + 'title': 'Foo', + 'type': 'object', + } + }, + 'properties': { + 'model': {'$ref': '#/$defs/Foo'}, + 'z': {'title': 'Z', 'type': 'integer'}, + }, + 'required': ['model', 'z'], + 'type': 'object', + 'additionalProperties': False, }, - 'required': ['model', 'z'], - 'type': 'object', + 'outer_typed_dict_key': None } ) diff --git a/uv.lock b/uv.lock index e7d5fc4242..cf006fa519 100644 --- a/uv.lock +++ b/uv.lock @@ -2070,27 +2070,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 }, - { url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 }, - { url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 }, - { url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 }, - { url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 }, - { url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 }, - { url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 }, - { url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 }, - { url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 }, - { url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 }, - { url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 }, - { url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 }, - { url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 }, - { url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 }, - { url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 }, - { url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 }, - { url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 }, +version = "0.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/5e/683c7ef7a696923223e7d95ca06755d6e2acbc5fd8382b2912a28008137c/ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3", size = 3378522 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/c4/bfdbb8b9c419ff3b52479af8581026eeaac3764946fdb463dec043441b7d/ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6", size = 10535860 }, + { url = "https://files.pythonhosted.org/packages/ef/c5/0aabdc9314b4b6f051168ac45227e2aa8e1c6d82718a547455e40c9c9faa/ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939", size = 10346327 }, + { url = "https://files.pythonhosted.org/packages/1a/78/4843a59e7e7b398d6019cf91ab06502fd95397b99b2b858798fbab9151f5/ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d", size = 9942585 }, + { url = "https://files.pythonhosted.org/packages/91/5a/642ed8f1ba23ffc2dd347697e01eef3c42fad6ac76603be4a8c3a9d6311e/ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13", size = 10797597 }, + { url = "https://files.pythonhosted.org/packages/30/25/2e654bc7226da09a49730a1a2ea6e89f843b362db80b4b2a7a4f948ac986/ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18", size = 10307244 }, + { url = "https://files.pythonhosted.org/packages/c0/2d/a224d56bcd4383583db53c2b8f410ebf1200866984aa6eb9b5a70f04e71f/ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502", size = 11362439 }, + { url = "https://files.pythonhosted.org/packages/82/01/03e2857f9c371b8767d3e909f06a33bbdac880df17f17f93d6f6951c3381/ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d", size = 12078538 }, + { url = "https://files.pythonhosted.org/packages/af/ae/ff7f97b355da16d748ceec50e1604a8215d3659b36b38025a922e0612e9b/ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82", size = 11616172 }, + { url = "https://files.pythonhosted.org/packages/6a/d0/6156d4d1e53ebd17747049afe801c5d7e3014d9b2f398b9236fe36ba4320/ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452", size = 12919886 }, + { url = "https://files.pythonhosted.org/packages/4e/84/affcb30bacb94f6036a128ad5de0e29f543d3f67ee42b490b17d68e44b8a/ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd", size = 11212599 }, + { url = "https://files.pythonhosted.org/packages/60/b9/5694716bdefd8f73df7c0104334156c38fb0f77673d2966a5a1345bab94d/ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20", size = 10784637 }, + { url = "https://files.pythonhosted.org/packages/24/7e/0e8f835103ac7da81c3663eedf79dec8359e9ae9a3b0d704bae50be59176/ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc", size = 10390591 }, + { url = "https://files.pythonhosted.org/packages/27/da/180ec771fc01c004045962ce017ca419a0281f4bfaf867ed0020f555b56e/ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060", size = 10894298 }, + { url = "https://files.pythonhosted.org/packages/6d/f8/29f241742ed3954eb2222314b02db29f531a15cab3238d1295e8657c5f18/ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea", size = 11275965 }, + { url = "https://files.pythonhosted.org/packages/79/e9/5b81dc9afc8a80884405b230b9429efeef76d04caead904bd213f453b973/ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964", size = 8807651 }, + { url = "https://files.pythonhosted.org/packages/ea/67/7291461066007617b59a707887b90e319b6a043c79b4d19979f86b7a20e7/ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9", size = 9625289 }, + { url = "https://files.pythonhosted.org/packages/03/8f/e4fa95288b81233356d9a9dcaed057e5b0adc6399aa8fd0f6d784041c9c3/ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936", size = 9078754 }, ] [[package]]