{"id":15009934,"url":"https://github.com/synacktraa/tool-parse","last_synced_at":"2025-07-14T11:09:10.640Z","repository":{"id":210252480,"uuid":"726144951","full_name":"synacktraa/tool-parse","owner":"synacktraa","description":"Making LLM Tool-Calling Simpler.","archived":false,"fork":false,"pushed_at":"2024-10-03T15:14:14.000Z","size":13581,"stargazers_count":29,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-26T04:42:35.542Z","etag":null,"topics":["function-calling","llm","python-3","structured-output","tool-calling"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/tool-parse","language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/synacktraa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-01T16:19:05.000Z","updated_at":"2025-04-07T01:24:09.000Z","dependencies_parsed_at":"2024-07-29T15:27:01.343Z","dependency_job_id":"ad47fa56-56d1-4f00-aeb0-d32660eee1cc","html_url":"https://github.com/synacktraa/tool-parse","commit_stats":null,"previous_names":["synacktraa/hypertion","synacktraa/tool-parse"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/synacktraa/tool-parse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synacktraa%2Ftool-parse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synacktraa%2Ftool-parse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synacktraa%2Ftool-parse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synacktraa%2Ftool-parse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/synacktraa","download_url":"https://codeload.github.com/synacktraa/tool-parse/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synacktraa%2Ftool-parse/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265281255,"owners_count":23739872,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["function-calling","llm","python-3","structured-output","tool-calling"],"created_at":"2024-09-24T19:29:10.841Z","updated_at":"2025-07-14T11:09:10.311Z","avatar_url":"https://github.com/synacktraa.png","language":"Jupyter Notebook","readme":"\u003cp align=\"center\"\u003eMaking LLM Tool-Calling Simpler.\u003c/p\u003e\n\n---\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://img.shields.io/github/v/release/synacktraa/tool-parse\"\u003e\n        \u003cimg src=\"https://img.shields.io/github/v/release/synacktraa/tool-parse\" alt=\"tool-parse version\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://img.shields.io/github/actions/workflow/status/synacktraa/tool-parse/master.yml?branch=master\"\u003e\n        \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/synacktraa/tool-parse/master.yml?branch=master\" alt=\"tool-parse build status\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://codecov.io/gh/synacktraa/tool-parse\"\u003e\n        \u003cimg src=\"https://codecov.io/gh/synacktraa/tool-parse/branch/master/graph/badge.svg\" alt=\"tool-parse codecov\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://img.shields.io/github/license/synacktraa/tool-parse\"\u003e\n        \u003cimg src=\"https://img.shields.io/github/license/synacktraa/tool-parse\" alt=\"tool-parse license\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n## 🚀 Installation\n\n```sh\npip install tool-parse\n```\n\n- with `pydantic` support\n\n  ```sh\n  pip install \"tool-parse[pydantic]\"\n  ```\n\n- with `langchain` based integration\n  ```sh\n  pip install \"tool-parse[langchain]\"\n  ```\n\n## 🌟 Key Features\n\n1. **Versatile Tool Management:**\n\n   - Support for functions (both synchronous and asynchronous)\n   - Compatible with `pydantic.BaseModel`, `typing.TypedDict`, and `typing.NamedTuple`\n   - Supports any docstring format recognized by the `docstring_parser` library\n   - `@tool` decorator for creating independent, standalone tools\n   - `ToolRegistry` class for managing multiple tools\n     - Multiple registration methods:\n       - Decorators (`@tr.register`)\n       - Direct passing (`tr.register(func)`)\n       - Key-value pairs (`tr[key] = func`)\n       - Bulk registration (`register_multiple`)\n     - Customizable tool naming and description\n\n2. **Extensive Parameter Type Support:**\n\n   - Handles a wide range of parameter types:\n     `str`, `int`, `float`, `bool`, `set`, `list`, `dict`, `pathlib.Path`,\n     `typing.Set`, `typing.List`, `typing.Dict`, `typing.NamedTuple`,\n     `typing.TypedDict`, `pydantic.BaseModel`, `typing.Literal`, `enum.Enum`\n   - Supports optional parameters:\n     `typing.Optional[\u003ctype\u003e]`, `typing.Union[\u003ctype\u003e, None]`, `\u003ctype\u003e | None`\n   - Handles forward references and complex nested types\n\n3. **Robust Schema Generation:**\n\n   - Generates schemas in both 'base' and 'claude' formats\n   - Extracts and includes parameter descriptions from docstrings\n   - Handles recursive type definitions gracefully\n\n4. **Flexible Tool Invocation:**\n\n   - Supports tool invocation from call expressions or metadata\n   - Handles argument parsing and type conversion\n   - Manages both positional and keyword arguments\n\n5. **Error Handling and Validation:**\n   - Comprehensive error checking for type mismatches, invalid arguments, and unsupported types\n   - Validates enum and literal values against allowed options\n   - Handles recursive parameter exceptions\n\n## Cookbooks\n\n- [GorillaLLM Integration](./cookbooks/gorillaLLM-integration.ipynb)\n- [Langgraph+Ollama Example](./cookbooks//langgraph-ollama-example.ipynb)\n\n## Usage 🤗\n\n### Creating independent tools\n\n```python\nfrom tool_parse import tool\nfrom typing import Optional\n\n@tool\ndef search_web(query: str, max_results: Optional[int]):\n    \"\"\"\n    Search the web for given query\n    :param query: The search query string\n    :param max_results: Maximum number of results to return\n    \"\"\"\n    print(f\"{query=}, {max_results=}\")\n    ...\n\n# Get tool schema\nschema = search_web.marshal('base') # `base` and `claude` schema are available\n\n# Invoke tool from LLM generated arguments\noutput = search_web.compile(arguments={\"query\": \"Transformers\"})\n```\n\n### Creating a tool registry\n\n```python\nfrom tool_parse import ToolRegistry\n\ntr = ToolRegistry()\n```\n\n#### Defining tools and registering them\n\nThere are multiple ways of registering tools:\n\n\u003e Adding a docstring is optional, but it's good practice to include descriptions for parameters. The library supports any format recognized by the `docstring_parser` library, with sphinx format being a personal preference.\n\n1. Decorating the object:\n\n```python\nfrom typing import TypedDict\n\n@tr.register\nclass UserInfo(TypedDict):\n    \"\"\"\n    User information schema\n    :param name: The user's full name\n    :param age: The user's age in years\n    \"\"\"\n    name: str\n    age: int\n\n# Override name and description\n@tr.register(name=\"search_web\", description=\"Performs a web search\")\ndef search_function(query: str, max_results: int = 10):\n    \"\"\"\n    Search the web for given query\n    :param query: The search query string\n    :param max_results: Maximum number of results to return\n    \"\"\"\n    ...\n```\n\n2. Passing the object directly:\n\n```python\nfrom typing import NamedTuple\n\nclass ProductInfo(NamedTuple):\n    \"\"\"\n    Product information\n    :param name: The product name\n    :param price: The product price\n    :param in_stock: Whether the product is in stock\n    \"\"\"\n    name: str\n    price: float\n    in_stock: bool\n\ntr.register(ProductInfo)\n\nasync def fetch_data(url: str, timeout: int = 30):\n    \"\"\"\n    Fetch data from a given URL\n    :param url: The URL to fetch data from\n    :param timeout: Timeout in seconds\n    \"\"\"\n    ...\n\ntr.register(fetch_data, name=\"fetch_api_data\", description=\"Fetches data from an API\")\n```\n\n3. Using key-value pair:\n\n\u003e Note: This method doesn't allow overriding the description.\n\n```python\nfrom pydantic import BaseModel\n\nclass OrderModel(BaseModel):\n    \"\"\"\n    Order information\n    :param order_id: Unique identifier for the order\n    :param items: List of items in the order\n    :param total: Total cost of the order\n    \"\"\"\n    order_id: str\n    items: list[str]\n    total: float\n\ntr['create_order'] = OrderModel\n```\n\n4. Registering multiple tools at once:\n\n\u003e Note: This method doesn't allow overriding the name and description\n\n```python\ntr.register_multiple(UserInfo, search_function, ProductInfo)\n```\n\n#### Check if a name has already been registered\n\n```python\n'search_web' in tr  # Returns True if 'search_web' is registered, False otherwise\n```\n\n#### Get registered tools as schema\n\n\u003e `base` and `claude` formats are available. The default `base` format works with almost all providers.\n\n- As a list of dictionaries:\n\n  ```python\n  tools = tr.marshal('base')  # list[dict]\n  ```\n\n- As a JSON string:\n\n  ```python\n  tools = tr.marshal(as_json=True)  # str\n  ```\n\n- Saving as a JSON file:\n\n  ```python\n  tools = tr.marshal('claude', persist_at='/path/to/tools_schema.json')  # list[dict]\n  ```\n\n- Get a single tool schema:\n  ```python\n  tool = tr['search_web']  # dict\n  ```\n\n#### Invoking a tool\n\n- From a call expression:\n\n  ```python\n  result = tr.compile('search_web(\"Python programming\", max_results=5)')\n  ```\n\n- From call metadata:\n\n  ```python\n  result = tr.compile(name='fetch_api_data', arguments={'url': 'https://api.example.com', 'timeout': 60})\n  ```\n\n\u003e Important: The `tool-parse` library does not interact directly with LLM-specific APIs. It cannot make requests to any LLM directly. Its primary functions are generating schemas and invoking expressions or metadata generated from LLMs. This design provides developers with more flexibility to integrate or adapt various tools and libraries according to their project needs.\n\n#### Combining two registries\n\n\u003e Note: A single `ToolRegistry` instance can hold as many tools as you need. Creating a new `ToolRegistry` instance is beneficial only when you require a distinct set of tools. This approach is particularly effective when deploying agents to utilize tools designed for specific tasks.\n\n```python\nnew_registry = ToolRegistry()\n\n@new_registry.register\ndef calculate_discount(\n    original_price: float,\n    discount_percentage: float = 10\n):\n    \"\"\"\n    Calculate the discounted price of an item\n    :param original_price: The original price of the item\n    :param discount_percentage: The discount percentage to apply\n    \"\"\"\n    ...\n\ncombined_registry = tr + new_registry\n```\n\n## Third Party Integrations\n\n### Langchain\n\nDefine the tools\n\n```python\nfrom tool_parse.integrations.langchain import ExtendedStructuredTool\n\nasync def search_web(query: str, safe_search: bool = True):\n    \"\"\"\n    Search the web.\n    :param query: Query to search for.\n    :param safe_search: If True, enable safe search.\n    \"\"\"\n    return \"not found\"\n\nclass UserInfo(NamedTuple):\n    \"\"\"User information\"\"\"\n    name: str\n    age: int\n    role: Literal['admin', 'tester'] = 'tester'\n\ntools = [\n    ExtendedStructuredTool(func=search_web),\n    ExtendedStructuredTool(func=UserInfo, name=\"user_info\", schema_spec='claude'),\n]\n# OR\ntools = ExtendedStructuredTool.from_objects(search_web, UserInfo, schema_spec='base')\n```\n\nPatch the chat model\n\n```python\nfrom langchain_ollama.chat_models import ChatOllama\n\nfrom tool_parse.integrations.langchain import patch_chat_model\n\nmodel = patch_chat_model(ChatOllama(model=\"llama3-groq-tool-use\")) # Patch the instance\n# OR\nmodel = patch_chat_model(ChatOllama)(model=\"llama3-groq-tool-use\") # Patch the class and then instantiate it\n```\n\nBind the tools\n\n```python\nmodel.bind_tools(tools=tools)\n```\n\n\u003e For langgraph agent usage, refer [Langgraph+Ollama Example](./cookbooks//langgraph-ollama-example.ipynb) cookbook\n\n## 🤝 Contributing\n\nContributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://github.com/synacktraa/tool-parse/issues).\n\n---\n\nMade with ❤️ by [synacktra](https://github.com/synacktraa)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynacktraa%2Ftool-parse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsynacktraa%2Ftool-parse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynacktraa%2Ftool-parse/lists"}