{"slug": "write-once-run-anywhere-universal-tools-for-ai-agents", "title": "Write Once, Run Anywhere: Universal Tools for AI Agents", "summary": "Yaala Labs has released Agent Kernel, an open-source library that lets developers write AI agent tools once as plain Python functions and bind them to multiple frameworks, including OpenAI, CrewAI, LangGraph, and Google ADK. The project addresses the duplication problem where the same tool, such as a weather lookup, must be rewritten for each framework's decorator and tool format. A single .bind() call translates the function into each framework's expected format.", "body_md": "*By [Yaala Labs](https://github.com/yaalalabs)*\n\n**Stop rewriting the same tools for different AI frameworks.**\n\nImagine building a calculator app, then discovering you need to rebuild it from scratch every time you want to run it on a different device. That's what building AI agent tools feels like today.\n\nWrite a weather lookup tool for OpenAI? It won't work with Google's framework. Build it for CrewAI? Start over if you switch to LangGraph. This wastes time and creates headaches every time you want to try a new AI framework.\n\n**Agent Kernel solves this: write your tools once, use them everywhere.**\n\nThink of tools as capabilities you give your AI agent—like looking up weather, searching a database, or sending emails. Right now, each AI framework requires you to write these tools in a completely different way.\n\nHere's the same weather tool written for four different frameworks:\n\n``` php\n# OpenAI version\n@function_tool\ndef get_weather(city: str) -> str:\n    return f\"Weather in {city}: sunny\"\n\n# CrewAI version  \n@tool\ndef get_weather(city: str) -> str:\n    return f\"Weather in {city}: sunny\"\n\n# LangGraph version\n@tool\ndef get_weather(city: str) -> str:\n    return f\"Weather in {city}: sunny\"\n\n# Google ADK version\ndef _get_weather(city: str) -> str:\n    return f\"Weather in {city}: sunny\"\nget_weather = FunctionTool(_get_weather)\n```\n\n**It's the same weather lookup, but you have to write and maintain four different versions.**\n\nWant to switch frameworks? Rewrite everything. Want to try a new framework alongside your current one? Duplicate all your tools. Building 10 custom tools means maintaining 40 versions if you use all four frameworks.\n\nWith Agent Kernel, you write your tools as regular Python functions—no special decorators or framework-specific code:\n\n``` php\ndef get_weather(city: str) -> str:\n    \"\"\"Returns the weather for a given city.\"\"\"\n    return f\"Weather in {city}: sunny, 25°C\"\n```\n\nThat's it. Just a normal Python function with a helpful description. Then use it with any framework you want.\n\nOnce you've written your tool as a normal Python function, you can add it to agents in any framework. The only thing that changes is one line of code:\n\n``` python\nfrom agents import Agent\nfrom agentkernel.openai import OpenAIToolBuilder\n\nweather_agent = Agent(\n    name=\"weather\",\n    instructions=\"You provide weather information.\",\n    tools=OpenAIToolBuilder.bind([get_weather]),  # ← Add your tool here\n)\npython\nfrom crewai import Agent\nfrom agentkernel.crewai import CrewAIToolBuilder\n\nweather_agent = Agent(\n    role=\"weather\",\n    goal=\"You provide weather information\",\n    backstory=\"Use the get_weather tool for queries.\",\n    tools=CrewAIToolBuilder.bind([get_weather]),  # ← Add your tool here\n)\npython\nfrom langgraph.prebuilt import create_react_agent\nfrom agentkernel.langgraph import LangGraphToolBuilder\n\nweather_agent = create_react_agent(\n    name=\"weather\",\n    tools=LangGraphToolBuilder.bind([get_weather]),  # ← Add your tool here\n    model=model,\n    prompt=\"Use the get_weather tool for queries.\",\n)\npython\nfrom google.adk.agents import Agent\nfrom agentkernel.adk import GoogleADKToolBuilder\n\nweather_agent = Agent(\n    name=\"weather\",\n    model=\"gemini-2.0-flash-exp\",\n    description=\"You provide weather information\",\n    tools=GoogleADKToolBuilder.bind([get_weather]),  # ← Add your tool here\n)\n```\n\n**Same `get_weather` function. Works everywhere.**\n\nThe `.bind()` method translates your normal Python function into whatever format each framework needs. You don't have to worry about the details—just write your function once and use it anywhere.\n\nMaking tools work across frameworks sounds simple, but it's actually quite challenging. Each framework has its own way of handling tools, and these differences create real technical problems.\n\nFrameworks handle async/sync functions differently:\n\n**LangGraph (LangChain)** requires you to specify whether a tool is async or sync upfront:\n\n```\n# Sync tools use 'func' parameter\nStructuredTool.from_function(func=my_tool)\n\n# Async tools use 'coroutine' parameter\nStructuredTool.from_function(coroutine=my_async_tool)\n```\n\n**OpenAI Agents SDK** automatically handles both, but wraps them differently internally.\n\n**Google ADK** expects all tools to potentially receive a `tool_context` parameter from the framework itself, which other frameworks don't provide.\n\nAgent Kernel detects whether your function is async or sync and generates the right code for each framework automatically. You just write `def` or `async def`.\n\nThis is the hardest problem: frameworks provide execution context (session info, runtime state) in completely different ways.\n\n**OpenAI, CrewAI, LangGraph** work well with Python's standard `contextvars`, which lets you set context in one place and access it anywhere in the call stack:\n\n```\n# Set once before calling agent\ncontext.set()\n\n# Access anywhere in your tool\nctx = ToolContext.get()\n```\n\n**Google ADK** manages its own execution context internally and doesn't reliably propagate Python's `contextvars`. Instead, it passes a `tool_context` parameter to every tool function:\n\n``` python\n# ADK calls your tool like this:\ndef my_tool(city: str, tool_context: ADKToolContext):\n    # ADK's context, not yours\n```\n\nThis means your tool function signature must be different for ADK versus other frameworks. Or does it?\n\nAgent Kernel solves this by:\n\n`tool_context` parameter automatically`ToolContext`\nAll this happens behind the scenes. You write one function, and it works everywhere.\n\nEach framework expects different information about your tool:\n\n| Framework | Name Source | Description Source | Schema Generation | \n|---|---|---|---|\n| OpenAI | Function name | Docstring | Automatic from type hints | \n| CrewAI | Function name | Docstring | Automatic from type hints | \n| LangGraph | Must specify | Must specify or falls back to function name | Must extract from function | \n| Google ADK | Function name | Function name if no docstring | Type inspection required | \n\nAgent Kernel extracts metadata once from your Python function (name, docstring, parameters, types) and formats it appropriately for each framework.\n\nWhen a tool raises an exception, frameworks handle it differently:\n\nAgent Kernel doesn't hide these differences (they're tied to framework behavior), but it ensures your tool code doesn't need to know which framework is calling it.\n\nEach framework's tool classes come from different packages:\n\n``` python\nfrom agents import function_tool              # OpenAI\nfrom crewai_tools import tool                 # CrewAI  \nfrom langchain_core.tools import StructuredTool  # LangGraph\nfrom google.adk.tools import FunctionTool     # Google ADK\n```\n\nIf you write tools using framework-specific decorators, you're locked in. Agent Kernel's `ToolBuilder` classes handle these imports internally, so your tool code has zero framework dependencies.\n\nHere's what Agent Kernel does behind the scenes for a single tool:\n\n`ToolContext.get()` works in your tool\nAll so you can write this:\n\n``` php\ndef my_tool(param: str) -> str:\n    \"\"\"Does something useful.\"\"\"\n    return result\n\ntools = AnyFrameworkToolBuilder.bind([my_tool])\n```\n\n**This simplicity is hard-won.** Framework-agnostic tools require handling edge cases most developers never see.\n\nSometimes your tools need to know things like \"which user is asking?\" or \"what session is this?\" Agent Kernel provides this information in a consistent way, regardless of which framework you're using:\n\n``` php\nfrom agentkernel.core import ToolContext\n\ndef get_weather(city: str) -> str:\n    \"\"\"Returns the weather for a given city.\"\"\"\n    # Get information about the current execution\n    ctx = ToolContext.get()\n\n    session = ctx.session      # Who's asking?\n    agent = ctx.agent          # Which agent is calling this?\n\n    # Example: Use session data for personalization\n    user_prefs = session.get_non_volatile_cache().get(\"preferences\", {})\n    units = user_prefs.get(\"temperature_units\", \"celsius\")\n\n    return f\"Weather in {city}: sunny, 25°C\"\n```\n\nThis works the same way across all frameworks—you don't need to learn different methods for each one.\n\nHere's a complete example using OpenAI:\n\n``` python\nfrom agentkernel.core import ToolContext\nfrom agentkernel.openai import OpenAIModule, OpenAIToolBuilder\nfrom agents import Agent\n\ndef get_weather(city: str) -> str:\n    \"\"\"Returns the weather for a given city.\"\"\"\n    if city == \"Tokyo\":\n        return \"The weather in Tokyo is sunny.\"\n    else:\n        return f\"Cannot find weather for {city}.\"\n\n# Create agents\nweather_agent = Agent(\n    name=\"weather\",\n    instructions=\"Use the get_weather tool for weather questions.\",\n    tools=OpenAIToolBuilder.bind([get_weather]),\n)\n\nmath_agent = Agent(\n    name=\"math\",\n    instructions=\"You help with math problems.\",\n)\n\ntriage_agent = Agent(\n    name=\"triage\",\n    instructions=\"Route questions to the right agent.\",\n    handoffs=[math_agent, weather_agent],\n)\n\nOpenAIModule([triage_agent, math_agent, weather_agent])\n```\n\n**Want to try LangGraph instead?** Change just the framework-specific parts:\n\n``` python\nfrom agentkernel.langgraph import LangGraphModule, LangGraphToolBuilder\nfrom langgraph.prebuilt import create_react_agent\n\n# Same get_weather function - no changes!\n\nweather_agent = create_react_agent(\n    name=\"weather\",\n    tools=LangGraphToolBuilder.bind([get_weather]),  # Different builder\n    model=ChatOpenAI(model=\"gpt-4o-mini\"),\n    prompt=\"Use the get_weather tool for weather questions.\",\n)\n\n# Define other agents...\nLangGraphModule([triage_agent, math_agent, weather_agent])\n```\n\nYour `get_weather` function? **Completely untouched.** No rewriting. No debugging. Just works.\n\nIf your tool needs to do asynchronous work (like database queries or API calls), just write it as an async function:\n\n``` php\nasync def search_database(query: str, limit: int = 10) -> str:\n    \"\"\"Searches the database for matching records.\"\"\"\n    results = await db.search(query, limit=limit)\n    return str(results)\n\n# Works with any framework automatically\ntools = OpenAIToolBuilder.bind([search_database])\ntools = LangGraphToolBuilder.bind([search_database])\ntools = GoogleADKToolBuilder.bind([search_database])\n```\n\nAgent Kernel handles the async details for you—just write your function normally.\n\nReal agents usually need multiple tools. Just add them all to the list:\n\n```\ntools = OpenAIToolBuilder.bind([\n    get_weather,\n    search_database,\n    send_email,\n    get_user_profile,\n])\n\nagent = Agent(\n    name=\"assistant\",\n    instructions=\"You're a helpful assistant with multiple tools.\",\n    tools=tools,\n)\n```\n\nAll tools work together seamlessly, regardless of your framework.\n\nWant to see if LangGraph is better than OpenAI for your use case? Just switch. Your tools keep working. No migration project needed.\n\nTest your tools as regular Python functions:\n\n``` python\ndef test_get_weather():\n    result = get_weather(\"Tokyo\")\n    assert \"sunny\" in result.lower()\n```\n\nNo complicated framework setup. No mocking. Just normal Python testing.\n\nYour tools are just functions. Easy to read. Easy to understand. Easy to maintain. No framework magic hiding what your code does.\n\nNew developers don't need to learn framework-specific tool systems. They write regular Python functions. Everything else is handled automatically.\n\nYou might have heard of **MCP (Model Context Protocol)** and wonder: \"Should I use MCP for my tools, or build them locally?\"\n\nThe answer depends on what your tools do. Let's break it down simply:\n\nMCP is designed for **connecting to external tool servers** that run as separate processes. Think of it like calling a web API or external service.\n\n**Use MCP when:**\n\n**MCP Example:**\n\n```\nYour Agent → Network → MCP Server → External Database\n```\n\nAgent Kernel's tool binding is for **tools that are part of your agent's business logic**—functions that live in your codebase alongside your agents.\n\n**Use local tool binding when:**\n\n**Local Tool Example:**\n\n```\nYour Agent → Direct Function Call → Your Code\n```\n\nFor most business logic, building tools directly in your agent code is simpler and more efficient:\n\n``` python\n# Local tool - just a function\ndef calculate_price(quantity: int, item_type: str) -> float:\n    \"\"\"Calculate price with discounts.\"\"\"\n    base_price = PRICES[item_type] * quantity\n    discount = get_bulk_discount(quantity)\n    return base_price * (1 - discount)\n\n# Use it immediately\ntools = OpenAIToolBuilder.bind([calculate_price])\n```\n\nWith MCP, you'd need to:\n\nLocal tools are just function calls—microseconds. MCP involves network requests—milliseconds or more. For tools that run frequently, this adds up.\n\n```\n# Local: Direct call, ~microseconds\nresult = calculate_price(100, \"widget\")\n\n# MCP: Network round-trip, ~10-100ms\nresult = await mcp_client.call_tool(\"calculate_price\", {...})\npython\n# Test local tools like any Python function\ndef test_bulk_discount():\n    price = calculate_price(quantity=100, item_type=\"widget\")\n    assert price < calculate_price(quantity=10, item_type=\"widget\")\n\n# MCP tools require running a server, mocking network calls, etc.\n```\n\nWhen something goes wrong with a local tool, your debugger works normally. Step through your code, inspect variables, see the full stack trace.\n\nWith MCP, you're debugging across process boundaries and network calls. Much harder.\n\nLocal tools deploy with your agent—one Docker container, one deployment. MCP tools need separate infrastructure, monitoring, and coordination.\n\nDon't get us wrong—MCP is valuable for the right use cases:\n\n**Shared Corporate Tools:**\n\nYour company has a central \"Employee Directory\" service used by 50 different AI agents. One MCP server, everyone connects to it.\n\n**External Services:**\n\nYou're integrating with a third-party tool provider (like a specialized search engine or data enrichment service) that offers MCP access.\n\n**Language Boundaries:**\n\nYour tool is written in Rust for performance, but your agent is in Python. MCP lets them communicate.\n\n**Heavy Resources:**\n\nYour tool needs 64GB of RAM and a GPU. Run it on a dedicated server via MCP instead of bundling it with every agent instance.\n\n**Start with local tools.** They're simpler, faster, and easier to build and test.\n\nOnly use MCP when you have a specific reason:\n\nFor most business logic—data formatting, calculations, querying your own databases, calling your internal APIs—local tools are the right choice.\n\nAgent Kernel supports both approaches. Use local tools for your custom logic, and connect to MCP servers when you need external capabilities:\n\n```\n# Local tools for your business logic\nlocal_tools = OpenAIToolBuilder.bind([\n    calculate_price,\n    format_invoice,\n    check_inventory,\n])\n\n# MCP tools for external services (if needed)\n# Connect to MCP server for shared document search\nmcp_tools = await connect_to_mcp_server(\"company-docs\")\n\n# Use both together\nagent = Agent(\n    name=\"sales_assistant\",\n    tools=local_tools + mcp_tools,\n)\n```\n\n**The key insight:** Local tools and MCP serve different purposes. Don't add MCP complexity unless you need it. For most agent development, simple local tools are the better choice.\n\nWhen you write a tool as a normal Python function, Agent Kernel automatically extracts everything the AI needs to know:\n\nThe AI sees the same tool, no matter which framework you use.\n\nUsing framework-agnostic tools is simple:\n\n```\npip install agentkernel\nphp\ndef my_tool(param: str) -> str:\n    \"\"\"Description of what this tool does.\"\"\"\n    # Your code here\n    return result\npython\nfrom agentkernel.openai import OpenAIToolBuilder\nfrom agents import Agent\n\nagent = Agent(\n    name=\"my_agent\",\n    instructions=\"Use my_tool when needed.\",\n    tools=OpenAIToolBuilder.bind([my_tool]),\n)\n```\n\nThat's all. Three steps, and your tool works across all frameworks.\n\n**Stop maintaining multiple versions of the same tool.**\n\nWrite your tools once. Test them once. Use them everywhere. That's the promise of framework-agnostic tools in Agent Kernel.\n\nTry different frameworks. Migrate painlessly. Focus on building great tools, not maintaining duplicates.\n\nStart building framework-agnostic tools today. Your future self will thank you.\n\n*Originally published at [kernel.yaala.ai](https://kernel.yaala.ai/blog/framework-agnostic-tool-binding) on February 18, 2026.*", "url": "https://wpnews.pro/news/write-once-run-anywhere-universal-tools-for-ai-agents", "canonical_source": "https://dev.to/agent-kernel/write-once-run-anywhere-universal-tools-for-ai-agents-1mmg", "published_at": "2026-10-06 10:32:57+00:00", "updated_at": "2026-10-06 10:48:16.772447+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "agent-protocols", "artificial-intelligence"], "entities": ["Yaala Labs", "Agent Kernel", "OpenAI", "CrewAI", "LangGraph", "Google ADK", "LangChain"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/write-once-run-anywhere-universal-tools-for-ai-agents", "markdown": "https://wpnews.pro/news/write-once-run-anywhere-universal-tools-for-ai-agents.md", "text": "https://wpnews.pro/news/write-once-run-anywhere-universal-tools-for-ai-agents.txt", "jsonld": "https://wpnews.pro/news/write-once-run-anywhere-universal-tools-for-ai-agents.jsonld"}}