|
| 1 | +#!/usr/bin/env python |
1 | 2 | # examples/demo_langchain_tool.py |
2 | 3 | """ |
3 | | -Demo: register a LangChain `BaseTool` with chuk-tool-processor and invoke it. |
| 4 | +Demo: expose a LangChain `BaseTool` as an async-native chuk-tool. |
4 | 5 | """ |
5 | 6 |
|
6 | 7 | from __future__ import annotations |
7 | 8 | import asyncio |
8 | | -from typing import ClassVar, Any |
| 9 | +from typing import Any, ClassVar |
9 | 10 |
|
10 | 11 | from langchain.tools.base import BaseTool |
11 | | -from chuk_tool_processor.registry.auto_register import register_langchain_tool |
12 | | -from chuk_tool_processor.core.processor import ToolProcessor |
| 12 | + |
| 13 | +from chuk_tool_processor.registry import initialize, register_tool |
| 14 | +from chuk_tool_processor.execution.tool_executor import ToolExecutor |
13 | 15 | from chuk_tool_processor.models.tool_call import ToolCall |
14 | 16 |
|
15 | 17 |
|
16 | | -# ── LangChain tool definition ─────────────────────────────────────────────── |
| 18 | +# ---------------------------------------------------------------------- |
| 19 | +# 1. A regular LangChain tool (sync + async implementations) |
| 20 | +# ---------------------------------------------------------------------- |
17 | 21 | class PalindromeTool(BaseTool): |
18 | | - # pydantic requires concrete type annotations here |
19 | 22 | name: ClassVar[str] = "palindrome_tool" |
20 | | - description: ClassVar[str] = ( |
21 | | - "Return whether the given text is a palindrome." |
22 | | - ) |
| 23 | + description: ClassVar[str] = "Return whether the given text is a palindrome." |
23 | 24 |
|
24 | | - # synchronous implementation (BaseTool will call this from .run/.arun) |
| 25 | + # sync entry-point used by BaseTool.run() |
25 | 26 | def _run(self, tool_input: str, *args: Any, **kwargs: Any) -> dict: |
26 | 27 | is_pal = tool_input.lower() == tool_input[::-1].lower() |
27 | 28 | return {"text": tool_input, "palindrome": is_pal} |
28 | 29 |
|
29 | | - # asynchronous implementation (optional but nice to have) |
30 | | - async def _arun( |
31 | | - self, tool_input: str, run_manager: Any | None = None, **kwargs: Any |
32 | | - ) -> dict: # noqa: D401 |
33 | | - # Just delegate to the sync version for this demo |
| 30 | + # async entry-point used by BaseTool.arun() |
| 31 | + async def _arun( # noqa: D401 |
| 32 | + self, |
| 33 | + tool_input: str, |
| 34 | + run_manager: Any | None = None, |
| 35 | + **kwargs: Any, |
| 36 | + ) -> dict: |
34 | 37 | return self._run(tool_input) |
35 | 38 |
|
36 | 39 |
|
37 | | -# ── register with the global registry ─────────────────────────────────────── |
38 | | -register_langchain_tool(PalindromeTool()) |
| 40 | +# ---------------------------------------------------------------------- |
| 41 | +# 2. Minimal async wrapper exposing `.execute()` for the executor |
| 42 | +# ---------------------------------------------------------------------- |
| 43 | +@register_tool(name="palindrome_tool") |
| 44 | +class PalindromeAdapter: |
| 45 | + """ |
| 46 | + Thin adapter that forwards the call to the LangChain tool’s |
| 47 | + async API and simply returns its result. |
| 48 | + """ |
| 49 | + |
| 50 | + def __init__(self) -> None: |
| 51 | + # One tool instance is enough; LangChain tools are thread-safe. |
| 52 | + self._tool = PalindromeTool() |
| 53 | + |
| 54 | + async def execute(self, tool_input: str) -> dict: # chuk-tool signature |
| 55 | + return await self._tool.arun(tool_input=tool_input) |
39 | 56 |
|
40 | 57 |
|
41 | | -# ── quick test run ────────────────────────────────────────────────────────── |
| 58 | +# ---------------------------------------------------------------------- |
| 59 | +# 3. Demo run |
| 60 | +# ---------------------------------------------------------------------- |
42 | 61 | async def main() -> None: |
43 | | - proc = ToolProcessor(enable_caching=False) |
| 62 | + # Initialise the default registry *and* make sure our adapter is loaded |
| 63 | + registry = await initialize() # returns the same singleton each call |
44 | 64 |
|
45 | | - # Pretend the LLM called the tool with {"tool_input": "Madam"} |
| 65 | + # Create an executor bound to that registry |
| 66 | + executor = ToolExecutor(registry=registry) |
| 67 | + |
| 68 | + # Simulate an LLM-produced tool-call |
46 | 69 | call = ToolCall(tool="palindrome_tool", arguments={"tool_input": "Madam"}) |
47 | | - [result] = await proc.executor.execute([call]) |
48 | 70 |
|
49 | | - print("Tool results:") |
50 | | - print("·", result.tool, result.result) |
| 71 | + # Execute |
| 72 | + (result,) = await executor.execute([call]) |
| 73 | + |
| 74 | + # Show the outcome |
| 75 | + print("\n=== LangChain Tool Demo ===") |
| 76 | + if result.error: |
| 77 | + print("ERROR:", result.error) |
| 78 | + else: |
| 79 | + print("Tool :", result.tool) |
| 80 | + print("Data :", result.result) |
51 | 81 |
|
52 | 82 |
|
53 | 83 | if __name__ == "__main__": |
|
0 commit comments