آموزش Agent-بخش 4: ابزارها (Tools) چیست؟

یکی از جنبههای مهم عاملهای هوش مصنوعی، توانایی آنها در انجام اقدامات (action) است. همانطور که دیدیم، این کار از طریق استفاده از ابزارها (Tools) انجام میشود.
در این بخش، یاد میگیریم که ابزارها چیست، چگونه آنها را به طور مؤثر طراحی کنیم و چگونه آنها را از طریق پیام سیستمی (System Message) در عامل خود ادغام کنیم.
با دادن ابزارهای مناسب به عامل خود – و توصیف واضح نحوه کارکرد این ابزارها – میتوانید به طور چشمگیری آنچه هوش مصنوعی شما میتواند انجام دهد را افزایش دهید. بیایید شروع کنیم!
ابزارهای هوش مصنوعی چیست؟
یک ابزار، تابعی است که به LLM داده میشود. این تابع باید هدف مشخصی را برآورده کند.
در اینجا برخی از ابزارهای رایج مورد استفاده در عاملهای هوش مصنوعی آمده است:
ابزار | توضیحات |
---|---|
جستجوی وب | به عامل اجازه میدهد اطلاعات بهروز را از اینترنت دریافت کند. |
تولید تصویر | تصاویر را بر اساس توصیفات متنی ایجاد میکند. |
بازیابی | اطلاعات را از یک منبع خارجی بازیابی میکند. |
رابط API | با یک API خارجی (GitHub، YouTube، Spotify و غیره) تعامل میکند. |
اینها فقط مثالهایی هستند، زیرا شما در واقع میتوانید برای هر مورد استفادهای یک ابزار ایجاد کنید!
یک ابزار خوب باید مکمل قدرت یک LLM باشد.
به عنوان مثال، اگر نیاز به انجام محاسبات ریاضی دارید، دادن یک ابزار ماشین حساب به LLM خود نتایج بهتری نسبت به تکیه بر قابلیتهای ذاتی مدل ارائه خواهد داد.
علاوه بر این، LLMها تکمیل یک پرامپت را بر اساس دادههای آموزشی خود پیشبینی میکنند، که به این معنی است که دانش داخلی آنها فقط شامل رویدادهای قبل از آموزش آنها میشود. بنابراین، اگر عامل شما به دادههای بهروز نیاز دارد، باید آن را از طریق یک ابزار فراهم کنید.
به عنوان مثال، اگر مستقیماً از یک LLM (بدون ابزار جستجو) درباره آب و هوای امروز بپرسید، LLM احتمالاً آب و هوای تصادفی را توهم خواهد کرد.
یک ابزار باید شامل موارد زیر باشد:
- توضیح متنی از آنچه تابع انجام میدهد.
- یک قابل فراخوانی (Callable) (چیزی برای انجام یک عمل).
- آرگومانها با تعیین نوع داده.
- (اختیاری) خروجیها با تعیین نوع داده.
ابزارها چگونه کار میکنند؟
همانطور که دیدیم، LLMها فقط میتوانند ورودیهای متنی دریافت کنند و خروجیهای متنی تولید کنند. آنها به خودی خود راهی برای فراخوانی ابزارها ندارند. وقتی درباره ارائه ابزار به یک عامل صحبت میکنیم، منظور این است که ما به LLM درباره وجود ابزارها آموزش میدهیم و از مدل میخواهیم متنی تولید کند که در صورت نیاز، ابزارها را فراخوانی کند.
برای مثال، اگر ابزاری برای بررسی آب و هوا در یک مکان از اینترنت ارائه دهیم و سپس از LLM درباره آب و هوای پاریس بپرسیم، LLM این سؤال را به عنوان یک فرصت مناسب برای استفاده از ابزار “آب و هوا” که به آن آموزش دادهایم، تشخیص میدهد. LLM متنی را به شکل کد تولید میکند تا آن ابزار را فراخوانی کند.
این مسئولیت عامل است که خروجی LLM را تجزیه کند، تشخیص دهد که یک فراخوانی ابزار لازم است و ابزار را از طرف LLM فراخوانی کند. خروجی از فراخوانی ابزار سپس به LLM بازگردانده میشود، که پاسخ نهایی خود را برای کاربر تنظیم میکند.
خروجی از یک فراخوانی ابزار، نوع دیگری از پیام در مکالمه است. مراحل فراخوانی ابزار معمولاً به کاربر نشان داده نمیشود: عامل مکالمه را بازیابی میکند، ابزار(ها) را فراخوانی میکند، خروجیها را دریافت میکند، آنها را به عنوان یک پیام مکالمه جدید اضافه میکند و مکالمه بهروز شده را دوباره به LLM ارسال میکند. از دیدگاه کاربر، مانند این است که LLM از ابزار استفاده کرده است، اما در واقع این کد برنامه ما (عامل) بود که این کار را انجام داد.
ما در پستهای بعدی بیشتر درباره این فرآیند صحبت خواهیم کرد.
چگونه ابزارها را به یک LLM میدهیم؟
پاسخ کامل ممکن است کمی پیچیده به نظر برسد، اما ما اساساً از پرامپت سیستمی برای ارائه توضیحات متنی از ابزارهای موجود به مدل استفاده میکنیم:
برای اینکه این کار موفقیتآمیز باشد، باید بسیار دقیق و صحیح در مورد موارد زیر باشیم:
- ابزار چه کاری انجام میدهد
- دقیقاً چه ورودیهایی انتظار دارد
به همین دلیل است که توضیحات ابزارها معمولاً با استفاده از ساختارهای گویا اما دقیق، مانند زبانهای کامپیوتری یا JSON ارائه میشوند. لزوماً نیازی نیست که به این شکل انجام شود، هر فرمت دقیق و منسجمی میتواند کار کند.
اگر این موضوع بیش از حد نظری به نظر میرسد، بیایید آن را از طریق یک مثال عملی درک کنیم.
ما یک ابزار ماشین حساب سادهشده را پیادهسازی خواهیم کرد که فقط دو عدد صحیح را ضرب میکند. این میتواند پیادهسازی پایتون ما باشد:
def calculator(a: int, b: int) -> int: """Multiply two integers.""" return a * b
بنابراین ابزار ما calculator نام دارد، دو عدد صحیح را ضرب میکند و به ورودیهای زیر نیاز دارد:
- a (int): یک عدد صحیح.
- b (int): یک عدد صحیح.
خروجی ابزار، یک عدد صحیح دیگر است که میتوانیم آن را به این صورت توصیف کنیم:
- (int): حاصلضرب a و b.
تمام این جزئیات مهم هستند. بیایید آنها را در یک رشته متنی قرار دهیم که ابزار ما را برای درک LLM توصیف میکند.
Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int
یادآوری: این توضیح متنی همان چیزی است که میخواهیم LLM درباره ابزار بداند.
وقتی رشته قبلی را به عنوان بخشی از ورودی به LLM ارسال میکنیم، مدل آن را به عنوان یک ابزار تشخیص میدهد و میداند چه چیزی را باید به عنوان ورودی ارسال کند و از خروجی چه انتظاری داشته باشد.
اگر بخواهیم ابزارهای اضافی ارائه دهیم، باید همیشه از همان فرمت استفاده کنیم. این فرآیند میتواند شکننده باشد و ممکن است به طور تصادفی برخی جزئیات را نادیده بگیریم.
فرمتدهی خودکار بخشهای ابزار
ابزار ما در پایتون نوشته شده است و پیادهسازی آن قبلاً همه چیزهایی را که نیاز داریم ارائه میدهد:
- یک نام توصیفی از آنچه انجام میدهد: calculator
- توضیحات طولانیتر، که توسط توضیحات docstring تابع ارائه شده است: Multiply two integers.
- ورودیها و نوع آنها: تابع به وضوح دو int انتظار دارد.
- نوع خروجی.
دلیلی وجود دارد که مردم از زبانهای برنامهنویسی استفاده میکنند: آنها گویا، مختصر و دقیق هستند.
ما میتوانیم کد منبع پایتون را به عنوان مشخصات ابزار برای LLM ارائه دهیم، اما نحوه پیادهسازی ابزار مهم نیست. تنها چیزی که مهم است، نام آن، کاری که انجام میدهد، ورودیهایی که انتظار دارد و خروجی که ارائه میدهد.
ما از ویژگیهای introspection پایتون استفاده خواهیم کرد تا از کد منبع بهرهبرداری کنیم و به طور خودکار یک توضیح ابزار برای ما بسازیم. تمام چیزی که نیاز داریم این است که پیادهسازی ابزار از type hints، docstrings و نامهای تابع معقول استفاده کند. ما کدی خواهیم نوشت تا بخشهای مربوطه را از کد منبع استخراج کنیم.
پس از اتمام کار، فقط نیاز داریم از یک دکوراتور پایتون استفاده کنیم تا نشان دهیم که تابع calculator یک ابزار است:
@tool def calculator(a: int, b: int) -> int: """Multiply two integers.""" return a * b print(calculator.to_string())
توجه کنید که دکوراتور @tool
قبل از تعریف تابع قرار گرفته است.
با پیادهسازی که در ادامه خواهیم دید، قادر خواهیم بود متن زیر را به صورت خودکار از کد منبع از طریق تابع to_string()
که توسط دکوراتور ارائه شده، بازیابی کنیم:
Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int
همانطور که میبینید، این دقیقاً همان چیزی است که قبلاً به صورت دستی نوشته بودیم!
پیادهسازی ابزار عمومی
ما یک کلاس Tool
عمومی ایجاد میکنیم که میتوانیم هر زمان که نیاز به استفاده از یک tool داشته باشیم، از آن مجدداً استفاده کنیم.
تذکر: این پیادهسازی نمونه غیر واقعی است که شباهت زیادی به پیادهسازیهای واقعی در اکثر کتابخانهها دارد.
class Tool: """ A class representing a reusable piece of code (Tool). Attributes: name (str): Name of the tool. description (str): A textual description of what the tool does. func (callable): The function this tool wraps. arguments (list): A list of argument. outputs (str or list): The return type(s) of the wrapped function. """ def __init__(self, name: str, description: str, func: callable, arguments: list, outputs: str): self.name = name self.description = description self.func = func self.arguments = arguments self.outputs = outputs def to_string(self) -> str: """ Return a string representation of the tool, including its name, description, arguments, and outputs. """ args_str = ", ".join([ f"{arg_name}: {arg_type}" for arg_name, arg_type in self.arguments ]) return ( f"Tool Name: {self.name}," f" Description: {self.description}," f" Arguments: {args_str}," f" Outputs: {self.outputs}" ) def __call__(self, *args, **kwargs): """ Invoke the underlying function (callable) with provided arguments. """ return self.func(*args, **kwargs)
در نگاه اول ممکن است پیچیده به نظر برسد، اما بیایید بررسی کرده و ببینیم هر قسمت چه کاری انجام میدهد. ما یک کلاس Tool تعریف میکنیم که شامل موارد زیر است:
- name (str): نام ابزار.
- description (str): توضیح مختصری از آنچه ابزار انجام میدهد.
- function (callable): تابعی که ابزار اجرا میکند.
- arguments (list): پارامترهای ورودی مورد انتظار.
- outputs (str or list): خروجیهای مورد انتظار ابزار.
- call(): هنگامی که نمونه ابزار فراخوانی میشود، تابع را فراخوانی میکند.
- to_string(): ویژگیهای ابزار را به یک نمایش متنی تبدیل میکند.
ما میتوانیم با استفاده از کدی مانند زیر، یک ابزار با این کلاس ایجاد کنیم:
calculator_tool = Tool( "calculator", # name "Multiply two integers.", # description calculator, # function to call [("a", "int"), ("b", "int")], # inputs (names and types) "int", # output )
اما ما همچنین میتوانیم از ماژول inspect
پایتون برای بازیابی تمام اطلاعات برای خودمان استفاده کنیم! این همان کاری است که دکوراتور @tool
انجام میدهد.
اگر علاقهمند هستید، میتوانید بخش زیر را برای مشاهده پیادهسازی دکوراتور باز کنید.
decorator code
def tool(func): """ A decorator that creates a Tool instance from the given function. """ # Get the function signature signature = inspect.signature(func) # Extract (param_name, param_annotation) pairs for inputs arguments = [] for param in signature.parameters.values(): annotation_name = ( param.annotation.__name__ if hasattr(param.annotation, '__name__') else str(param.annotation) ) arguments.append((param.name, annotation_name)) # Determine the return annotation return_annotation = signature.return_annotation if return_annotation is inspect._empty: outputs = "No return annotation" else: outputs = ( return_annotation.__name__ if hasattr(return_annotation, '__name__') else str(return_annotation) ) # Use the function's docstring as the description (default if None) description = func.__doc__ or "No description provided." # The function name becomes the Tool name name = func.__name__ # Return a new Tool instance return Tool( name=name, description=description, func=func, arguments=arguments, outputs=outputs )
برای تأکید مجدد، با استفاده از این دکوراتور میتوانیم ابزار خود را به این صورت پیادهسازی کنیم:
@tool def calculator(a: int, b: int) -> int: """Multiply two integers.""" return a * b print(calculator.to_string())
و میتوانیم از متد to_string
ابزار برای بازیابی خودکار متنی مناسب استفاده کنیم که به عنوان توضیح ابزار برای یک LLM مورد استفاده قرار گیرد:
Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int
این توضیح در پرامپت سیستم تزریق میشود. با توجه به مثالی که در ابتدای این بخش شروع کردیم، پس از جایگزینی tools_description
به این صورت میگردد:
در پستهای بعدی و در بخش Actionها، بیشتر درباره چگونگی فراخوانی ابزاری که همین الان ایجاد کردیم توسط یک Agent میآموزیم.
ابزارها نقش مهمی در افزایش قابلیتهای Agentهای هوش مصنوعی ایفا میکنند.
چیزهایی که در این پست آموختیم:
- ابزارها چیستند: توابعی که به مدلهای زبانی بزرگ قابلیتهای اضافی میدهند، مانند انجام محاسبات یا دسترسی به دادههای خارجی.
- چگونگی تعریف یک ابزار: با ارائه توضیح متنی واضح، ورودیها، خروجیها و یک تابع قابل فراخوانی.
- چرا ابزارها ضروری هستند: آنها به عاملها امکان میدهند بر محدودیتهای آموزش ثابت مدل غلبه کنند، وظایف زمان واقعی را مدیریت کنند و اقدامات تخصصی انجام دهند.
حال در پست آتی، میتوانیم به گردش کار عامل (Agent Workflow) بپردازیم که در آن خواهید دید چگونه یک عامل مشاهده میکند، فکر میکند و عمل میکند. بدین صورت همه آنچه را که تاکنون پوشش دادهایم کنار هم قرار داده و زمینه را برای ایجاد Agent هوش مصنوعی کاملاً کاربردی خودتان فراهم میکند.
اما قبل از انتهای این پست، وقت یک کوییز کوتاه از مباحث این پست است! برای شرکت در کوییز کلیک کنید.
دیدگاهتان را بنویسید