Уже неделю чувствую себя ненормированно свободным, впервые за долгое время проснулось кодовдохновение. А потому - делюсь! Много мыслей и немного кода.
В чем экзистенциальный ужас платформенной разработки? Приходится поддерживать и учитывать разные платформы, их версии, ограничения. И как бы я ни бегал от мрака версионности, всё-же напоролся на примере sgr core
Итак, проблема:
Фреймворк построен на Structured Output, но его поддерживают далеко не все конфигурации локальных/проприетарных LLM. Что самое страшное, даже если поддерживают, то не всегда однородно! Требуемые JSON схемы могут различаться.
Если фреймворк может работать, а может не работать на неопределённом множестве моделей - это ужасненько.
Structured output(SO) по моему скромному мнению это железная база, без которой сложно представить взаимодействие агентов
Примеры:
- Кто-то гарантирует ответ строго по формату, а кто-то может допустить ошибки даже при заданной схеме
- Кто-то поддерживает вложенные AnyOf и прочие агрегаты схем, а кто-то нет
- Где-то можно прокинуть ограничения
min_length=1, max_length=3, а где-то нет, они в лучшем случае будут проигнорированы- Кто-то хавает литералы и сопутствующие им enums, а кто-то отказывается
Как общий знаменатель пришла мысля создать решение, которое бы эмулировало независимый от ллмки SO без реальной в нём потребности на стороне провайдера . Идея "попроси модель сделать как надо" далеко не нова, и тем не менее было полезно посмотреть, насколько хорошо и стабильно это могут делать современные LLM
Концепция:
class
ToolInstantiator принимает в свой init Pydantic модель, и имеет два основных метода интерфейса:- Сгенерить промпт с описанием схемы для LLM
- Провалидировать полученный ответ LLM на предмет возможности билда инстанса Pydantic модели
На каждом следующем этапе промпт,выдаваемый классом, учитывает ошибки и проблемы предыдущей итерации, корректируя/фокусируя LLM
Путём некоторых экспериментов было выявлено, что прямая json схема для LLM сложновата ввиду нотации и неконсистентной информации о полях и их типах. А ещё иногда модельки путались и выдавали JSON schema аналогичную промптовой в ответ. Поэтому появился класс-помогатор
SchemaSimplifier, разбирающий схему и преобразующий в более минималистичную нотациюЕщё была интересная концепция, где каждое поле валидировалось по отдельности и даже если модель выдала в общем не полностью корректный JSON, часть верных полей принимались и не требовались на дальнейших итерациях генерёжки. Идея была отброшена ввиду нелицеприятности кодреализации такой фичи.
Лучше никому не видеть мою попытку в конвертацию типов raw context regex parsing -> json string->python type -> pydantic validator
Вот тут реализация - почти хорошо
работает следующим образом
for attempt in range(max_retries):
async with self.openai_client.chat.completions.stream(
messages=messages + [{"role": "user", "content": instantiator.generate_format_prompt()}],
) as stream:
completion = await stream.get_final_completion()
try:
content = completion.choices[0].message.content
tool_instance = instantiator.build_model(content)
return tool_instance
except ValueError:
continue