Calling custom functions in the Gigachat API

Recently, a section dedicated to working with functions, similar to how it is implemented in ChatGPT. The idea is that the model is given information about the functions available to it, and depending on the user's request, the model can access these functions and add their result to the program to generate a response.

This functionality is not yet available in gigachain and is available via the API only for the model "GigaChat-Pro-preview"and I want to share my first experience of using it.

On the documentation page you will find a lot of json schemas that you need to add to requests to call functions, but here we will look at how to implement function calls in your code.

In my example, in addition to weather information, the assistant will receive prices for the requested products from a Google table and transmit them to the user.

I took the code from this as a basis videoI recommend reading it to start learning how to work with the GigaChat API.

Let's start with authorization.

from google.colab import userdata
# auth token API GigaChat
giga_key = userdata.get('GIGA_CHAT_KEY')
# id гугл-таблицы из которой будем получать данные
price_id = userdata.get('PRICE_ID')

Using auth token we get a temporary token for accessing the API, the token is valid for 30 minutes:

import requests
import uuid

def get_token(auth_token, scope="GIGACHAT_API_CORP"):
    """
      Выполняет POST-запрос к эндпоинту, который выдает токен.

      Параметры:
      - auth_token (str): токен авторизации, необходимый для запроса.
      - область (str): область действия запроса API. По умолчанию — «GIGACHAT_API_PERS».

      Возвращает:
      - ответ API, где токен и срок его "годности".
      """
    # Создадим идентификатор UUID (36 знаков)
    rq_uid = str(uuid.uuid4())

    # API URL
    url = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth"

    # Заголовки
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'application/json',
        'RqUID': rq_uid,
        'Authorization': f'Basic {auth_token}'
    }

    # Тело запроса
    payload = {
        'scope': scope
    }

    try:
        # Делаем POST запрос с отключенной SSL верификацией
        # (можно скачать сертификаты Минцифры, тогда отключать проверку не надо)
        response = requests.post(url, headers=headers, data=payload, verify=False)
        return response
    except requests.RequestException as e:
        print(f"Ошибка: {str(e)}")
        return -1

If you use GigaChat as an individual, then indicate the argument scope=«GIGACHAT_API_PERS»

response = get_token(giga_key)
if response != 1:
  print(response.text)
  giga_token = response.json()['access_token']
{"access_token":"token","expires_at":1712650905952}

The request will return the token and its expiration date in the format of a timestamp in milliseconds; for clarity, let's convert it to a date and time format:

from datetime import datetime

# Временная метка в миллисекундах
timestamp_ms = response.json()["expires_at"]

# Преобразование в секунды
timestamp_s = timestamp_ms / 1000

# Преобразование в объект datetime
date_time = datetime.utcfromtimestamp(timestamp_s)

print(date_time.strftime('%Y-%m-%d %H:%M:%S'))
2024-04-09 08:21:45

As I wrote earlier, function calls are only available for the model "GigaChat-Pro-preview"you need to check that this model is available on your tariff:

import requests

url = "https://gigachat.devices.sberbank.ru/api/v1/models"

payload={}
headers = {
  'Accept': 'application/json',
  'Authorization': f'Bearer {giga_token}'
}

response = requests.request("GET", url, headers=headers, data=payload, verify=False)

print(response.text)
{"object":"list","data":[{"id":"GigaChat","object":"model","owned_by":"salutedevices"},{"id":"GigaChat-Plus","object":"model","owned_by":"salutedevices"},{"id":"GigaChat-Pro","object":"model","owned_by":"salutedevices"},{"id":"GigaChat-Pro-preview","object":"model","owned_by":"salutedevices"}]}

If "GigaChat-Pro-preview" appears in your list, you can move on to the next step.

Let's define custom functions:

def weather_forecast(location):
  """
  Получение погоду в Москве
  """
  return "в Москве +13"

The simplest function that will produce a fixed string for any call, but it has a location argument as its input; now I’ll explain why it’s necessary.

Functions are passed to the model in the form of a json schema:


        {
            "name": "weather_forecast",
            "description": "Возвращает температуру на заданный период",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "Местоположение, например, название города"
                    },
                    "format": {
                        "type": "string",
                        "enum": [
                            "celsius",
                            "fahrenheit"
                        ],
                        "description": "Единицы изменерия температуры"
                    },
                    "num_days": {
                        "type": "integer",
                        "description": "Период, для которого нужно вернуть прогноз"
                    }
                },
                "required": [
                    "location",
                    "num_days"
                ]
            }
        }

This diagram has a field "properties"which specifies the parameters that the model should generate for this function, and "required", which specifies a list of required arguments. In the case when the function does not require input arguments, the values ​​of these fields should, in theory, indicate an empty array and an empty list, but sometimes in such cases GigaChat begins to independently generate arguments that the function will not be ready to accept, for example, for the weather function I got an argument 'city':

{'choices': [{'message': {'content': '', 'role': 'assistant', 'function_call': {'name': 'weather_forecast', 'arguments': {'city': 'Москва'}}}, 'index': 0, 'finish_reason': 'function_call'}], 'created': 1712654212, 'model': 'GigaChat-Pro-preview:2.2.25.3', 'object': 'chat.completion', 'usage': {'prompt_tokens': 389, 'completion_tokens': 15, 'total_tokens': 404, 'system_tokens': 25}}

To avoid such situations, I use dummy arguments that can be passed without fear that it will break the function, so here I pass the location argument despite the fact that the output is a fixed string.

Let's define a couple more functions:

def get_prices(products):
  if isinstance(products, str):
    products = [products]
  return json.dumps(price_list[price_list['вид продукции'].isin(products)][['вид продукции', 'цена за 1 кг, рубли РФ']].to_dict(orient="records"), ensure_ascii=False)

This function will generate a list of eligible products for which prices from the price list will be obtained.

def get_full_pricelist(products):
    """
    Получение информации по всей продукции
    """
    return json.dumps(price_list[['вид продукции', 'цена за 1 кг, рубли РФ']].to_dict(orient="records"), ensure_ascii=False)

When requested, the function displays all available prices from the price list; a dummy parameter is also used here 'products'. Let's create a json function diagram for the model:

giga_functions = [
    {
        "name": "weather_forecast",
        "description": "Возвращает температуру в Москве",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "Местоположение, например, название города"
                    }
            },
            "required": ["location"]
            },
        "return_parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "Местоположение, например, название города"
                    },
                "temperature": {
                    "type": "integer",
                    "description": "Температура для заданного местоположения"
                    },
                "forecast": {
                    "type": "array",
                    "items": {
                        "type": "string"
                        },
                    "description": "Описание погодных условий"
                    },
                "error": {
                    "type": "string",
                    "description": "Возвращается при возникновении ошибки. Содержит описание ошибки"
                    }
                }
            }
        },
        {
            "name": "get_full_pricelist",
            "description": "Возвращает цены на всю имеющуюуся продукцию",
            "parameters": {
                "type": "object",
                "properties": {
                    "products": {
                        "type": "string",
                        "description": f"список наиболее подходящих продуктов из списка {price_list['вид продукции'].to_list()}"
                        }
                    },
                "required": ["products"]
                },
            "return_parameters": {
                "type": "object",
                "properties": {
                    "product": {
                        "type": "string",
                        "description": "Наименование продукции"
                        },
                    "price": {
                        "type": "integer",
                        "description": "Цена продукции в рублях РФ"
                        },
                    "error": {
                        "type": "string",
                        "description": "Возвращается при возникновении ошибки. Содержит описание ошибки"
                        }
                    }
                }
            },
             {
                 "name": "get_prices",
                 "description": "Возвращает цену на запрашиваемый продукт",
                 "parameters": {
                     "type": "object",
                     "properties": {
                         "products": {
                             "type": "string",
                             "description": f"список наиболее подходящих продуктов из списка {price_list['вид продукции'].to_list()}"
                             }
                         },
                     "required": [
                         "products"
                         ]
                     },
                 "few_shot_examples": [
                     {
                         "request": "сколько стоит лопатка?",
                         "params": {
                             "products": ["Свиная лопатка"]
                             }
                         }
                     ],
                 "return_parameters": {
                     "type": "object",
                     "properties": {
                         "products": {
                             "type": "string",
                             "description": "Наименование продукции"
                             },
                         "price": {
                             "type": "integer",
                             "description": "Цена для данного вида продукции"
                             },
                         "error": {
                             "type": "string",
                             "description": "Возвращается при возникновении ошибки. Содержит описание ошибки"
                             }
                         }
                     }
                 }
    ]

Here is a description of the function argument ''get_prices is an f-string with a list of products that are in the price list, so that the model generates the correct arguments to call the function, this almost always works, but of course there are exceptions.

And finally, let's create a function that will accept the user's question, call a function if necessary, and generate an answer.

import requests
import json

def get_chat_completion(auth_token, user_message, conversation_history=None):
    """
    Отправляет POST-запрос к API чата для получения ответа от модели GigaChat в рамках диалога.

    Параметры:
    - auth_token (str): Токен для авторизации в API.
    - user_message (str): Сообщение от пользователя, для которого нужно получить ответ.
    - conversation_history (list): История диалога в виде списка сообщений (опционально).

    Возвращает:
    - response (requests.Response): Ответ от API.
    - conversation_history (list): Обновленная история диалога.
    """
    # URL API, к которому мы обращаемся
    url = "https://gigachat.devices.sberbank.ru/api/v1/chat/completions"

    # Если история диалога не предоставлена, добавляем в неё системный промт
    if conversation_history is None or conversation_history == []:
        conversation_history = [
            {
                "role": "system",
                "content": "ты менеджер по продажам, твоя задача сообщать цены из прайс-листа и погоду"
                }
            ]

    # Добавляем сообщение пользователя в историю диалога
    conversation_history.append({
        "role": "user",
        "content": user_message
    })
    # json схемы доступных функций
    giga_functions = [
    {
        "name": "weather_forecast",
        "description": "Возвращает температуру в Москве",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "Местоположение, например, название города"
                    }
            },
            "required": ["location"]
            },
        "return_parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "Местоположение, например, название города"
                    },
                "temperature": {
                    "type": "integer",
                    "description": "Температура для заданного местоположения"
                    },
                "forecast": {
                    "type": "array",
                    "items": {
                        "type": "string"
                        },
                    "description": "Описание погодных условий"
                    },
                "error": {
                    "type": "string",
                    "description": "Возвращается при возникновении ошибки. Содержит описание ошибки"
                    }
                }
            }
        },
        {
            "name": "get_full_pricelist",
            "description": "Возвращает цены на всю имеющуюуся продукцию",
            "parameters": {
                "type": "object",
                "properties": {
                    "products": {
                        "type": "string",
                        "description": f"список наиболее подходящих продуктов из списка {price_list['вид продукции'].to_list()}"
                        }
                    },
                "required": ["products"]
                },
            "return_parameters": {
                "type": "object",
                "properties": {
                    "product": {
                        "type": "string",
                        "description": "Наименование продукции"
                        },
                    "price": {
                        "type": "integer",
                        "description": "Цена продукции в рублях РФ"
                        },
                    "error": {
                        "type": "string",
                        "description": "Возвращается при возникновении ошибки. Содержит описание ошибки"
                        }
                    }
                }
            },
             {
                 "name": "get_prices",
                 "description": "Возвращает цену на запрашиваемый продукт",
                 "parameters": {
                     "type": "object",
                     "properties": {
                         "products": {
                             "type": "string",
                             "description": f"список наиболее подходящих продуктов из списка {price_list['вид продукции'].to_list()}"
                             }
                         },
                     "required": [
                         "products"
                         ]
                     },
                 "few_shot_examples": [
                     {
                         "request": "сколько стоит лопатка?",
                         "params": {
                             "products": ["Свиная лопатка"]
                             }
                         }
                     ],
                 "return_parameters": {
                     "type": "object",
                     "properties": {
                         "products": {
                             "type": "string",
                             "description": "Наименование продукции"
                             },
                         "price": {
                             "type": "integer",
                             "description": "Цена для данного вида продукции"
                             },
                         "error": {
                             "type": "string",
                             "description": "Возвращается при возникновении ошибки. Содержит описание ошибки"
                             }
                         }
                     }
                 }
    ]
    # Подготовка данных запроса в формате JSON
    payload = json.dumps({
        "model": "GigaChat-Pro-preview",
        "messages": conversation_history,
        "function_call": "auto",
        "functions": giga_functions,
        "temperature": 0.5,
        "top_p": 0.1,
        "n": 1,
        "stream": False,
        "max_tokens": 32000,
        "repetition_penalty": 1,
        "update_interval": 0
    })
    # Заголовки запроса
    headers = {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': f'Bearer {auth_token}'
    }
    # словарь с функциями для обработки запроса
    available_functions = {
        "weather_forecast": weather_forecast,
        "get_full_pricelist": get_full_pricelist,
        "get_prices": get_prices
        }
    # Выполнение POST-запроса и возвращение ответа
    try:
        response = requests.post(url, headers=headers, data=payload, verify=False)
        response_data = response.json()
        # проверяем ответ модели на наличие обращений к функциям
        try:
          # json с информацией о необходимой функции
          func_calls = response_data['choices'][0]['message']['function_call']
          # имя вызываемой функции
          func_name = func_calls['name']
          # аргументы вызываемой функции
          func_args = func_calls['arguments']
          # достаём нужную функцию из словаря 
          function_to_call = available_functions[func_name]
          
          # добавляем в историю сообщений ответ модели с вызовом функции, БЕЗ ЭТОГО МОДЕЛЬ НЕ ОТВЕТИТ
          conversation_history.append(response_data['choices'][0]['message'])
          # добавляем в историю сообщений результаты функции
          conversation_history.append(
              {
                  "role": "function",
                  "content": function_to_call(**func_args),
                  "name": func_name
                  }
              )
          # обновляем данные
          payload = json.dumps({
              "model": "GigaChat-Pro-preview",
              "messages": conversation_history,
              "function_call": "auto",
              "functions": giga_functions,
              "temperature": 0.5,
              "top_p": 0.1,
              "n": 1,
              "stream": False,
              "max_tokens": 32000,
              "repetition_penalty": 0.5,
              "update_interval": 0
              })
          # повторяем зарос  
          response = requests.post(url, headers=headers, data=payload, verify=False)
          response_data = response.json()
          # for func in func_calls:
        except:
          pass

        # Добавляем ответ модели в историю диалога
        conversation_history.append({
            "role": "assistant",
            "content": response_data['choices'][0]['message']['content']
        })

        return response, conversation_history
    except requests.RequestException as e:
        # Обработка исключения в случае ошибки запроса
        print(f"Произошла ошибка: {str(e)}")
        return None, conversation_history

Examples of responses received:

conversation_history = []

response, conversation_history = get_chat_completion(giga_token, "какая погода в Москве?", conversation_history)
[{'role': 'system',
  'content': 'ты менеджер по продажам, твоя задача сообщать цены из прайс-листа и погоду'},
 {'role': 'user', 'content': 'какая погода в Москве?'},
 {'content': '',
  'role': 'assistant',
  'function_call': {'name': 'weather_forecast',
   'arguments': {'location': 'Москва'}}},
 {'role': 'function', 'content': 'в Москве +13', 'name': 'weather_forecast'},
 {'role': 'assistant', 'content': 'В Москве сейчас +13°С.'}]

learned about the weather

conversation_history = []

response, conversation_history = get_chat_completion(giga_token, "дай цены по всей продукции", conversation_history)
[{'role': 'system',
  'content': 'ты менеджер по продажам, твоя задача сообщать цены из прайс-листа и погоду'},
 {'role': 'user', 'content': 'дай цены по всей продукции'},
 {'content': '',
  'role': 'assistant',
  'function_call': {'name': 'get_full_pricelist',
   'arguments': {'products': 'всю'}}},
 {'role': 'function',
  'content': '[{"вид продукции": "Тушка индейки самец", "цена за 1 кг, рубли РФ": "295"}, ... , {"вид продукции": "Свиная шея", "цена за 1 кг, рубли РФ": "400"}]',
  'name': 'get_full_pricelist'},
 {'role': 'assistant',
  'content': 'Цены на всю продукцию следующие: Тушка индейки самец - 295 рублей за 1 кг, ... , Свиная шея - 400 рублей за 1 кг.'}]

got the prices from the price list

conversation_history = []

response, conversation_history = get_chat_completion(giga_token, "сколько стоит рагу и какая погода в москве?", conversation_history)
[{'role': 'system',
  'content': 'ты менеджер по продажам, твоя задача сообщать цены из прайс-листа и погоду'},
 {'role': 'user', 'content': 'сколько стоит рагу и какая погода в москве?'},
 {'content': '',
  'role': 'assistant',
  'function_call': {'name': 'get_prices',
   'arguments': {'products': 'Рагу индейки  (монолит), Рагу индейки (лоток)'}}},
 {'role': 'function', 'content': '[]', 'name': 'get_prices'},
 {'role': 'assistant',
  'content': 'Извините, но я не могу предоставить вам информацию о цене рагу. Могу ли я помочь вам с чем-то ещё?'}]

Here the model generated the argument as a string, not a list, so the products were not found in the dataframe; this case can be processed. Another thing is that here the model calls only one function, and not two, as the context requires. However, we must not forget that the functionality is at the stage 'preview' and this will most likely be improved.

This is my first experience of using function calls in the GigaChat API, share your ideas/opinions/tips on this topic, I’ll post all the code on github and I will share again videowith which I began my acquaintance with the GigaChat API.

Author of the article: Sergey Romanov

Similar Posts

Leave a Reply

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