Документация

Choice

Choice — это тип вопроса System One для выбора одного варианта из заданного набора. Ответ содержит выбранный вариант, вероятность для каждого варианта и уверенность.

Используйте Choice, когда ответ — один из фиксированного набора вариантов. Например, какая команда обрабатывает тикет, к какой категории относится товар или на каком языке написан фрагмент кода. Если ответ — позиция на шкале, используйте Score. Если ответ «да» или «нет», используйте Noul. Как выбрать тип вопроса сравнивает все три.

Ответ Choice — это выбранный вариант в choice. Модель также возвращает вероятность для каждого варианта в probabilities и значение confidence для выбранного варианта.

Примеры вопросов:

"What programming language is this code written in"
  → options: python, javascript, typescript, go, rust, other

"What type of meeting is this based on the title and description"
  → options: standup, planning, retrospective, one on one, brainstorm, none of the above

"Which product category does this item belong to"
  → options: electronics, clothing, home garden, food and beverage

Структура запроса

Тело POST-запроса к API TypeSafe имеет определённую структуру. На верхнем уровне три поля: state — содержимое для оценки; model; и questions — отображение выбранных вами id вопросов в объекты вопросов. Каждый вопрос Choice имеет следующие поля:

  • type: Всегда "choice".
  • instructions: Вопрос, на который отвечает модель.
  • criteria: Варианты ответа в виде отображения. Каждый ключ — имя варианта, каждое значение — описание этого варианта.

Ниже запрос, где состояние — тикет поддержки из интернет-магазина обуви, а вопрос — какая команда должна его обработать:

request
{
  "state": "My running shoes arrived in the wrong size. Can I swap them for a size 10?",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this?",
      "criteria": {
        "returns": "Exchanges, wrong or damaged items",
        "shipping": "Delivery status, delays, lost packages",
        "billing": "Charges, invoices, payment problems"
      }
    }
  }
}

Id вопроса выбираете вы, в этом случае department. Ответ возвращается под тем же id. Модель никогда не видит id вопроса. Имена вариантов и их описания передаются модели вместе, поэтому пишите описания, которые отличают варианты друг от друга.

Наши клиентские SDK предоставляют типизированные вопросы. В Python тот же вопрос — это Choice:

from typesafe_sdk import Choice, TypeSafeClient

with TypeSafeClient() as client:
    response = client.system_one(
        state="My running shoes arrived in the wrong size. Can I swap them for a size 10?",
        questions={
            "department": Choice(
                instructions="Which team should handle this?",
                criteria={
                    "returns": "Exchanges, wrong or damaged items",
                    "shipping": "Delivery status, delays, lost packages",
                    "billing": "Charges, invoices, payment problems",
                },
            ),
        },
    )

    print(response.answers["department"].choice)

Используйте метод system_one или endpoint https://api.typesafe.ai/v1/systemone, чтобы вызвать модель System One. Поле model выбирает, какая модель обрабатывает запрос. Как создавать с TypeSafe рассказывает, где в вашем коде её вызывать.

Используйте один из наших клиентских SDK или обратитесь напрямую к HTTP API. Если интеграцию за вас пишет агент программирования, сначала установите навык агента TypeSafe, чтобы он знал формы запроса и ответа.

Структура ответа

В ответе на каждый вопрос приходится одна запись в answers, под id из запроса. Это ответ на пример запроса выше:

{
  "model": "jev-1.13.0",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "returns",
      "confidence": 1.0,
      "probabilities": {
        "shipping": 0.0,
        "returns": 1.0,
        "billing": 0.0
      }
    }
  },
  "usage": {
    "input_tokens": 328,
    "output_tokens": 34
  }
}

Помимо type, каждый ответ Choice содержит три значения:

  • choice: Вариант с наибольшей вероятностью.
  • probabilities: Полное распределение вероятностей по всем вариантам. Сумма всех значений равна 1.
  • confidence: Число от 0 до 1, вычисленное из того, как распределены probabilities. Плоская форма, когда вероятность размазана по нескольким вариантам, означает низкую уверенность. Один пик на одном варианте означает высокую уверенность.

Этот тикет простой, поэтому вся вероятность приходится на returns, а уверенность равна 1.0. Тикет, в котором упомянуты неправильный размер и пропавший возврат, разделил бы вероятность между returns и billing, и уверенность упала бы.

Хорошая практика: несколько вопросов за один вызов

Задавайте все вопросы Choice, которые могут понадобиться вашему коду, в одном запросе, а не по одному запросу на вопрос. Вопросы оцениваются параллельно. Добавление вопросов почти не меняет время ответа, а код может игнорировать ненужные ответы. Лишние вопросы всё равно стоят токенов. Задавайте несколько вопросов вместе объясняет это подробно; следующая секция показывает пять вопросов Choice в одном вызове.

Та же логика применима к вариантам внутри одного вопроса Choice. Вопрос Choice принимает до 255 вариантов, и каждый вариант стоит несколько токенов, поэтому дайте модели полный список команд, категорий или товаров, а не короткий список. Добавьте вариант other или none of the above, когда список может не покрывать все входные данные, чтобы модель могла сказать, что ни один из остальных не подходит.

Чтобы классифицировать документы по глубокой иерархии или большой таксономии, соединяйте вопросы Choice уровень за уровнем. Cookbook по иерархической классификации показывает, как выполнить лучевой поиск по вероятностям Choice, сохраняя лучшие K путей-кандидатов на каждом уровне вместо того, чтобы остановиться на одном жадном пути.

Более сложный пример

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

Запрос ниже задаёт пять вопросов Choice о тикете, который неоднозначнее первого: он касается трёх команд и не говорит, чего хочет клиент.

request
{
  "state": "Shoes arrived two weeks late and in the wrong size. Also I see two charges of $120 on my card. What are you going to do about this?",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this?",
      "criteria": {
        "returns": "Exchanges, wrong or damaged items",
        "shipping": "Delivery status, delays, lost packages",
        "billing": "Charges, invoices, payment problems"
      }
    },
    "return_reason": {
      "type": "choice",
      "instructions": "If the customer wants to return something, why?",
      "criteria": {
        "wrong_size": "The item doesn't fit",
        "wrong_item": "A different product was delivered",
        "damaged": "The item arrived broken or faulty",
        "changed_mind": "The item is fine, the customer no longer wants it",
        "other": "A return reason that fits none of the above"
      }
    },
    "shipping_issue": {
      "type": "choice",
      "instructions": "If this is a shipping problem, which kind is it?",
      "criteria": {
        "not_delivered": "The package never arrived",
        "delayed": "The package is late but still on its way",
        "wrong_address": "The package went to the wrong place",
        "damaged_in_transit": "The package arrived damaged",
        "other": "A shipping problem that fits none of the above"
      }
    },
    "requested_resolution": {
      "type": "choice",
      "instructions": "What does the customer want to happen?",
      "criteria": {
        "exchange": "Swap the item for a different one",
        "refund": "Money back",
        "replacement": "The same item sent again",
        "information": "Just an answer, no action needed"
      }
    },
    "tone": {
      "type": "choice",
      "instructions": "What is the customer's tone?",
      "criteria": {
        "calm": null,
        "frustrated": null,
        "angry": null
      }
    }
  }
}

Два из этих вопросов Choice — спекулятивные: return_reason важен только если department — это returns, а shipping_issue важен только если это shipping. Вопрос tone использует описания null, потому что имена вариантов сами по себе понятны.

Ответ TypeSafe:

{
  "model": "jev-1.13.0",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "returns",
      "confidence": 0.42,
      "probabilities": {
        "shipping": 0.04,
        "billing": 0.35,
        "returns": 0.61
      }
    },
    "return_reason": {
      "type": "choice",
      "choice": "wrong_size",
      "confidence": 1.0,
      "probabilities": {
        "other": 0.0,
        "wrong_size": 1.0,
        "changed_mind": 0.0,
        "damaged": 0.0,
        "wrong_item": 0.0
      }
    },
    "shipping_issue": {
      "type": "choice",
      "choice": "delayed",
      "confidence": 0.67,
      "probabilities": {
        "wrong_address": 0.0,
        "other": 0.26,
        "not_delivered": 0.0,
        "damaged_in_transit": 0.0,
        "delayed": 0.74
      }
    },
    "requested_resolution": {
      "type": "choice",
      "choice": "refund",
      "confidence": 0.2,
      "probabilities": {
        "replacement": 0.34,
        "refund": 0.4,
        "information": 0.02,
        "exchange": 0.24
      }
    },
    "tone": {
      "type": "choice",
      "choice": "frustrated",
      "confidence": 0.76,
      "probabilities": {
        "frustrated": 0.84,
        "angry": 0.16,
        "calm": 0.0
      }
    }
  },
  "usage": {
    "input_tokens": 589,
    "output_tokens": 212
  }
}

Каждый вопрос оценивается отдельно относительно тикета:

  • Ответ department — returns с вероятностью 0.61, но у billing 0.35 из-за двойного списания. Тикет принадлежит двум командам, и разделённая уверенность 0.42 это отражает.
  • return_reason — wrong_size с уверенностью 1.0, что ожидаемо: в тикете это сказано прямо.
  • Ответ shipping_issue разделён между delayed и other. Это спекулятивный вопрос, и department не оказался shipping, поэтому код может его игнорировать, как показано в примере кода ниже.
  • Ответ requested_resolution склоняется к refund с 0.40, а replacement и exchange делят почти всё остальное; уверенность — 0.20. Двойное списание говорит о возврате денег, неправильный размер — о замене, и клиент так и не говорит, чего именно хочет.
  • Ответ tone — frustrated с вероятностью 0.84 и уверенностью 0.76.

Пример кода ниже читает нужные ответы, игнорирует остальные и считает ответ с низкой уверенностью поводом спросить, а не действовать:

from typesafe_sdk import Choice, TypeSafeClient

TRIAGE_QUESTIONS = {
    "department": Choice(
        instructions="Which team should handle this?",
        criteria={
            "returns": "Exchanges, wrong or damaged items",
            "shipping": "Delivery status, delays, lost packages",
            "billing": "Charges, invoices, payment problems",
        },
    ),
    "return_reason": Choice(
        instructions="If the customer wants to return something, why?",
        criteria={
            "wrong_size": "The item doesn't fit",
            "wrong_item": "A different product was delivered",
            "damaged": "The item arrived broken or faulty",
            "changed_mind": "The item is fine, the customer no longer wants it",
            "other": "A return reason that fits none of the above",
        },
    ),
    "shipping_issue": Choice(
        instructions="If this is a shipping problem, which kind is it?",
        criteria={
            "not_delivered": "The package never arrived",
            "delayed": "The package is late but still on its way",
            "wrong_address": "The package went to the wrong place",
            "damaged_in_transit": "The package arrived damaged",
            "other": "A shipping problem that fits none of the above",
        },
    ),
    "requested_resolution": Choice(
        instructions="What does the customer want to happen?",
        criteria={
            "exchange": "Swap the item for a different one",
            "refund": "Money back",
            "replacement": "The same item sent again",
            "information": "Just an answer, no action needed",
        },
    ),
    "tone": Choice(
        instructions="What is the customer's tone?",
        criteria={"calm": None, "frustrated": None, "angry": None},
    ),
}

def triage(ticket: str) -> None:
    with TypeSafeClient() as client:
        response = client.system_one(
            state=ticket,
            questions=TRIAGE_QUESTIONS,
        )
    answers = response.answers

    department = answers["department"]
    if department.confidence < 0.3:
        # Not clear which team to send to. Let a person decide.
        send_to_manual_triage(ticket)
        return

    if department.choice == "returns":
        # return_reason answer is only used here
        assign(ticket, team="returns", issue=answers["return_reason"].choice)
    elif department.choice == "shipping":
        # shipping_issue answer is only used here
        assign(ticket, team="shipping", issue=answers["shipping_issue"].choice)
    else:
        assign(ticket, team="billing")

    # A second team with a real share of the probability gets a copy
    for team, probability in department.probabilities.items():
        if team != department.choice and probability > 0.25:
            notify(ticket, team=team)

    resolution = answers["requested_resolution"]
    if resolution.confidence < 0.5:
        # The customer hasn't said what they want. Ask, don't guess.
        ask_customer_what_they_want(ticket)
    elif resolution.choice == "refund":
        flag_for_refund_approval(ticket)

    if answers["tone"].choice == "angry":
        flag_for_senior_agent(ticket)

Для тикета выше этот код направляет тикет в команду returns с проблемой wrong_size, отправляет команде billing копию, потому что её доля 0.35 превышает порог 0.25, и спрашивает клиента, чего он хочет, потому что уверенность решения 0.20 ниже 0.5. Ответ shipping_issue код не использует.

Один запрос, пять ответов, а логика маршрутизации — обычные операторы if. Если позже вам понадобится узнать язык клиента или о каком товаре тикет, добавьте ещё один вопрос Choice в TRIAGE_QUESTIONS; число запросов останется прежним — один.

Демо умного домашнего помощника оценивает каждый запрос пользователя по длинному списку вопросов Choice в одном вызове: категория запроса, комната, устройство и действие. Большинство этих вопросов не относятся к конкретному запросу, и код их игнорирует.

Структурированные инструкции и criteria

Начните с однострочного описания для каждого варианта. Когда два варианта похожи и модель их путает, опишите каждый объектом вместо строки. Дайте ему поля: что покрывает вариант, что вместо этого относится к соседнему варианту, и несколько примеров входных данных.

Два варианта ответа ниже, return_policy и return_status, легко перепутать. Тикет о любом из них может упоминать возвраты и возвраты денег, поэтому каждый вариант говорит, для чего он не предназначен.

request
{
  "state": "I sent the shoes back a week ago. When do I get my money?",
  "questions": {
    "return_topic": {
      "type": "choice",
      "instructions": {
        "question": "Which returns topic is the customer asking about?",
        "focus": "Classify the information the customer wants."
      },
      "criteria": {
        "return_policy": {
          "what": "Whether and how an item can be returned",
          "not_for": "Progress of a return already sent",
          "examples": [
            "Can I return shoes I've worn once?",
            "How long do I have to return an order?"
          ]
        },
        "return_status": {
          "what": "Progress of a return already sent",
          "not_for": "Whether and how an item can be returned",
          "examples": [
            "Has my return arrived yet?",
            "When will my refund be paid?"
          ]
        }
      }
    }
  }
}

Ответ — return_status с уверенностью 1.0:

{
  "model": "jev-1.13.0",
  "answers": {
    "return_topic": {
      "type": "choice",
      "choice": "return_status",
      "confidence": 1.0,
      "probabilities": {
        "return_policy": 0.0,
        "return_status": 1.0
      }
    }
  },
  "usage": {
    "input_tokens": 407,
    "output_tokens": 32
  }
}

Имена полей question, focus, what, not_for и examples не входят в API, и ни одно из них не зарезервировано. Вы выбираете их так же, как выбираете имена вариантов. Модель видит имена вместе со значениями, поэтому используйте короткие имена, которые обозначают то, что за ними следует.