Перейти к содержимому
ПереводыDatawhale и китайские курсы

Tiny Universe, часть 4: TinyAgent — минимальный агент ReAct

Полный перевод раздела TinyAgent из tiny-universe: модель InternLM2, описание инструмента «поиск Google», системная подсказка в формате ReAct, разбор вызова инструмента и пример диалога — плюс пояснения, чем этот учебный агент отличается от настоящего цикла агента.

Эфир «Агент своими руками»: записи эфира пока нет — найдите в видеоканале Datawhale (WeChat) ролик «Практический разбор „большая модель с нуля“. Tiny-Agent: строим минимальную систему агента».

После того как ChatGPT появился как гром среди ясного неба и отобрал корону у Bert, большие модели становятся всё популярнее; в Китае одна модель появляется за другой — это время назвали «войной ста моделей». Способности больших моделей бесспорны, но в вопросах о текущих событиях или в узких профессиональных областях им может не хватать сил. Поэтому моделям нужны инструменты, которые дадут им «рычаг» и позволят сверять детали с тем, что происходит в реальном мире, — так получается модель, которой удобнее пользоваться.

Здесь, следуя подходу ReAct, мы вручную сделали минимальную структуру Agent (на деле это скорее вызов инструментов). Летом попробуем переделать структуру ReAct в структуру SOP.

Если писать Agent шаг за шагом своими руками, лучше понимаешь, из чего он состоит и как работает. Ниже — несколько небольших примеров из статьи о ReAct.

Статья: ReAct: Synergizing Reasoning and Acting in Language Models

Рисунок в оригинале (images/React.png): примеры из статьи ReAct — модель чередует «мысли» (Thought), «действия» (Act: поиск в Википедии и т. п.) и «наблюдения» (Obs) и приходит к верному ответу там, где только рассуждения или только действия ошибаются.

Сначала нужна большая модель; в качестве модели агента возьмём InternLM2. InternLM2 — универсальная диалоговая модель архитектуры Decoder-Only, её можно загрузить библиотекой transformers.

Как и раньше, начнём с класса BaseModel и определим в нём базовые методы, например chat и load_model, — чтобы потом легко подключать другие модели.

class BaseModel:
def __init__(self, path: str = '') -> None:
self.path = path
def chat(self, prompt: str, history: List[dict]):
pass
def load_model(self):
pass

Затем создаём класс InternLM2, наследник BaseModel, и реализуем в нём методы chat и load_model. Загружаем InternLM2 так же, как обычно, и просто возвращаем ответ.

class InternLM2Chat(BaseModel):
def __init__(self, path: str = '') -> None:
super().__init__(path)
self.load_model()
def load_model(self):
print('================ Loading model ================')
self.tokenizer = AutoTokenizer.from_pretrained(self.path, trust_remote_code=True)
self.model = AutoModelForCausalLM.from_pretrained(self.path, torch_dtype=torch.float16, trust_remote_code=True).cuda().eval()
print('================ Model loaded ================')
def chat(self, prompt: str, history: List[dict], meta_instruction:str ='') -> str:
response, history = self.model.chat(self.tokenizer, prompt, history, temperature=0.1, meta_instruction=meta_instruction)
return response, history

В файле tools.py создадим инструменты, например «поиск Google». В этом файле делаем класс Tools и добавляем в него описание каждого инструмента и его реализацию.

Описание инструмента нужно, чтобы при сборке system_prompt модель знала, какие инструменты можно вызвать, что они делают и какие у них параметры.

  • Сначала добавляем в tools описание инструмента.
  • Затем добавляем в tools его реализацию.

Для поиска Google нужно получить token на сайте serper: https://serper.dev/dashboard, а потом вписать ключ в файл tools.py. Каждый может бесплатно получить один ключ с 2500 бесплатными вызовами — для экспериментов хватит.

class Tools:
def __init__(self) -> None:
self.toolConfig = self._tools()
def _tools(self):
tools = [
{
'name_for_human': 'Поиск Google',
'name_for_model': 'google_search',
'description_for_model': 'Поиск Google — универсальная поисковая система: доступ в интернет, справочные знания, новости.',
'parameters': [
{
'name': 'search_query',
'description': 'ключевое слово или фраза для поиска',
'required': True,
'schema': {'type': 'string'},
}
],
}
]
return tools
def google_search(self, search_query: str):
pass

В файле Agent.py создаём класс Agent — агента в парадигме ReAct. В нём реализован метод text_completion — метод диалога: он вызывает модель InternLM2 и по логике агента ReAct вызывает инструменты из Tools.

Сначала собираем system_prompt — системную подсказку. В неё можно добавить системные указания, например подсказку в формате ReAct.

def build_system_input(self):
tool_descs, tool_names = [], []
for tool in self.tool.toolConfig:
tool_descs.append(TOOL_DESC.format(**tool))
tool_names.append(tool['name_for_model'])
tool_descs = '\n\n'.join(tool_descs)
tool_names = ','.join(tool_names)
sys_prompt = REACT_PROMPT.format(tool_descs=tool_descs, tool_names=tool_names)
return sys_prompt

Если всё прошло гладко, результат будет таким:

Answer the following questions as best you can. You have access to the following tools:
google_search: Call this tool to interact with the Поиск Google API. What is the Поиск Google API useful for? Поиск Google — универсальная поисковая система: доступ в интернет, справочные знания, новости. Parameters: [{'name': 'search_query', 'description': 'ключевое слово или фраза для поиска', 'required': True, 'schema': {'type': 'string'}}] Format the arguments as a JSON object.
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [google_search]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can be repeated zero or more times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!

Эта system_prompt сообщает модели, какие инструменты она может вызывать, в каком формате отвечать, что делает каждый инструмент и какие параметры он принимает.

Пока реализован только простой инструмент «поиск Google»; потом добавятся инструменты для анализа в геоинформационных системах — да, я студент-геоинформатик.

Полная структура агента — в tinyAgent/Agent.py. Скажем коротко: структура Agent — это структура ReAct: system_prompt сообщает модели, какие инструменты она может вызвать и в каком формате отвечать.

На каждый вопрос пользователя, если нужен инструмент, модель вызывается два раза: первый раз — чтобы разобрать вопрос и выбрать инструмент и параметры, второй — чтобы объединить результат инструмента с вопросом пользователя. Так и получается структура ReAct.

Ниже — упрощённый код Agent; полная реализация каждой функции — в tinyAgent/Agent.py.

class Agent:
def __init__(self, path: str = '') -> None:
pass
def build_system_input(self):
# собрать системную подсказку, описанную выше
pass
def parse_latest_plugin_call(self, text):
# разобрать первый ответ модели: какой инструмент выбран и с какими параметрами
pass
def call_plugin(self, plugin_name, plugin_args):
# вызвать выбранный инструмент
pass
def text_completion(self, text, history=[]):
# объединить два вызова модели
pass

Рисунок в оригинале (images/Agent.png): схема работы агента — ниже она перерисована по коду Agent.py.

flowchart TB
U["Вопрос пользователя"] --> P1["Вызов 1: модель + system_prompt<br/>ответ: Thought / Action / Action Input"]
P1 --> PARSE["parse_latest_plugin_call:<br/>ищем последние Action и Action Input"]
PARSE -->|"инструмент выбран"| TOOL["call_plugin → google_search(...)<br/>добавляем Observation"]
PARSE -->|"инструмента нет"| P2
TOOL --> P2["Вызов 2: модель продолжает текст<br/>с Observation"]
P2 --> A["Final Answer"]
Схема переводчика по коду tinyAgent/Agent.py

В этом примере использована модель InternLM2-chat-7B. Если хотите, чтобы Agent работал стабильнее, возьмите её «большую чашку» — InternLM2-20b-chat: это повышает стабильность агента.

from Agent import Agent
agent = Agent('/root/share/model_repos/internlm2-chat-20b')
response, _ = agent.text_completion(text='Привет', history=[])
print(response)
# Thought: Здравствуйте, чем могу помочь?
# Action: google_search
# Action Input: {'search_query': 'Привет'}
# Observation: (сниппет первого результата поиска — страница англо-китайского онлайн-словаря с примерами перевода)
# Final Answer: Здравствуйте, чем могу помочь?
response, _ = agent.text_completion(text='В каком году родился Джей Чоу?', history=_)
print(response)
# Final Answer: Джей Чоу родился в 1979 году.
response, _ = agent.text_completion(text='Кто такой Джей Чоу?', history=_)
print(response)
# Thought: По результатам поиска, Джей Чоу — тайваньский певец-автор, пианист и автор песен. Его первый альбом
# «Джей» вышел в 2000 году, его музыку слушают в Азиатско-Тихоокеанском регионе и на Западе.
# Final Answer: Джей Чоу — тайваньский певец-автор, пианист, автор песен и музыкальный продюсер. В 2000 году
# он выпустил первый альбом «Джей», его музыку слушают в Азиатско-Тихоокеанском регионе и на Западе. У него
# своеобразный стиль, в котором смешаны поп, рок, хип-хоп, электроника и другие жанры, и множество поклонников
# по всему миру. Самые известные его песни — «Аромат риса», «Бело-синий фарфор», «Слушай маму» и другие.
response, _ = agent.text_completion(text='Какой у него первый альбом?', history=_)
print(response)
# Final Answer: Первый альбом Джея Чоу — «Jay».

Не забудьте поставить репозиторию маленькую звёздочку!

От переводчика: как запустить на своей машине

Заголовок раздела «От переводчика: как запустить на своей машине»

Что сделать руками:

  1. Добавьте второй инструмент — calculator(expression: str) — в _tools() и в call_plugin. Спросите агента «Сколько будет 17,5% от 2340?» и проверьте, что он выбрал калькулятор.
  2. Перепишите text_completion в цикл: до 5 шагов, выход по Final Answer. Задайте вопрос, требующий двух поисков подряд («Сколько лет было автору песни „Аромат риса“, когда она вышла?»).
  3. Сравните с версией агента из Happy-LLM (раздел 7.3, вызов функций через API): что в ней делает модель, а что — библиотека?
EnglishРусскийПояснение
agentагентLLM + инструменты + цикл решений
tool / pluginинструмент, плагинфункция, которую может вызвать модель
system promptсистемная подсказкаинструкции модели «сверху» диалога
ReAct (Reason + Act)ReAct, «рассуждай и действуй»чередование Thought / Action / Observation
Observationнаблюдениерезультат вызова инструмента
Final Answerокончательный ответсигнал остановки цикла
SOP (standard operating procedure)стандартная процедураагент по заранее заданному сценарию
war of a hundred models«война ста моделей»бум китайских LLM 2023 года