Створення AI агента з нуля на Ruby — Практичний посібник

pic

Створення AI-агента з нуля за допомогою Ruby

Ця стаття не лише про написання коду; вона також про архітектуру та шаблони, які є основою розробки AI-агентів. Ruby, завдяки своїй простоті та читабельності, є ідеальним інструментом для ілюстрації цих концепцій. Елегантність цієї мови дозволяє зосередитись на самих патернах та принципах, не заглиблюючись у складний синтаксис. Мета цієї статті — зробити ці ідеї доступними та зрозумілими, незалежно від вашого досвіду в програмуванні.

Що таке AI-агент?

Уявіть собі систему, яка не просто обробляє інформацію, а активно взаємодіє з навколишнім середовищем. AI-агент сприймає своє оточення, розмірковує над проблемами, приймає рішення та здійснює цілеспрямовані дії для досягнення конкретних цілей. Хоча це звучить просто, концепція "агентного AI-системи" далеко не однозначна. Деякі визначення наголошують на автономії та прийнятті рішень, інші зосереджуються на співпраці, адаптивності чи специфічних технічних реалізаціях.

Цей термін охоплює широкий спектр підходів, архітектур та типів агентів — кожен з яких орієнтований на різні потреби та цілі. Наприклад, системи на основі правил покладаються на попередньо задану логіку, в той час як агенти, засновані на машинному навчанні, адаптують свою поведінку через навчання на даних. Існують різні архітектури, такі як ті, що описані в наукових статтях, від одноагентних до складних багатоягентних систем. У цій статті ми почнемо з простого і зосередимося на архітектурі ReAct (Reasoning and Action), щоб побудувати нашого першого AI-агента, використовуючи простоту та елегантність Ruby для його реалізації.

Архітектура агента

Архітектура AI-агента досить проста (як має бути на початку) і є типовою для багатьох агентних систем. В її центрі знаходиться Агент, який має набір інструментів, а LLM отримує інформацію про середовище, розмірковує і приймає рішення. Усі дії агента збираються в сесії, при цьому окремі операції зберігаються як "спани".

pic

Основна архітектура агента

Що стосується внутрішнього механізму розмірковування агента, то ми використаємо ReAct.

ReAct — це простий, але потужний підхід, представлений на початку ери AI-агентів на базі LLM у березні 2023 року цього технічного звіту. Він усуває розрив між розмірковуванням і дією, використовуючи можливості великих мовних моделей (LLM). На відміну від традиційних систем, які розділяють планування і виконання, ReAct безперешкодно поєднує розмірковування і дію, дозволяючи агенту динамічно генерувати думки та виконувати дії, орієнтуючись на конкретні завдання. Цей підхід дає агенту змогу не лише планувати і виконувати, але й адаптуватися в реальному часі.

Наприклад, агент може генерувати детальні сліди міркувань, переглядати і оновлювати свої плани дій у реальному часі, обробляти неочікувані ситуації та навіть використовувати зовнішні джерела, як-от API чи бази даних для додаткової інформації. Використовуючи структурований, крок за кроком підхід до вирішення проблем, ReAct гарантує, що кожне рішення буде і обґрунтованим, і здійсненним. Це робить його ідеальним вибором для створення універсальних, надійних AI-агентів, які здатні виконувати складні завдання у реальному світі з легкістю.

У цій статті ми не лише будуємо AI-агента, але й створюємо основу для міні-рамки (Ruby gem), що дозволить без труднощів інтегрувати AI-агентів у ваші Ruby-додатки. Ця рамка буде розроблена з урахуванням гнучкості та модульності, що дозволить легко розширювати чи змінювати її компоненти. Бажаєте експериментувати з іншою архітектурою? Не проблема. Просто замініть компонент "двигун" і ви готові до роботи. Наприкінці цієї подорожі ви отримаєте потужний, адаптивний набір інструментів, який розвивається разом із швидко змінюваним ландшафтом розробки AI-агентів.
І найкраща частина? Ви зрозумієте кожен елемент цього коду, від і до.

Агент

Клас Agent є точкою входу у фреймворк, представляючи собою сутність, яку можна використовувати для виконання різноманітних завдань. Він управляє загальним життєвим циклом взаємодій, що робить його основою архітектури.

Як було зазначено вище, я хочу відокремити високорівневу логіку агента від низькорівневої реалізації “циклу”, який керує агентом. Метою є зробити клас Agent зручним для використання, незалежно від внутрішньої архітектури, яка повинна бути легко змінною без великих зусиль. Агент, в своєму основному вигляді, є універсальною сутністю, що інтегрує велику мовну модель (LLM) та набір інструментів, які передаються під час ініціалізації.

Крім того, він веде список сесій (або викликів), оскільки екземпляр агента може бути викликаний кілька разів для різних завдань. Щоб уникнути таких проблем, як нескінченні цикли — звичайна проблема для агентів на базі LLM — ми встановлюємо ліміт ітерацій, який за замовчуванням становить 10.

  • execute: Приймає завдання, яке є рядком, що представляє команду або запит користувача. Цей метод ініціює сесію і передає завдання "двигуну" агента.
  • running?: Перевіряє, чи триває остання сесія. Цей дизайн підтримує виконання сесій послідовно. В майбутньому, коли буде підтримка паралельного виконання завдань, ця перевірка буде визначати, чи працює будь-яка сесія.
  • session: Простий геттер, який повертає останню сесію.
class Agent  
 include Concerns::Identifiable  

 DEFAULT_MAX_ITERATIONS = 10  

 def initialize(llm:, tools: [], **options)  
 super()  

 @llm = llm  
 @sessions = []  
 @tools = tools.is_a?(Toolchain) ? tools : Toolchain.new(Array(tools))  
 @max_iterations = options[:max_iterations] || DEFAULT_MAX_ITERATIONS  
 end  

 attr_reader :sessions, :llm, :tools  

 def execute(task)  
 raise ArgumentError, "Task cannot be empty" if task.to_s.strip.empty?  

 start_session  
 react.reason(task)  
 ensure  
 complete_session  
 end  

 def running?  
 session&.active? || false  
 end  

 def session  
 @sessions.last  
 end  

 private  

 def start_session  
 complete_session  
 @sessions << Session.new  
 session.start  
 end  

 def complete_session  
 session&.complete if running?  
 end  

 def react  
 Regent::Engine::React.new(llm, tools, session, @max_iterations)  
 end  
end

Сесія

Сесія — це серце AI-агента, що представляє собою один цикл взаємодії — від отримання завдання до доставки кінцевого результату. Вона діє як детальний журнал, який фіксує кожен крок агента для виконання призначеного завдання. Сесії є критично важливими для підтримки контексту, аналізу ефективності та розуміння того, як агент працює з часом.

Відстеження дій за допомогою спанів Кожна сесія зберігає масив спанів — малих, окремих операцій, які агент виконує під час сесії. Ці спани надають детальний погляд на робочий процес агента. Для кращого розуміння управління часом, сесія також фіксує час початку та завершення, що дозволяє проводити аналіз тривалості.
Ось деякі ключові методи:

  • start: Фіксує час початку сесії, позначаючи початок взаємодії.
  • continue: Захоплює виконання однієї операції, додаючи її як спан, та повертає результат операції.
  • complete: Означає кінець сесії, записує час завершення та отримує кінцевий результат, викликаючи метод виведення останнього спана.
  • running?: Визначає, чи сесія все ще активна, перевіряючи, чи існує час початку без відповідного часу завершення.
  • result: Повертає кінцевий результат сесії, надаючи простий спосіб отримати кінцевий продукт взаємодії.
  • result: Повертає кінцевий результат сесії, надаючи простий спосіб отримати кінцевий продукт взаємодії.

Історія комунікації Атрибут messages відстежує діалог між агентом і великою мовною моделлю (LLM), створюючи повний запис взаємодії. Це важливо для підтримки контексту та налагодження.

Посилання на агента Кожна сесія пов’язана з агентом, який її ініціював. Це з'єднання дозволяє сесії використовувати ресурси агента, такі як доступ до LLM через session.llm. Ця безшовна інтеграція забезпечує консистентність і ефективність.

Інкапсулюючи весь життєвий цикл виконання завдання агентом, клас Session надає структурований, прозорий і модульний спосіб управління взаємодіями. Будь то відстеження ефективності чи налагодження складних робочих процесів, сесії грають ключову роль у тому, щоб агенти штучного інтелекту були ефективними та надійними.

class Session  
 include Concerns::Identifiable  
 include Concerns::Durationable  

 class SessionError < StandardError; end  
 class InactiveSessionError < SessionError; end  
 class AlreadyStartedError < SessionError; end  

 def initialize  
 super()  

 @spans = []  
 @messages = []  
 @start_time = nil  
 @end_time = nil  
 end  

 attr_reader :id, :spans, :messages, :start_time, :end_time  

 # Починає сесію  
 # @raise [AlreadyStartedError] якщо сесія вже почата  
 # @return [void]  
 def start  
 raise AlreadyStartedError, "Session already started" if @start_time  

 @start_time = Time.now.freeze  
 end  

 # Виконує новий спан у сесії  
 # @param type [Symbol, String] Тип спана  
 # @param options [Hash] Опції для спана  
 # @raise [InactiveSessionError] якщо сесія не активна  
 # @return [String] Результат спана  
 def exec(type, options = {}, &block)  
 raise InactiveSessionError, "Cannot execute span in inactive session" unless active?  

 @spans << Span.new(type: type, arguments: options)  
 current_span.run(&block)  
 end  

 # Відтворює сесію  
 # @return [String] Результат сесії  
 def replay  
 spans.each { |span| span.replay }  
 result  
 end  

 # Завершує сесію та повертає результат  
 # @return [Object] Результат останнього спана  
 # @raise [InactiveSessionError] якщо сесія не активна  
 # @return [String] Результат останнього спана  
 def complete  
 raise InactiveSessionError, "Cannot complete inactive session" unless active?  

 @end_time = Time.now.freeze  
 result  
 end  

 # @return [Span, nil] Поточний спан або nil, якщо спанів немає  
 def current_span  
 @spans.last  
 end  

 # @return [String, nil] Виведення поточного спана або nil, якщо спанів немає  
 def result  
 current_span&.output  
 end  

 # @return [Boolean] Чи активна наразі сесія  
 def active?  
 start_time && end_time.nil?  
 end  

 # Додає повідомлення до сесії  
 # @param message [String] Повідомлення для додавання  
 # @raise [ArgumentError] якщо повідомлення nil або порожнє  
 def add_message(message)  
 raise ArgumentError, "Message cannot be nil or empty" if message.nil? || message.empty?  

 @messages << message  
 end  
end

Спан

Спан — це найменша одиниця роботи, яку виконує AI-агент під час сесії. Уявіть його як один крок у шляху агента до виконання завдання.
Спани (Spans) надають детальний погляд на діяльність агента, дозволяючи відстежувати та аналізувати його поведінку на кожному етапі виконання.

Клас Span є добре структурованим компонентом, який відстежує окремі операції в межах сесії. Ось як він працює:

Кожен спан має атрибут type, який визначає тип операції, яку він представляє. Звичні типи включають:

  • INPUT: Отримання вводу від користувача або іншої системи.
  • LLM_CALL: Взаємодія з великою мовною моделлю.
  • TOOL_EXECUTION: Виконання конкретного інструменту або функції.
  • MEMORY_ACCESS: Доступ або оновлення збереженої інформації.
  • ANSWER: Виведення кінцевого результату для користувача.

Спани записують час початку та завершення операції, що дозволяє детально аналізувати час виконання. Вони зберігають аргументи використані в операції та відповідний вивід, створюючи повну картину кожного кроку.

  • execute: Обробляє операцію на основі її типу та зберігає результат для подальшого використання.
  • running? та completed?: Вказують на те, чи спан зараз виконується, або вже завершений.

Клас Span підтримує специфічні реалізації для різних типів операцій, таких як llm_call, tool_execution, memory_access, input та answer. Кожен тип відповідає за виконання своєї задачі та записування деталей.

Розбиваючи дії агента на атомарні одиниці, спани пропонують безпрецедентне розуміння внутрішньої роботи сесії. Вони дають можливість:

  • Визначити вузькі місця в виконанні.
  • Аналізувати процес прийняття рішень агентом.
  • Легко налагоджувати складні взаємодії з чіткістю та точністю.
class Span  
 include Concerns::Identifiable  
 include Concerns::Durationable  

 module Type  
 INPUT = 'INPUT'.freeze  
 LLM_CALL = 'LLM'.freeze  
 TOOL_EXECUTION = 'TOOL'.freeze  
 MEMORY_ACCESS = 'MEMO'.freeze  
 ANSWER = 'ANSWER'.freeze  

 def self.all  
 constants.map { |c| const_get(c) }  
 end  

 def self.valid?(type)  
 all.include?(type)  
 end  
 end  

 # @param type [String] Тип спана (повинен бути одним з Type.all)  
 # @param arguments [Hash] Аргументи для спана  
 # @param logger [Logger] Екземпляр логера  
 def initialize(type:, arguments:, logger: Logger.new)  
 super()  

 validate_type!(type)  

 @logger = logger  
 @type = type  
 @arguments = arguments  
 @meta = nil  
 @output = nil  
 end  

 attr_reader :name, :arguments, :output, :type, :start_time, :end_time  

 # @raise [ArgumentError] якщо блок не передано  
 # @return [String] Вивід спана  
 def run  
 @output = log_operation do  
 yield  
 rescue StandardError => e  
 logger.error(label: type, message: e.message, **arguments)  
 raise  
 end  
 end  

 # @return [String] Вивід спана  
 def replay  
 log_operation(live: false) { @output }  
 end  

 # @return [Boolean] Чи спан зараз виконується  
 def running?  
 @start_time && @end_time.nil?  
 end  

 # @return [Boolean] Чи спан завершений  
 def completed?  
 @start_time && @end_time  
 end  

 # @param value [String] Мета-значення для встановлення  
 def set_meta(value)  
 @meta = value.freeze  
 end  

 private  

 attr_reader :logger, :meta  

 def validate_type!(type)  
 raise InvalidSpanType, "Invalid span type: #{type}" unless Type.valid?(type)  
 end  

 def log_operation(live: true, &block)  
 @start_time = live ? Time.now.freeze : @start_time  
 logger.start(label: type, **arguments)  

 result = yield  

 @end_time = live ? Time.now.freeze : @end_time  
 logger.success(label: type, **({ duration: duration.round(2), meta: meta }.merge(arguments)))  

 result  
 end  
end

Інструменти

В світі AI-агентів інструменти виступають як потужні розширення, що дозволяють агенту виконувати спеціалізовані завдання, які виходять за межі його основної логіки.
Клас Tool представляє ці зовнішні можливості, тоді як Toolchain виступає як менеджер, який організовує набір інструментів, доступних для агента.

class Tool  
 def initialize(name:, description:)  
 @name = name  
 @description = description  
 end  

 attr_reader :name, :description  

 def call(argument)  
 raise NotImplementedError, "Tool #{name} has not implemented the execute method"  
 end  

 def to_s  
 "#{name} - #{description}"  
 end  
end
class Toolchain  
 def initialize(tools)  
 @tools = tools  
 end  

 attr_reader :tools  

 def find(name)  
 tools.find { |tool| tool.name.downcase == name.downcase }  
 end  

 def to_s  
 tools.map(&:to_s).join("\n")  
 end  
end

Розділяючи спеціалізовані завдання на інструменти, ця архітектура забезпечує гнучкість та модульність. Агенти можуть адаптуватися до різних сценаріїв, просто оновлюючи набір інструментів (toolchain) новими інструментами, без необхідності змінювати основну логіку агента.

Наприклад:

  • Агент-чатбот може включати інструменти для отримання оновлень погоди, отримання записів з бази даних або створення звітів.
  • Розробник може додавати, видаляти або замінювати інструменти в залежності від розвитку вимог, зберігаючи агента адаптивним і готовим до майбутнього.

По суті, класи Tool та Toolchain формують динамічну, масштабовану систему, що підвищує універсальність агента, роблячи його більш ефективним вирішувачем проблем в різноманітних середовищах.

Двигун

Engine — це мозок агента, який організовує складну взаємодію між розумінням, діями та спостереженнями. Забезпечуючи модульність і чітке розділення логіки, він надає можливість розробникам:

  • Експериментувати з різними архітектурами без зміни решти фреймворку.
  • Безперешкодно налагоджувати взаємодії за допомогою сесій та спанів.
  • Розширювати функціональність, інтегруючи власні інструменти або змінюючи цикл міркування.

Хоча стандартна реалізація, Regent::Engine::React, слідує за шаблоном ReAct, дизайн дозволяє гнучкість. Розробники можуть замінювати різні класи для дослідження інших підходів та архітектур агентів, при цьому все одно користуючись єдиним API, відтворюваністю та можливістю налагодження через сесії та спани.

Метод reason керує робочим процесом двигуна.
Це:

  • Приймає завдання користувача як вхід.
  • Передає ввід користувача в сесію та ініціалізує історію повідомлень системним підказкою та повідомленням користувача.
  • Входить в цикл, де розмірковує, діє та спостерігає, використовуючи LLM для генерації відповідей, виконання інструментів і надання кінцевої відповіді.

Цей метод є інтерфейсом, який кожен двигун повинен реалізувати для того, щоб бути використаним як заміна двигуна та розширити фреймворк.

module Engine  
 class React  
 SEQUENCES = {  
 answer: "Answer:",  
 action: "Action:",  
 observation: "Observation:",  
 stop: "PAUSE"  
 }.freeze  

 def initialize(llm, toolchain, session, max_iterations)  
 @llm = llm  
 @toolchain = toolchain  
 @session = session  
 @max_iterations = max_iterations  
 end  

 attr_reader :llm, :toolchain, :session, :max_iterations  

 def reason(task)  
 initialize_session(task)  

 max_iterations.times do |i|  
 content = get_llm_response  
 session.add_message({role: :assistant, content: content })  
 return extract_answer(content) if answer_present?(content)  

 if action_present?(content)  
 tool, argument = parse_action(content)  
 return unless tool  

 process_tool_execution(tool, argument)  
 end  
 end  

 error_answer("Max iterations reached without finding an answer.")  
 end  

 private  

 def initialize_session(task)  
 session.add_message({role: :system, content: Regent::Engine::React::PromptTemplate.system_prompt(toolchain.to_s)})  
 session.add_message({role: :user, content: task})  
 session.exec(Span::Type::INPUT, message: task) { task }  
 end  

 def get_llm_response  
 session.exec(Span::Type::LLM_CALL, type: llm.model, message: session.messages.last[:content]) do  
 result = llm.invoke(session.messages, stop: [SEQUENCES[:stop]])  

 session.current_span.set_meta("#{result.usage.input_tokens} → #{result.usage.output_tokens} tokens")  
 result.content  
 end  
 end  

 def extract_answer(content)  
 answer = content.split(SEQUENCES[:answer])[1]&.strip  
 success_answer(answer)  
 end  

 def parse_action(content)  
 sanitized_content = content.gsub(SEQUENCES[:stop], "")  
 lookup_tool(sanitized_content)  
 end  

 def process_tool_execution(tool, argument)  
 result = session.exec(Span::Type::TOOL_EXECUTION, { type: tool.name, message: argument }) do  
 tool.call(argument)  
 end  

 session.add_message({ role: :user, content: "#{SEQUENCES[:observation]} #{result}" })  
 end  

 def answer_present?(content)  
 content.include?(SEQUENCES[:answer])  
 end  

 def action_present?(content)  
 content.include?(SEQUENCES[:action])  
 end  

 def success_answer(content)  
 session.exec(Span::Type::ANSWER, type: :success, message: content, duration: session.duration.round(2)) { content }  
 end  

 def error_answer(content)  
 session.exec(Span::Type::ANSWER, type: :failure, message: content, duration: session.duration.round(2)) { content }  
 end  

 def lookup_tool(content)  
 tool_name, argument = parse_tool_signature(content)  
 tool = toolchain.find(tool_name)  

 unless tool  
 session.exec(Span::Type::ANSWER, type: :failure, message: "No matching tool found for: #{tool_name}")  
 return [nil, nil]  
 end  

 [tool, argument]  
 end  

 def parse_tool_signature(content)  
 action = content.split(SEQUENCES[:action])[1]&.strip  
 return [nil, nil] unless action  

 parts = action.split('|', 2).map(&:strip)  
 tool_name = parts[0]  
 argument = parts[1].gsub('"', '')  

 # Обробка випадків, коли аргумент є nil, порожнім або містить лише пробіли  
 argument = nil if argument.nil? || argument.empty?  

 [tool_name, argument]  
 rescue  
 [nil, nil]  
 end  
 end

Щоб вийти за межі простого циклу і надати нашому агенту можливість приймати рішення, ми, звісно, повинні додати важливу частину. Системну підказку, яка спрямовує LLM поводитися певним чином, щоб він міг підтримувати AI-агента.
Двигун React використовує шаблон підказки (prompt template), який:

  • Надає LLM чіткі інструкції про те, як йому поводитись.
  • Включає приклади взаємодії між користувачем, LLM та інструментами, забезпечуючи послідовність та точність відповідей.
module Engine  
 class React  
 module PromptTemplate  
 def self.system_prompt(tool_names)  
 <<~PROMPT  
 Ви є асистентом, який розмірковує крок за кроком для вирішення складних проблем.  
 Ваш процес розмірковувань відбувається в циклі "Роздуми", "Дія", "Спостереження".  
 Роздуми - опис ваших думок щодо питання.  
 Дія - вибір дії з доступних інструментів. Якщо інструменти не можуть допомогти, поверніть відповідь, що ви не можете допомогти.  
 Спостереження - це результат виконання інструменту.  

 ## Доступні інструменти:  
 #{tool_names}  

 ## Приклад сесії  
 Питання: Яка погода в Лондоні сьогодні?  
 Роздуми: Мені потрібно дізнатись погоду в Лондоні.  
 Дія: weather_tool | "London"  
 PAUSE  

 Ви отримаєте відповідь із Спостереженням:  
 Спостереження: Сьогодні 32 градуси і сонячно  

 ... (цей цикл Роздуми/Дія/Спостереження може повторюватися N разів)  

 Роздуми: Я знаю остаточну відповідь  
 Відповідь: Сьогодні 32 градуси і сонячно в Лондоні  
 PROMPT  
 end  
 end  
 end  
end

Системна підказка спрямовує Large Language Model (LLM) спочатку генерувати роздуми — розмірковувати, як підійти до вирішення проблеми. Згідно з цими роздумами, модель або надає пряму відповідь користувачеві, якщо має достатньо інформації, або виявляє потребу в додаткових даних. У цьому випадку модель вказує на необхідність виконати дію, використовуючи один з наданих інструментів для збору відсутньої інформації.

Агент виконує цю дію з зазначеними параметрами, використовує відповідний інструмент і повертає результат як спостереження LLM. Модель потім відповідає новими роздумами, після чого або надає відповідь, або вказує на наступну дію. Цей ітеративний процес триває, поки модель не знайде рішення або не досягне максимального ліміту ітерацій.

Логер

Я не буду заглиблюватися в подробиці класу Logger, але ви можете ознайомитися з його реалізацією на GitHub за посиланням в кінці цієї статті. Компонент Logger відіграє важливу роль у забезпеченні прозорості та чіткості виконання агента, виводячи кожен крок сесії з детальною та корисною інформацією. Ви побачите його вплив у наведених нижче прикладах.

Час діяти!

Давайте одразу перейдемо до створення чогось простого. Для тесту ми швидко побудуємо агента, який може відповісти на питання про часто змінювану інформацію, щоб перевірити здатність агента отримувати інформацію, а не покладатися лише на знання LLM. Давайте отримаємо актуальні ціни на криптовалюту.

Крок 1: Створення інструменту для цін

Почнемо зі створення класу PriceTool, який запитує API CoinGecko для отримання останніх цін на криптовалюту. Для простоти я просто задам USD як валюту повернення.

class PriceTool < Tool  
 def call(query)  
 fetch_crypto_price(query, "usd")  
 end  

 private  

 def fetch_crypto_price(crypto_id, currency)  
 url = "https://api.coingecko.com/api/v3/simple/price"  
 response = HTTP.get(url, params: { ids: crypto_id.downcase, vs_currencies: currency })  

 if response.status.success?  
 data = response.parse  
 price = data.dig(crypto_id.downcase, currency)  
 return "$#{price}"  
 else  
 raise "Error fetching data: #{response.status}"  
 end  
 end  
end

Крок 2: Налаштування LLM та агента

Далі ми ініціалізуємо модель Language Learning Model (LLM) за допомогою класу Regent::LLM, який буде шукати API-ключ у змінних середовища, залежно від моделі, яку ви передаєте як аргумент. Ми використаємо gpt-4o-mini, тому треба переконатися, що змінна середовища OPENAI_API_KEY встановлена.

model = Regent::LLM.new(model: "gpt-4o-mini")

Ми також ініціалізуємо інструмент для отримання цін, щоб наш агент міг отримувати актуальні ціни на криптовалюту.
Ось як ви можете налаштувати все:

tool = PriceTool.new(name: "price_tool", description: "Отримати ціни на криптовалюти")

Наприкінці давайте ініціалізуємо агента з створеною моделлю та інструментом:

agent = Agent.new(llm: llm, tools: [price_tool])

Крок 3: Задаємо кілька запитань!

Тепер, коли налаштування завершено, давайте взаємодіємо з нашим агентом. Ми запитаємо його про ціну Біткоїна:

pic

Агент, що використовує модель gpt-4o-mini та інструмент pricetool для відповіді про ціну Біткоїна_

Крок 4: Запитання про кілька монет

А що якщо ми хочемо дізнатися ціни на кілька криптовалют одночасно? Давайте спробуємо запитати про Ефіріум і Деш (але також зробимо орфографічну помилку і подивимося, як агент впорається з завданням):

pic

Агент, що використовує модель gpt-4o-mini та інструмент pricetool для відповіді про ціни Ефіріума та Деша_

Крок 5: Трохи складніше запитання

А що якщо ціна — не кінцева мета? Що, якщо ми хочемо дізнатися, яка з валют є найдорожчою чи найдешевшою? І для того, щоб відповісти на це, агент має подивитися на ціни цих валют.

pic

Агент, що використовує модель gpt-4o-mini та інструмент pricetool для визначення найдорожчої криптовалюти зі списку_

Крок 6: Невелика несподіванка — запит про погоду

А тепер подивимося, що трапиться, якщо ми запитаємо нашого агента про щось, до чого він не готовий, наприклад, про погоду в Лондоні:

pic

Оскільки наш агент не оснащений інструментами для отримання даних про погоду, це був очікуваний результат.

Це, безумовно, дуже прості приклади, але це лише перші кроки нашого AI агента. У коді вище ще є багато чого, що можна покращити, і, звісно, виникнуть баги 🙂

За замовчуванням наш агент не може обробити кожне завдання, яке ми йому запропонуємо. Але пам’ятайте, це не AGI (Штучний Загальний Інтелект), а спеціалізований AI агент, створений для виконання конкретного набору завдань. Ключова мета — створити агента, який розуміє людську мову та виконує дії, необхідні для досягнення поставленої мети.

Як і обіцяв, я зібрав ці компоненти в Ruby гем під назвою Regent. Цей гем дозволяє легко будувати базових AI агентів і розширювати їх можливості, додаючи спеціальні інструкції для агента та нові інструменти. Ви можете ознайомитися з ним на GitHub:

Regent на GitHub — дайте зірку, якщо ця стаття була корисною.

Спробуйте та дайте знати, що ви думаєте! Зірки та відгуки завжди оцінюються, а внесок завжди вітається. 😉

Перекладено з: Building AI Agent from Scratch with Ruby — A Practical Guide

Leave a Reply

Your email address will not be published. Required fields are marked *