3.3 🕵️‍♂️ Агенты intro

🧑‍🎓 Грокаем с агентом школьные математические задачки

Теперь, когда у вас есть инструментарий (функции, которые вы написали в предыдущем степе), то можно с помощью него начать решать математические задачи.

Что делать? Создайте своего агента-математика, использовав tools с предыдущего степа. Решите с помощью него предоставленные примеры и запишите ответ в столбец answer (только ответ, во всех задачах это числа - целые или вещественные).

Что на входе? Вам предоставляется датасет с одним столбцом task - условия самих задач

import pandas as pd
df = pd.read_csv("https://stepik.org/media/attachments/lesson/1110884/custom_math_tools.csv")

Что на выходе? csv файл, содержащий два столбца - task, answer
Замечание: Ваше решение будет зачтено, если в нём будет минимум 8 правильных ответов.

Примечание: для инициализиции агента с инструментами, принимающими несколько аргументов, воспользуйтесь параметром agent, записав в него значение AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION.

from langchain.agents import initialize_agent, AgentType
agent = initialize_agent(agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,)

Всем привет.
Я создал функции, создал агента, всё отработало хорошо, но я столкнулся со следующей проблемой, а именно: я забираю ответ по ключу output, но в этом ответе, бывает, так что содержится не только "голый" ответ, но еще ключи "action" и "action_input". Вопрос: как мне избавиться от случайного появления ключей "action" и "action_input" в output ответа? Как-то дополнить докстринг в функциях? Использовать парсер? Просто про него ничего в ноутбке не сказанно((

Я пробовал output в словарь, но это не помогло т.к. ключи "action" и "action_input" то появляються, то нет - это видно на скрине под цифрой два и три.

 

@Эдуард_Поляков, да, можно использовать аутпут парсер или добавить в цикл обработку появления этих ключей. В щаблоне реализован минимально рабочий пример, чтобы было с чего начать - рекомендуется его расширять под свои нужды

@Иван_Александров, спасибо за ответ.

Я реализовал парсер, но он работает "через раз" и как-то не предсказуемо: ключ 'output' то появляется только с 'math_answer' (задан в схеме) то с "action_input", то вообще с другими ключами "number" и "binary_number" и т.д.


Не могли бы вы посмотреть на мой ноутбук и сказать, что я делаю не так и как я могу стабильно, используя парсер забирать ответ от LLM ключу math_answer, который определил в парсере.

Шаблон промпта не совсем корректен. Если подставить в него все переменные, то инструкции неоднозначные для ЛЛМ. Попробуйте отдельно выделить "Задача:" и указать в каком формате нужен ответ.

@Иван_Александров, спасибо за ответ, я сдал это задание.

Но всё же мне не даёт покоя, что мы не можем нормально и стабильно получать ответ согласно установленной в парсере схемой, а именно получать значение по ключу math_answer без дополнительной очистке.

не могли бы мы подсказать или поделитсья эталооным решением? я просто перерыл документацию, ютуб, но так и не смог допиться желаемого((

также я нашел похожую реализация парсер + агент (за искл. что агент не кастомный), но на строчке output=output_parser.parse(response) я получил исключение((
мой ноутбук

@Эдуард_Поляков, агент, как и все остальные элементы LangChain содержит внутри себя промпт. А вы внего ещё один промпт подаете примерно похожий с похожими инструкциями. Я так понимаю,что вы его использовали для того чтобы атутпут парсер привязать. Ваш Аутпут парсер можно передать в функцию initialize_agent, как  параметр  (output_parser = output_parser,) и он будет подставляться в агентский промпт. Попробуйте так.

Вообще, конечно, странная реализация метода agent.invoke - почему не сделали схему ответа унифицированной? 

Возникает ошибка при решении этого вопроса: "Переведи числа из двоичной системы счисления 1101011010 и 101011 и посчитай их сумму."

Thought: I need to convert the binary numbers to decimal, add them together, and then provide the sum as a float number. 
Action:
```
{
  "action": "add_numbers",
  "action_input": {
    "num1": {
      "action": "convert_binary_to_decimal",
      "action_input": {
        "binary_number": "1101011010"
      }
    },
    "num2": {
      "action": "convert_binary_to_decimal",
      "action_input": {
        "binary_number": "101011"
      }
    }
  }

В итоге на вход функции сложения двух чисел попадают словари:
num1 = {'action': 'convert_binary_to_decimal', 'action_input': {'binary_number': '1101011010'}}
num2 = {'action': 'convert_binary_to_decimal', 'action_input': {'binary_number': '101011'}}

Вместо переведённых в десятичные числа. Подскажите пожалуйста, в чём может быть ошибка?

@Andrey_Galitsin, а вы учли примечание в самом низу?

Примечание: для инициализиции агента с инструментами, принимающими несколько аргументов, воспользуйтесь параметром agent, записав в него значение AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION.

@Иван_Александров, да, вот так инициализирую агента:

self.agent = initialize_agent(
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    tools=tools,
    llm=self.llm,
    verbose=True,
    handle_parsing_errors=True,
)

@Andrey_Galitsin, попробуйте докстриги для тулов по английский переписать

Помогите понять что делаю не так:

from langchain.prompts import ChatPromptTemplate
import pandas as pd
from tqdm import tqdm
from langchain.agents import initialize_agent, AgentType
from langchain.agents import tool
from math import pi
import math
from langchain_community.chat_models import GigaChat
import warnings
from langchain.output_parsers import ResponseSchema, StructuredOutputParser

warnings.filterwarnings('ignore')

giga_token = ***

# Инициализируем языковую модель
giga = GigaChat(credentials=giga_token, verify_ssl_certs=False)
giga.temperature = 0.1

# https://stepik.org/media/attachments/lesson/1110884/custom_math_tools.csv
df = pd.read_csv("custom_math_tools.csv")
df.head()


@tool
def compute_arc_length(radius, angle_in_degrees):
"""Вычисляет длину дуги по радиусу и углу в градусах"""
return (angle_in_degrees / 360) * 2 * math.pi * radius


@tool
def compute_triangle_area(a, b, c):
"""Вычисляет площадь треугольника по длинам его сторон"""
s = (a + b + c) / 2
return math.sqrt(s * (s - a) * (s - b) * (s - c))


@tool
def add_numbers(num1, num2):
"""Складывает два числа."""
return num1 + num2


@tool
def convert_meters_to_cm(num):
"""
Переводит метры в сантиметры.
"""
return num * 100


@tool
def convert_cubic_cm_to_liters(num):
"""
Переводит кубические сантиметры в литры.
"""
return num / 1000


@tool
def compute_rectangle_perimeter(a, b):
"""
Вычисляет периметр прямоугольника.
"""
return 2 * (a + b)


@tool
def compute_circle_area(radius):
"""
Вычисляет площадь круга.
"""
return math.pi * radius ** 2


@tool
def compute_cylinder_volume(radius, height):
"""
Вычисляет объем цилиндра.
"""
return math.pi * radius ** 2 * height


@tool
def compute_cube_volume(a):
"""
Вычисляет объем куба.
"""
return a ** 3


@tool
def convert_binary_to_decimal(binary_number: str):
"""
Переводит число из двоичной системы счисления в десятичную систему счисления.
"""
return int(binary_number, 2)


@tool
def convert_decimal_to_binary(decimal_number):
"""
Переводит число из десятичной системы счисления в двоичную систему счисления.
"""
return bin(decimal_number)[2:]


@tool
def get_count_ones(number: str):
"""
Находит количество единиц в двоичном представлении числа.
"""
count = 0
for bit in number:
if bit == '1':
count += 1
return count


# Поместите все написанные функции в tools
tools = [compute_arc_length, compute_triangle_area, add_numbers, convert_meters_to_cm, convert_cubic_cm_to_liters,
compute_rectangle_perimeter, compute_circle_area, compute_cylinder_volume, compute_cube_volume,
convert_binary_to_decimal, convert_decimal_to_binary, get_count_ones]

# Определим схемы ответа
math_answer_schema = ResponseSchema(name="math_answer",
description="""Ответ должен быть представлен в виде целого числа, если итоговый
результат является точным целым числом, или в виде вещественного числа
с точностью до двух знаков после запятой, если результат содержит дробную
часть. Не допускаеться в ответе содержания слов""")


response_schemas = [math_answer_schema]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas) # Создаём парсер и подаём в него список со схемами
format_instructions = output_parser.get_format_instructions() # Получаем инструкции по форматированию ответа

text = "Найди сумму 3 и 5"

template = """Тебе нужно дать короткий ответ на этот задание ```{text}```,
используя эти инструменты.

math_answer:
Ответ на математический вопрос это число - целое или вещественное, с учетом указанных выше условий.

Окончательный ответ должен быть в таком виде:
{format_instructions}
"""
prompt = ChatPromptTemplate.from_template(template=template)

messages = prompt.format_messages(text=text,
format_instructions=format_instructions)

agent = initialize_agent(
agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
tools=tools,
template=template,
llm=giga,
output_parser=output_parser,
handle_parsing_errors=True
)

response = agent(messages)

print(response["output"])

 

Выдаёт:

C:\Users\K\PycharmProjects\LLM_Course\venv\Scripts\python.exe C:\Users\K\PycharmProjects\LLM_Course\3_3\test.py 
Окончательный ответ человеку

Process finished with exit code 0
 

Агент под капотом уже содержит промпт, а вы ему ещё один даёте. Попробуйте просто инициализировать агента, передать ему инструменты и просто передавать в него условие задачи.

@Иван_Александров, Почему-то не получается добиться от гигачата использования нескольких инструментов.

1) В задаче "Периметр прямоугольника со сторонами 1.5 м и 2 м. Отвей дай в сантиметрах." - он просто юзает тул compute_rectangle_perimeter и отдаёт результат в метрах, хотя должен бы ещё в см перевести.

2) Если я уточняю в задаче что делать надо - "Периметр прямоугольника со сторонами 1.5 м и 2 м. Ответ переведи в сантиметры с помощью инструмента." - то скрипт останавливается на второй мысли:

Код:

# Поместите все написанные функции в tools
tools = [compute_arc_length, compute_triangle_area, add_numbers, convert_meters_to_cm, convert_cubic_cm_to_liters,
compute_rectangle_perimeter, compute_circle_area, compute_cylinder_volume, compute_cube_volume,
convert_binary_to_decimal, convert_decimal_to_binary, get_count_ones]

math_answer_schema = ResponseSchema(name="math_answer",
description="""Ответ должен быть представлен в виде целого числа, если итоговый
результат является точным целым числом, или в виде вещественного числа
с точностью до двух знаков после запятой, если результат содержит дробную
часть. Не допускаеться в ответе содержания слов""")

response_schemas = [math_answer_schema]
output_parser = StructuredOutputParser.from_response_schemas(
response_schemas) # Создаём парсер и подаём в него список со схемами
format_instructions = output_parser.get_format_instructions() # Получаем инструкции по форматированию ответа

template = """Тебе нужно дать короткий ответ на это задание,
используя доступные инструменты.

task: {text}

{format_instructions}
"""
prompt = ChatPromptTemplate.from_template(template=template)
print(f"prompt: {prompt}")


agent = initialize_agent(
agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
tools=tools,
llm=giga,
template=template,
output_parser=output_parser,
verbose=True,
handle_parsing_errors=True,
max_iterations=5
)


text = "Периметр прямоугольника со сторонами 1.5 м и 2 м. Ответ переведи в сантиметры с помощью инструмента."

messages = prompt.format_messages(text=text, format_instructions=format_instructions)
response = agent(messages)
print(response["output"])

 

Ответ

C:\Users\K\PycharmProjects\LLM_Course\venv\Scripts\python.exe C:\Users\K\PycharmProjects\LLM_Course\3_3\agents_and_chains.py 
prompt: input_variables=['format_instructions', 'text'] messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['format_instructions', 'text'], template='Тебе нужно дать короткий ответ на это задание,\nиспользуя доступные инструменты.\n\ntask: {text}\n\n{format_instructions}\n'))]


> Entering new AgentExecutor chain...
Thought: Мне нужно вычислить периметр прямоугольника со сторонами 1.5 м и 2 м.
Action:
```
{
  "action": "compute_rectangle_perimeter",
  "action_input": {
    "a": 1.5,
    "b": 2
  }
}
```
Observation: Периметр прямоугольника равен 7 метрам.
Observation: 7.0
Thought:Теперь я должен преобразовать этот результат в сантиметры.

> Finished chain.
Теперь я должен преобразовать этот результат в сантиметры.

Process finished with exit code 0
 

Все функции прошли проверку. Но при решении четвертой задачи постоянно получаю следующую ошибку. В какую сторону копать для решения?  При этом иногда выпадает результат как сложение бинарых чисел, а иногда то, что нужно. Как получать стабильные ответы?


@Эдуард_Поляков, спасибо! Протестирую ) 

@Руслан_Трифонов, Возникает та же самая ошибка. Вместо чисел на вход приходят словари. Подскажите, как можно решить эту проблему?

Если кто-то как и я плохо знаком с Python, имейте в виду, что в указанном Colab зарыта ловушка) 

assert add_numbers(-3, -5) == -8

и прочие у вас не запустятся, если вы использовали @tool

@Павел_Сотников, Как вариант, для проверки написанных функций, добавить функцию декоратор:

def tool(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

Учу считать ChatGPT  :-) 
- Сколько букв в слове зачёт?
- Слово "зачёт" состоит из 6 букв.
- Из каких?
- Слово "зачёт" на русском языке состоит из следующих букв: ... (дальше перечисляет 5 букв)
- И где шестая?
- Прошу прощения за путаницу. Действительно, слово "зачёт" состоит только из пяти букв

Будьте аккуратны, токены на этом задании улетают очень быстро.

Мама, давай напишем агента для решения математических задач

Мама: но у нас дома для решения математических задач есть доступ к GPT-3.5

GPT-3.5:

Сравните ответы после решения)

Не использовал initialize_agent, т.к. langchain выдал что функция устарела, пришлось танцевать с бубном.
Первначальная версия выдала только 6 правильных ответов, например не переводила см3 в литры. Ниже версия с чуть доработанным промтом, уже не проверял на всей выборке вопросов.
from utils import ChatOpenAI
import math
from math import pi
from langchain.prompts import PromptTemplate
from langchain.output_parsers import ResponseSchema, StructuredOutputParser
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.tools import tool
import pandas as pd
from tqdm import tqdm
df = pd.read_csv("3_3_8_custom_math_tools.csv")
course_api_key= "course_api_key"
llm = ChatOpenAI(temperature=0.0, course_api_key=course_api_key)

# реализация функций с декораторами пропущена

tools = [add_numbers, convert_meters_to_cm, convert_cubic_cm_to_liters, 
         compute_rectangle_perimeter, compute_circle_area, compute_cylinder_volume,
         compute_cube_volume, convert_binary_to_decimal, convert_decimal_to_binary,
         get_count_ones, compute_arc_length, compute_triangle_area]
response_schemas = [
    ResponseSchema(
        name="answer",
        description="Ответ на математический вопрос в виде целого или вещественного числа",
        type="float"
    ),
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)

 

prompt = PromptTemplate(
    input_variables=["task"],
    template="Верни ответ на математическую задачу {task}.\n\nУчитывай в каких единицах инструменты возвращают ответы, а а каких единицах нужен ответ, конвертируй при необходимости\n\nФинальный ответ в формате JSON c ключом answer: <ответ число>.\n\n{agent_scratchpad}",
)

 

agent = create_tool_calling_agent(
    llm=llm,
    tools=tools,
    prompt=prompt,
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)

 

answers = []
for task in tqdm(df['task']):
    response = agent_executor.invoke({"task": task})
    output_dict = output_parser.parse(response.get('output'))
    answers.append(output_dict['answer'])
result_df = pd.DataFrame({'task': df['task'], 'answer': answers})
result_df.to_csv("3.3.8_solution.csv", index=False)

Вы побили порог в 8: ваша точность 8 из 10.

@Vladimir_Rudenko, спасибо тебе, добрый человек! у меня было 7 из 10 все время ( пришлось ответы руками вбить чтобы открыть эту ветку и прочитать правильное решение

# Реализуйте функции

@tool

def add_numbers(num1, num2):

    """Складывает два числа"""

    return num1 + num2

 

@tool

def convert_meters_to_cm(num):

    """Переводит метры в сантиметры"""

    return num * 100

 

@tool

def convert_cubic_cm_to_liters(num):

    """Переводит кубические сантиметры в литры."""

    return num / 1000

 

@tool

def compute_rectangle_perimeter(a, b):

    """Вычисляет периметр прямоугольника"""

    return (a + b) * 2

 

@tool

def compute_circle_area(radius):

    """Вычисляет площадь круга по заданному радиусу."""

    if radius < 0:

        raise ValueError("Радиус не может быть отрицательным")

    return math.pi * radius ** 2

 

@tool

def compute_cylinder_volume(radius, height):

    """Вычисляет объем цилиндра по заданному радиусу основания и высоте."""

    if radius < 0 or height < 0:

        raise ValueError("Радиус и высота должны быть положительными числами")

    base_area = math.pi * radius ** 2

    volume = base_area * height

    return volume

 

@tool

def compute_cube_volume(a):

    """Вычисляет объем куба по заданной длине ребра."""

    if a < 0:

        raise ValueError("Длина ребра куба должна быть положительным числом")

    volume = a ** 3

    return volume

 

@tool

def convert_binary_to_decimal(binary_number: str) -> int:

    """Переводит число из двоичной системы счисления в десятичную систему счисления."""

    return int(binary_number, 2)

 

@tool

def convert_decimal_to_binary(decimal_number: int) -> str:

    """Переводит число из десятичной системы счисления в двоичную систему счисления."""

    try:

        # Если входное значение - строка, пробуем преобразовать в число

       if isinstance(decimal_number, str):

           decimal_number = float(decimal_number)

       # Если число с плавающей точкой, разделяем целую и дробную части

       if isinstance(decimal_number, float):

           integer_part = int(decimal_number)

       fractional_part = decimal_number - integer_part

       # Конвертируем целую часть

       integer_binary = bin(integer_part)[2:]

       # Конвертируем дробную часть (если есть)

       if fractional_part > 0:

           fractional_binary = ''

           for _ in range(10): # Ограничиваем до 10 знаков после запятой

               fractional_part *= 2

               bit = int(fractional_part)

               fractional_binary += str(bit)

               fractional_part -= bit

               return f"{integer_binary}.{fractional_binary}"

       else:

           return integer_binary

    else:

      # Если целое число, просто конвертируем

       return bin(int(decimal_number))[2:]

except ValueError:

     return "Ошибка: Невозможно преобразовать входное значение в число"

 

@tool

def get_count_ones(number: str) -> int:

    """Находит количество единиц в двоичном представлении числа."""

    return number.count('1')

 

@tool

def compute_triangle_area(a: float, b: float, c: float) -> float:

    """Вычисляет площадь треугольника по длинам его сторон, используя формулу Герона."""

    # Проверка на существование треугольника

    if a <= 0 or b <= 0 or c <= 0:

        raise ValueError("Длины сторон должны быть положительными числами")

    if a + b <= c or a + c <= b or b + c <= a:

         raise ValueError("Сумма длин любых двух сторон должна быть больше длины третьей стороны")

 

# Поместите все написанные функции в tools

tools = [compute_arc_length, compute_triangle_area, add_numbers, convert_meters_to_cm, convert_cubic_cm_to_liters, compute_rectangle_perimeter, compute_circle_area, compute_cylinder_volume, compute_cube_volume, convert_binary_to_decimal, convert_decimal_to_binary, get_count_ones]

 

...

 

df['answer'] = ''

 

for i, task in enumerate(tqdm(df['task'])):

    response = agent.run(f'Дана задача: {task}. Выведи только число и ничего другого!')

    # numeric_response = int(response.strip())

    df.at[i, 'answer'] = response

Вы побили порог в 8: ваша точность 8 из 10.

все решение указывать не буду, оно +- у всех одно, покажу лишь цикл

df['answer'] = None

for idx, task in enumerate(tqdm(df['task'])):

ans = agent.run(f'Реши задачу: {task} В ответе укажи только число и ничего больше')

f_ans = llm.invoke(f"Дан ответ модели: {ans}. В ответе отдай число, которое содержится по ключу 'action_input' или иному, или после символа /n но по смыслу связанному с ответом, если дано просто число отдай его без изменений. Запомни, только число и ничего больше").content

df.at[idx, 'answer'] = f_ans

Решение на 8 из 10 

Раз 5-6 упал на 9 вопросе вообще без каких-либо объективных причин, но в итоге все таки смог отработать. Магия какая-то

Вы побили порог в 8: ваша точность 8 из 10.

Меня тоже раздражает, что используются деприкейтед методы, пытался на локальной оламе (лама3 инстракт) реализовать, как не мучился - не получилось. Оказалось, не вывозила сама лама, локальная моделька не может работать с агентами сложности свыше 1 тул = 1 агент. Решил с мистралем из хагинг фейс:
 

import os
from tqdm import tqdm
from math import pi, sqrt
import pandas as pd
from langchain.agents import tool, AgentType
from langchain.agents import initialize_agent
from langchain_community.llms import HuggingFaceEndpoint
os.environ['HUGGINGFACEHUB_API_TOKEN'] = 'ххх'
llm = HuggingFaceEndpoint(repo_id="mistralai/Mixtral-8x7B-Instruct-v0.1")  #самая норм пока что
@tool
def compute_arc_length(radius, angle_in_degrees):
    """Calculates the arc length given radius and angle in degrees"""
    return (angle_in_degrees / 360) * 2 * pi * radius
@tool
def compute_triangle_area(a, b, c):
    """Calculates the area of a triangle given the lengths of its sides"""
    if a + b <= c or a + c <= b or b + c <= a:
        return "Стороны не могут образовать треугольник"
    s = (float(a) + b + c) / 2
    return sqrt(s * (s - float(a)) * (s - float(b)) * (s - float(c)))
@tool
def add_numbers(num1, num2):
    """Adds two numbers"""
    return num1 + num2
@tool
def convert_meters_to_cm(num):
    """Converts meters to centimeters"""
    return num * 100
@tool
def convert_cubic_cm_to_liters(num):
    """Converts cubic centimeters to liters"""
    return int(num) / 1000
@tool
def compute_rectangle_perimeter(a, b):
    """Calculates the perimeter of a rectangle"""
    return 2 * (a + b)
@tool
def compute_circle_area(radius):
    """Calculates the area of a circle"""
    print(radius)
    return pi * radius ** 2
@tool
def compute_cylinder_volume(radius, height):
    """Calculates the volume of a cylinder"""
    return pi * radius ** 2 * height
@tool
def compute_cube_volume(a):
    """Calculates the volume of a cube"""
    return a ** 3
@tool
def convert_binary_to_decimal(binary_number: str):
    """Converts a binary number to a decimal number"""
    return int(binary_number, 2)
@tool
def convert_decimal_to_binary(decimal_number):
    """Converts a decimal number to a binary number"""
    print(f"decimal number is: {decimal_number}")
    return bin(int(decimal_number))[2:]
@tool
def get_count_ones(number: str):
    """Finds the number of ones in a binary representation of a number"""
    return number.count('1')
tools = [compute_arc_length, compute_triangle_area, convert_cubic_cm_to_liters, add_numbers, convert_meters_to_cm,
         compute_rectangle_perimeter, compute_circle_area, get_count_ones, convert_decimal_to_binary, compute_cube_volume,
         compute_cylinder_volume, convert_binary_to_decimal]
agent = initialize_agent(
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    tools=tools,
    llm=llm,
    verbose=True,
handle_parsing_errors=True
)
df = pd.read_csv("https://stepik.org/media/attachments/lesson/1110884/custom_math_tools.csv")
answers = []
for task in tqdm(df['task']):
    result = agent(task)
    print(task + " " + str(result))
    answers.append(result['output'])
# Добавляем ответы в DataFrame и сохраняем его в CSV файл
df['answer'] = answers
df.to_csv('math_solutions.csv', index=False)

Вы побили порог в 8: ваша точность 9 из 10.

как делать не надо (решение на 7 из 10):

from langchain.output_parsers import ResponseSchema
from langchain.output_parsers import StructuredOutputParser
from langchain.prompts import ChatPromptTemplate
out_schema = ResponseSchema(name="out",
                             description="Выдай результат вычислений  одним числом , например: 700, 7.2, 0")

response_schemas = [out_schema]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
# получаем инструкции по форматированию ответа
format_instructions = output_parser.get_format_instructions()

template = """
Из текста оставь только последнее число
текст: {text} 
{format_instructions}
"""
prompt = ChatPromptTemplate.from_template(template)

agent = initialize_agent(
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION
    , tools = tools
    , llm = llm
    , handle_parsing_errors=True
 #   , output_parser = output_parser
)
answer = []
for task in tqdm(df['task']):
    try:
        messages = prompt.format_messages(text=agent.run(f'{task}'), 
                                format_instructions=format_instructions)
        response = llm.invoke(messages)
        output_dict = output_parser.parse(response.content)
        print(output_dict['out'], "____", task, "____", messages)
        if abs(int(output_dict['out']) - output_dict['out']) < 0.000001:
            output_dict['out'] = int(output_dict['out'])
        answer.append(output_dict['out'])
    except:
        print(task)
        answer.append(0)

Вы побили порог в 8: ваша точность 8 из 10.

Так и не понял, как консистентного ответа от агента. Иногда он выдает словарь с output: 700, а иногда с инпутом для следующей цепочки

Вы побили порог в 8: ваша точность 8 из 10.

Мёрдж двух запусков GPT 3.5
template = """{text}

{format_instructions}
"""
math_answer_schema = ResponseSchema(name="math_answer",
 description="""Если ответ нецелый то округлить до 2 знаков float. Если целый то в вернуть в формате int. Не допускаеться в ответе содержания слов""")

и с 

description="""Ответ должен быть представлен в виде int, если возможно иначе float. Не допускаеться в ответе содержания слов""")

Парсинг ответа агента

def agent_str_dict_out(output_json):
    if type(output_json) is str:
        cleaned_response_json = output_json.replace('\n', '').strip()
        cleaned_response_json = json.loads(output_json)
    else:
        cleaned_response_json = output_json
    #print({cleaned_response_json})
    if 'action_input' in cleaned_response_json:
        act_in = cleaned_response_json['action_input']
        if type(act_in) is str:
            math_dict = json.loads(act_in)
        else: 
            math_dict = act_in
    else:
        math_dict = cleaned_response_json
    return math_dict

Вы побили порог в 8: ваша точность 8 из 10.

Решил с помощью Gemini от Google. Работает стабильнее, чем ChatGPT, почему-то...

Вы побили порог в 8: ваша точность 8 из 10.

не буду все указывать, основные моменты:
- дать описание входных параметров в функциях с декоратором tool;
- давать итоговый ответ агента в модель с промптом по типу "тебе дан развернутый ответ на задачу - верни число, являющееся непосредственно ответом"

Вы побили порог в 8: ваша точность 8 из 10.