Как создать простой агент с Guidance и локальной моделью LLM

Когда я попытался создать агент ReAct с помощью Langchain, то понял, насколько сложно обеспечивать его корректную работу на постоянной основе. Модель часто сбивается и не выполняет надлежащие инструкции. Чаще всего это касается небольших моделей, таких как Alpaca/Vicuna 3B-7B. Процесс выполнения останавливается из-за синтаксических ошибок в регулярных выражениях. Довольно сложно заставить модель строго следовать инструкциям, и можно потратить много времени на оптимизацию текстовых подсказок. Следовательно, в данном случае не обойтись без инструмента для управления выводом больших языковых моделей (LLM). Именно эту задачу решает Guidance!

Подробнее о Guidance

Guidance  —  это инструмент от Microsoft, представляющий собой “язык, организующий управление LLM”. Он позволяет управлять выводом LLM, что облегчает выполнение инструкций. Что касается GPT 3.5–4, то он успешно работает с большинством инструкций. Но небольшие локальные модели, такие как LLaMa и ее разновидности (Alpca, WizardML), не всегда выдают правильный ответ. А это большая проблема. Некоторые фреймворки, такие как ReAct, требуют определенного формата инструкций для ответа. Кроме того, работу усложняет тот факт, что LLM иногда предоставляет данные в формате JSON с синтаксической ошибкой. 

Пример:

valid_dish = ["Pizza", "Noodles", "Pho"]

# определение текстовой подсказки
order_maker = guidance("""The following is a order in JSON format.
```json
{
"name": "{{name}}",
"age": {{gen 'age' pattern='[0-9]+' stop=','}},
"delivery": "{{#select 'delivery'}}Yes{{or}}No{{/select}}",
"order": "{{select 'order' options=valid_dish}}",
"amount": {{gen 'amount' pattern='[0-9]+' stop=','}}
}```""")

# генерация имени заказчика доставки
order_maker(
name="Alex",
valid_dish=valid_dish
)
**Вывод**
The following is a order in JSON format.
```json
{
"name": "Alex",
"age": 25,
"delivery": "Yes",
"order": "Noodles",
"amount": 10
}```

Как видно, мы легко составили текстовую подсказку, предварительно определили несколько пунктов и добились от LLM точного их выполнения. Скорректировали регулярное выражение для поля age с помощью {{gen ‘age’ pattern=’[0–9]+’ stop=’,’}}. Это значит, что оно принимает только цифры и заканчивается на ,. Кроме того, используя valid_dish, ограничили тип заказанной еды с помощью “{{select ‘order’ options=valid_dish}}”. Более подробная информация предоставлена по официальной ссылке на GitHub

Агент ReAct с Guidance и Wizard-Mega-13B-GPTQ

Примечание. С кодом данного раздела можно ознакомиться по ссылке

Перед запуском необходимо разместить LLM на локальном ПК. В данном случае для этой цели применяется wizard-mega-13B-GPTQ. Вы можете выбрать и другие модели. Сначала загружаем модель и позволяем Guidance ее использовать: 

model_para = 'YOUR_MODEL_DIR'
checkpoint_para = 'YOUR_MODEL_FILE'
model = load_quant(model_para, checkpoint_para, 4, 128)
model.to(DEV)
tokenizer = AutoTokenizer.from_pretrained(model_para)

llama = guidance.llms.Transformers(model=model, tokenizer=tokenizer, device=0)
guidance.llm = llama

Испытаем на простой текстовой подсказке. С помощью формата ReAct проверяем, насколько корректно работает модель.

valid_answers = ['Action', 'Final Answer']
valid_tools = ['Google Search']

prompt_template = """Ниже дана инструкция, которая описывает задачу и предоставляет входные данные для обеспечения дальнейшего контекста. Напиши ответ, который удовлетворяет запрос.

### Инструкция:
Ответь на следующие вопросы как можно лучше. В твоем распоряжении такие инструменты, как:

Поисковая система Google Search: Обертка вокруг Google Search. Пригодится при ответе на вопросы о текущих событиях. Входные данные - это вопрос для поиска соответствующей информации.

Строго используй следующий формат:

Question (вопрос): входной вопрос, на который ты должна ответить.
Thought (осмысление): всегда осмысливай предстоящее действие.
Action (действие): предпринимаемое действие, суть которого обращение к [Google Search].
Action Input (входные данные для действия): входными данными для действия является задаваемый вопрос.
Observation (результат выполнения): результат действия.
... (Thought/Action/Action Input/Observation могут повторяться N количество раз)
Thought (осмысление): теперь я знаю итоговый ответ.
Final Answer (итоговый ответ): итоговый ответ на исходный входной вопрос.

Пример:
Question: Сколько лет жене генерального директора Microsoft?
Thought: Сначала я должна узнать, кто является генеральным директором Microsoft.
Action: Google Search
Action Input: Кто генеральный директор Microsoft?
Observation: Сатья Наделла - генеральный директор Microsoft.
Thought: Теперь я должна узнать, кто является женой Сатьи Наделлы.
Action: Google Search
Action Input: Как зовут жену Сатьи Наделлы?
Observation: Жену Сатьи Наделлы зовут Анупама Наделла.
Thought: Теперь я должна узнать ее возраст.
Action: Google Search
Action Input: Сколько лет Анупаме Наделле?
Observation: Анупаме Наделле 50 лет.
Thought: Тепеь я знаю итоговый ответ.
Final Answer: Анупаме Наделле 50 лет.

### Ввод:
{{question}}

### Ответ :
Question: {{question}}
Thought: {{gen 'thought' stop='\\n'}}
Action: {{select 'tool_name' options=valid_tools}}
Action Input: {{gen 'actInput' stop='\\n'}}
Observation:{{search actInput}}
Thought: {{gen 'thought2' stop='\\n'}}
Final Answer: {{gen 'final' stop='\\n'}}"""

def searchGoogle(t):
return search.run(t)

prompt = guidance(prompt_template)
result = prompt(question='Is Eminem a football player?', search=searchGoogle, valid_answers=valid_answers, valid_tools=valid_tools)

В этом примере модель строго следует заданной инструкции, отвечая на вопрос, является ли Эминем футболистом. В итоге она делает вывод, что он не футболист. Отлично сработано! Попробуем с помощью Guidance создать агент ReAct. 

Определяем список инструментов. В данном случае он включает только один, а именно searchGoogle. В этот список можно добавить калькулятор, инструмент извлечения SQL и др. 

def searchGoogle(key_word):    
return search.run(key_word)

dict_tools = {
'Google Search': searchGoogle
}

Теперь создаем CustomAgentGuidance

class CustomAgentGuidance:
def __init__(self, guidance, tools, num_iter=3):
self.guidance = guidance
self.tools = tools
self.num_iter = num_iter

def do_tool(self, tool_name, actInput):
return self.tools[tool_name](actInput)

def __call__(self, query):
prompt_start = self.guidance(prompt_start_template)
result_start = prompt_start(question=query, valid_answers=valid_answers)

result_mid = result_start

for _ in range(self.num_iter - 1):
if result_mid['answer'] == 'Final Answer':
break
history = result_mid.__str__()
prompt_mid = self.guidance(prompt_mid_template)
result_mid = prompt_mid(history=history, do_tool=self.do_tool, valid_answers=valid_answers, valid_tools=valid_tools)

if result_mid['answer'] != 'Final Answer':
history = result_mid.__str__()
prompt_mid = self.guidance(prompt_final_template)
result_final = prompt_mid(history=history, do_tool=self.do_tool, valid_answers=['Final Answer'], valid_tools=valid_tools)
else:
history = result_mid.__str__()
prompt_mid = self.guidance(history + "{{gen 'fn' stop='\\n'}}")
result_final = prompt_mid()
return result_final['fn']

Код вполне понятен. Запускаем подсказку prompt_start_template посредством инструкции. Всегда проверяем, дает ли модель итоговый ответ “Final Answer”. При необходимости дополнительной информации выполняется поиск и предоставляются соответствующие сведения с помощью prompt_mid_template. Максимум через 3 итерации агент должен прекратить поиск и дать итоговый ответ. Зададим несколько вопросов и проверим, как он справится. Первый вопрос: “Сколько получает игрок команды Манчестер Юнайтед под номером 8?”:

custom_agent = CustomAgentGuidance(guidance, dict_tools)

list_queries = [
"How much is the salary of number 8 of Manchester United?",
"What is the population of Congo?",
"Where was the first president of South Korean born?",
"What is the population of the country that won World Cup 2022?"
]

final_answer = custom_agent(list_queries[0])
**Вывод**
### Входной вопрос:
How much is the salary of number 8 of Manchester United?
### Ответ:
Question: Какой размер зарплаты у игрока команды Манчестер Юнайтед под номером 8?
Thought: Я должна узнать, кто играет под номером 8 в команде Манчестер Юнайтед.
Action: Google Search
Action Input: Кто играет под номером 8 в команде Манчестер Юнайтед?
Observation: Знаменитую футболку с номером 8 в текущем составе команды Манчестере Юнайтед носит Бруну Фернандеш – но какое место он занимает в ряду своих предшественников?
Thought: Я должна узнать размер зарплаты Бруну Фернандеша.
Action: Google Search
Action Input: Сколь получает Бруну Фернандеш?
Observation: Бруну Фернандеш перешел в Манчестер Юнайтед в январе 2020 года из Спортинга. При этом его зарплата сразу увеличилась с $59 717 до $289 418 в неделю. По данным от 3 апреля 2023 года.
Thought: Теперь я знаю итоговый ответ.
Final Answer: Бруну Фернандеш получает $289 418 в неделю.

Теперь не нужно беспокоиться об оптимизации текстовых подсказок. Просто доверьте эту задачу инструменту Guidance, и он строго выполнит все инструкции. 

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи gArtist: A Simple Agent With Guidance and Local LLM

Предыдущая статьяОтчего «паникует» даже камнеукладчик: инцидент с удалением строк
Следующая статьяТри фактора сдерживания прогресса ИИ