Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import re | |
| import uuid | |
| import inspect | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Any | |
| import requests | |
| ROOT = Path(__file__).resolve().parent | |
| DATA_DIR = ROOT / "data" | |
| AUTH_URL = os.getenv( | |
| "GIGACHAT_AUTH_URL", | |
| "https://ngw.devices.sberbank.ru:9443/api/v2/oauth", | |
| ) | |
| CHAT_URL = os.getenv( | |
| "GIGACHAT_CHAT_URL", | |
| "https://gigachat.devices.sberbank.ru/api/v1/chat/completions", | |
| ) | |
| DEFAULT_SCOPE = os.getenv("GIGACHAT_SCOPE", "GIGACHAT_API_CORP") | |
| DEFAULT_MODEL = os.getenv("GIGACHAT_MODEL", "GigaChat-2-Max") | |
| MODEL_CHOICES = ["GigaChat-2-Max", "GigaChat-2-Pro", "GigaChat-3-Ultra"] | |
| BANNED_CLOSINGS = ( | |
| "рассказать подробнее", | |
| "узнать больше", | |
| "узнать подробности", | |
| "нужна дополнительная информация", | |
| "рассказать подробности", | |
| "узнать об условиях", | |
| "об условиях оформления", | |
| ) | |
| BANNED_PROCESS_WORDS = ( | |
| "оператор:", | |
| "специалист", | |
| "пока вы ожидаете", | |
| "решаем ваш вопрос", | |
| "текущей проблемы", | |
| ) | |
| DEFAULT_PRODUCT_NAME = "Бизнес-карта" | |
| DEFAULT_BIZ_FEATURES = ( | |
| "Бизнес-карта — это именная банковская карта, выпускаемая к расчётному счёту " | |
| "юридического лица или индивидуального предпринимателя в рублях, при этом количество " | |
| "выпускаемых карт не ограничено. Карту можно выпустить к действующему счёту начиная со " | |
| "дня активации пакета услуг или позже, но обязательно после его оплаты. К открытому счёту " | |
| "бизнес-карту оформляют либо в отделении банка только на пластиковом носителе, либо через " | |
| "СберБизнес как на пластике, так и в цифровом формате без пластика. Используется платёжная " | |
| "система МИР, и после выпуска необходимо установить лимиты на совершение операций. " | |
| "Продукт обеспечивает экономию времени на банковском обслуживании, эффективное управление " | |
| "подотчётными расходами, анализ расходования средств и контроль оборота наличности, а также " | |
| "доступ к средствам со счёта компании в рублях в удобное время в банкоматах Сбербанка и " | |
| "сторонних банков. Кроме того, бизнес-карта гарантирует сохранность денежных средств благодаря " | |
| "возможности дистанционной блокировки при утере, начисление бонусов СберБизнес Спасибо с " | |
| "конвертацией в рубли на банковский счёт, а также предоставляет скидки на АЗС и контроль за " | |
| "расходами на топливо без выпуска отдельной топливной карты через сервис " | |
| "'Заправить авто для бизнеса'." | |
| ) | |
| DEFAULT_BIZ_EXAMPLES = ( | |
| "Пример фразы: К расчётному счёту можно подключить бизнес-карту и оформить на себя или " | |
| "сотрудника, настроив лимиты. Это позволит уйти от выдачи денег под отчёт. Рассказать о всех " | |
| "возможностях или перейдем к оформлению?\n" | |
| "Пример фразы: С помощью бизнес-карты можно оплачивать покупки и получать кешбэк, делать " | |
| "переводы без платёжек, для переводов нужен только номер карты или телефон получателя. Вам " | |
| "рассказать о всех возможностях бизнес карты или хотите сразу оформить?\n" | |
| "Пример фразы: У нас есть предложение, которое может сделать снятие наличных для бизнеса более " | |
| "выгодным. Это наша Бизнес-карта. Комиссия по ней от 2% до 4,5%, а через кассу до 10%. Снимать " | |
| "и вносить наличные можно круглосуточно. Рассказать о всех возможностях или перейдем к оформлению?" | |
| ) | |
| EARLY_DIALOGS = { | |
| "Командировки и подотчёт": ( | |
| "Клиент: Здравствуйте. Подскажите, как удобнее выдавать сотрудникам деньги на командировки, " | |
| "а то постоянно вожусь с авансовыми отчётами." | |
| ), | |
| "Снятие наличных": ( | |
| "Клиент: Добрый день. Часто снимаю наличные для бизнеса через кассу, комиссия кусается. " | |
| "Есть что-то выгоднее?" | |
| ), | |
| "Контроль расходов": ( | |
| "Клиент: Здравствуйте. Хочу контролировать, на что сотрудники тратят деньги компании, " | |
| "и видеть аналитику по расходам." | |
| ), | |
| "АЗС и топливо": ( | |
| "Клиент: Добрый день. У нас разъездная работа, много заправляемся. Можно как-то отслеживать " | |
| "траты на топливо без отдельной топливной карты?" | |
| ), | |
| } | |
| AFTER_DIALOGS = { | |
| "Госзакупки": ( | |
| "Клиент: Мы участвуем в госзакупках по 44-ФЗ, для обеспечения заявки нужна банковская гарантия. " | |
| "Поможете?" | |
| ), | |
| "Свободные деньги": ( | |
| "Клиент: У меня на счету лежат свободные деньги, хочу получать процент, но чтобы доступ к ним " | |
| "оставался каждый день." | |
| ), | |
| "Зарплата": "Клиент: Платим зарплату сотрудникам, хочется удобнее и без комиссии за переводы.", | |
| "Страхование": "Клиент: Хочу застраховать и бизнес, и сотрудников от основных рисков одним полисом.", | |
| "Подотчёт": "Клиент: Постоянно выдаём сотрудникам деньги под отчёт на расходы, неудобно с отчётами.", | |
| } | |
| FALLBACK_AFTER_PRODUCTS = [ | |
| ( | |
| "1", | |
| "Особенности предложения: Банковская гарантия — способ обеспечения исполнения обязательств. " | |
| "Размер гарантии определяется заказчиком и составляет от 5 до 30 процентов от начальной " | |
| "максимальной цены контракта. Доступны онлайн-гарантии для 44-ФЗ и 223-ФЗ.", | |
| ), | |
| ( | |
| "2", | |
| "Особенности предложения: Неснижаемый остаток позволяет получать процентный доход по ставкам, " | |
| "близким к депозитам, и сохранять доступ к средствам в течение дня. Минимальная сумма размещения " | |
| "500 000 рублей, минимальный срок от 7 календарных дней.", | |
| ), | |
| ( | |
| "3", | |
| "Особенности предложения: Защита бизнеса 360° объединяет страхование имущества, перерыва " | |
| "деятельности, экологической ответственности, киберрисков, персонала и гражданской ответственности. " | |
| "Оформление занимает 5 минут на сайте Сбера.", | |
| ), | |
| ( | |
| "4", | |
| "Особенности предложения: Бизнес-карта выпускается к расчётному счёту, помогает управлять " | |
| "подотчётными расходами, анализировать расходы, контролировать наличность и настраивать лимиты.", | |
| ), | |
| ( | |
| "5", | |
| "Особенности предложения: Индивидуальное решение по страховой защите подбирается страховым " | |
| "менеджером под потребности клиента, риски и страховые суммы.", | |
| ), | |
| ( | |
| "6", | |
| "Особенности предложения: Депозит позволяет получить процентный доход за счёт размещения " | |
| "свободных средств. Минимальная сумма депозита 10 000 рублей, минимальный срок размещения один день.", | |
| ), | |
| ( | |
| "7", | |
| "Особенности предложения: Зарплатный проект включает онлайн-сервисы, QR-код для выпуска карт, " | |
| "зачисления зарплаты и выплаты самозанятым без комиссии, скорость зачисления от 1 минуты.", | |
| ), | |
| ] | |
| class PromptPair: | |
| old_system: str | |
| old_human: str | |
| new_system: str | |
| new_human: str | |
| def read_data_file(name: str) -> str: | |
| path = DATA_DIR / name | |
| if not path.exists(): | |
| return "" | |
| return path.read_text(encoding="utf-8") | |
| def decode_source(raw: str) -> str: | |
| return raw.replace("\\n", "\n").replace("\\xa0", " ").replace("\\'", "'").strip() | |
| def select_early_dialog(name: str) -> str: | |
| return EARLY_DIALOGS.get(name, next(iter(EARLY_DIALOGS.values()))) | |
| def select_after_dialog(name: str) -> str: | |
| return AFTER_DIALOGS.get(name, next(iter(AFTER_DIALOGS.values()))) | |
| def load_local_credentials() -> str: | |
| for path in (ROOT / "GigaChat-2-Max_key.txt", ROOT.parent / "GigaChat-2-Max_key.txt"): | |
| if path.exists(): | |
| value = path.read_text(encoding="utf-8").strip() | |
| if value: | |
| return value | |
| return "" | |
| def resolve_credentials(ui_credentials: str | None) -> str: | |
| candidates = [ | |
| ui_credentials, | |
| os.getenv("GIGACHAT_CREDENTIALS"), | |
| os.getenv("GC_KEY_CORP"), | |
| os.getenv("GIGACHAT_API_KEY"), | |
| load_local_credentials(), | |
| ] | |
| for candidate in candidates: | |
| if candidate and candidate.strip(): | |
| return candidate.strip() | |
| return "" | |
| def auth_headers(credentials: str) -> dict[str, str]: | |
| return { | |
| "Authorization": f"Basic {credentials}", | |
| "RqUID": str(uuid.uuid4()), | |
| "Content-Type": "application/x-www-form-urlencoded", | |
| "Accept": "application/json", | |
| } | |
| def response_excerpt(response: requests.Response) -> str: | |
| text = response.text.replace("\n", " ").strip() | |
| return text[:600] | |
| def disable_ssl_warnings_if_needed(verify_ssl: bool) -> None: | |
| if verify_ssl: | |
| return | |
| import urllib3 | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| def request_token(credentials: str, scope: str, verify_ssl: bool, timeout: int = 30) -> str: | |
| if not credentials: | |
| raise ValueError( | |
| "Не найден ключ GigaChat. В Space добавьте Secret GIGACHAT_CREDENTIALS " | |
| "или GC_KEY_CORP, либо вставьте ключ в поле интерфейса." | |
| ) | |
| disable_ssl_warnings_if_needed(verify_ssl) | |
| response = requests.post( | |
| AUTH_URL, | |
| headers=auth_headers(credentials), | |
| data={"scope": scope or DEFAULT_SCOPE}, | |
| timeout=timeout, | |
| verify=verify_ssl, | |
| ) | |
| if response.status_code >= 400: | |
| raise RuntimeError(f"OAuth {response.status_code}: {response_excerpt(response)}") | |
| payload = response.json() | |
| token = payload.get("access_token") or payload.get("accessToken") | |
| if not token: | |
| raise RuntimeError("OAuth ответ не содержит access_token.") | |
| return token | |
| def chat_completion( | |
| token: str, | |
| system_prompt: str, | |
| user_prompt: str, | |
| model: str, | |
| temperature: float, | |
| verify_ssl: bool, | |
| timeout: int = 90, | |
| ) -> str: | |
| disable_ssl_warnings_if_needed(verify_ssl) | |
| response = requests.post( | |
| CHAT_URL, | |
| headers={ | |
| "Authorization": f"Bearer {token}", | |
| "Content-Type": "application/json", | |
| "Accept": "application/json", | |
| }, | |
| json={ | |
| "model": model or DEFAULT_MODEL, | |
| "messages": [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt}, | |
| ], | |
| "temperature": temperature, | |
| "stream": False, | |
| }, | |
| timeout=timeout, | |
| verify=verify_ssl, | |
| ) | |
| if response.status_code >= 400: | |
| raise RuntimeError(f"Chat {response.status_code}: {response_excerpt(response)}") | |
| payload = response.json() | |
| return payload["choices"][0]["message"]["content"].strip() | |
| def build_early_prompts( | |
| dialog: str, | |
| product_name: str, | |
| product_features: str, | |
| examples: str, | |
| ) -> PromptPair: | |
| product_name = (product_name or DEFAULT_PRODUCT_NAME).strip() | |
| product_features = (product_features or DEFAULT_BIZ_FEATURES).strip() | |
| examples = (examples or DEFAULT_BIZ_EXAMPLES).strip() | |
| dialog = (dialog or select_early_dialog("Командировки и подотчёт")).strip() | |
| old_system = "Ты помощник по продажам оператора поддержки юридических лиц Сбербанка." | |
| old_human = f"""Ниже приведен диалог клиента: | |
| Диалог: | |
| {dialog} | |
| Ты помогаешь продать продукт '{product_name}'. | |
| Особенности предложения: {product_features} | |
| {examples} | |
| При генерации следи, чтобы ни в одном предложении не появлялись слова и фразы: | |
| "ранее клиент уже был", "пока", "специалист", "в процессе", "оператор:", "хочешь", | |
| "могу помочь", "пока вы ожидаете", "пока мы решаем ваш вопрос", "текущей проблемы". | |
| Избегай формулировок, связанных смыслом с наличием этих слов. | |
| Фраза будет сказана оператором без изменений. Вопрос возможен только в конце фразы должен | |
| быть направлен на утверждение, что подключить удобно и просто. Или предложением рассказать | |
| подробнее. напр.: Подключить удобно и просто, рассказать подробнее? | |
| Исключи приветствия и обсуждение проблемы клиента. Исключи информацию для оператора. | |
| Должна быть только фраза продажи для клиента. | |
| Фраза пишется исходя из Особенности предложения выбранного продукта. Фраза целиком должна | |
| подтверждаться информацией из Особенности предложения. Исключи из Фразы все, что не | |
| подтверждено из Особенности предложения. | |
| Напиши фразу (и ничего кроме фразы) с предложением о продаже продукта, учитывая, что нельзя | |
| упоминать процесс решения вопроса клиента. Учти контекст всего диалога и описание продукта. | |
| Фраза должна содержать предложение подключения услуги и объяснение, почему клиенту продукт | |
| может быть выгоден.""" | |
| new_system = ( | |
| "Ты — оператор поддержки юридических лиц Сбербанка. Ты пишешь короткую фразу, которой " | |
| "оператор предложит клиенту банковский продукт. Фразу произнесут клиенту дословно. " | |
| "Обращайся к клиенту на «вы»." | |
| ) | |
| new_human = f"""Диалог с клиентом: | |
| {dialog} | |
| Продукт для предложения: {product_name}. | |
| Факты о продукте (бери информацию и цифры только отсюда): | |
| {product_features} | |
| Как написать фразу: | |
| 1. Определи потребность клиента из диалога и выбери 1–2 преимущества продукта, которые её закрывают. Не перечисляй все возможности подряд. | |
| 2. Длина — 1–2 предложения, до 250 знаков. Коротко и по делу. | |
| 3. Используй только факты из блока о продукте. Если точного значения там нет — не указывай его и не выдумывай. | |
| 4. Концовка должна быть привязана к конкретной выгоде продукта или к простоте оформления. Запрещены концовки-отписки вида «рассказать подробнее?», «узнать больше?», «узнать подробности?», «нужна дополнительная информация?». Приглашение к подключению ставь в начало фразы («Подключите…», «Оформите…»), а не в концовку-вопрос. | |
| 5. Пиши только фразу для клиента: без приветствий, без обсуждения вопроса клиента, без пояснений для оператора. | |
| Примеры ниже — образец тона и длины, а НЕ текст для копирования. Аргументы подбирай под конкретный диалог, а не переписывай примеры: | |
| {examples} | |
| Напиши только фразу.""" | |
| return PromptPair(old_system, old_human, new_system, new_human) | |
| def extract_products_from_sale_prompt(raw_prompt: str) -> list[tuple[str, str]]: | |
| decoded = decode_source(raw_prompt) | |
| catalog_part = decoded.split("Используй информацию")[0] | |
| chunks = re.split(r"Номер продукта:\s*(\d+)\.", catalog_part) | |
| products: list[tuple[str, str]] = [] | |
| for index in range(1, len(chunks), 2): | |
| number = chunks[index].strip() | |
| body = chunks[index + 1].strip() | |
| if number and body: | |
| products.append((number, body)) | |
| return products | |
| def renumber_catalog(products: list[tuple[str, str]]) -> str: | |
| return "\n\n".join( | |
| f"Номер продукта: {new_number}.\n{body.strip()}" | |
| for new_number, (_old_number, body) in enumerate(products, 1) | |
| ) | |
| def default_after_catalogs() -> tuple[str, str]: | |
| products = extract_products_from_sale_prompt(read_data_file("sale_after_fix_chain.txt")) | |
| if not products: | |
| products = FALLBACK_AFTER_PRODUCTS | |
| old_catalog = renumber_catalog(products) | |
| by_number = {number: body for number, body in products} | |
| keep_order = ["6", "2", "3", "4", "5", "7", "8"] | |
| if all(number in by_number for number in keep_order): | |
| cleaned = [(number, by_number[number]) for number in keep_order] | |
| else: | |
| cleaned = products[:] | |
| return old_catalog, renumber_catalog(cleaned) | |
| def textbox_copy_kwargs(textbox_cls: Any) -> dict[str, Any]: | |
| textbox_params = inspect.signature(textbox_cls).parameters | |
| if "show_copy_button" in textbox_params: | |
| return {"show_copy_button": True} | |
| if "buttons" in textbox_params: | |
| return {"buttons": ["copy"]} | |
| return {} | |
| def build_after_prompts(dialog: str, old_catalog: str, new_catalog: str) -> PromptPair: | |
| dialog = (dialog or select_after_dialog("Подотчёт")).strip() | |
| old_catalog = (old_catalog or default_after_catalogs()[0]).strip() | |
| new_catalog = (new_catalog or default_after_catalogs()[1]).strip() | |
| system = "Ты помощник по продажам оператора поддержки юридических лиц Сбербанка." | |
| old_human = f"""Ты помогаешь продать продукты только из списка: | |
| {old_catalog} | |
| Используй информацию и пояснения к продуктам строго в рамках каждого продукта. | |
| Ниже приведен диалог оператора с клиентом. | |
| Диалог: | |
| {dialog} | |
| При генерации избегай слов про процесс решения проблемы клиента ("пока", "специалист", "в процессе" и т.п.). | |
| Продукт предлагается на основе потребностей, озвученных клиентом. Исключи приветствия и обсуждение проблемы клиента. | |
| Фраза в sales_phrase пишется исходя из Особенности предложения выбранного продукта и должна целиком ими подтверждаться. Не указывай значений, которых нет в Особенности предложения. | |
| Фраза будет сказана оператором без изменений. Вопрос возможен только в конце и должен быть направлен на утверждение, что подключить удобно и просто, или предложением рассказать подробнее. | |
| product_number и sales_phrase должны быть заполнены! В ответе верни JSON: | |
| {{"product_number": "номер продукта из списка" (str), "sales_phrase": "несколько предложений для начала продажи выбранного продукта" (str)}} | |
| JSON:""" | |
| new_human = f"""Ты помогаешь продать ОДИН продукт из списка: | |
| {new_catalog} | |
| Используй информацию строго в рамках выбранного продукта. | |
| Диалог оператора с клиентом: | |
| {dialog} | |
| Как выбрать и написать: | |
| 1. Выбери один продукт, который закрывает потребность, озвученную клиентом в диалоге. | |
| 2. В sales_phrase возьми 1–2 преимущества этого продукта под потребность клиента. Не перечисляй всё подряд. | |
| 3. Длина sales_phrase — 1–2 предложения, до 250 знаков. | |
| 4. Используй только факты и цифры из «Особенности предложения» выбранного продукта. Нет значения — не выдумывай. | |
| 5. Концовка привязана к выгоде продукта или к простоте оформления. Запрещены концовки-отписки («рассказать подробнее?», «узнать больше?»). Приглашение к подключению ставь в начало фразы. | |
| 6. sales_phrase — только текст для клиента: без приветствий, без обсуждения проблемы, без слов про процесс её решения. | |
| Верни строго JSON: | |
| {{"product_number": "номер выбранного продукта" (str), "sales_phrase": "фраза для клиента" (str)}} | |
| JSON:""" | |
| return PromptPair(system, old_human, system, new_human) | |
| def parse_json_object(text: str) -> dict[str, Any] | None: | |
| match = re.search(r"\{.*\}", text, flags=re.DOTALL) | |
| if not match: | |
| return None | |
| try: | |
| parsed = json.loads(match.group(0)) | |
| except json.JSONDecodeError: | |
| return None | |
| return parsed if isinstance(parsed, dict) else None | |
| def phrase_from_output(text: str, expects_json: bool) -> tuple[str, str, str]: | |
| if not expects_json: | |
| return text.strip(), "n/a", "n/a" | |
| parsed = parse_json_object(text) | |
| if parsed is None: | |
| return text.strip(), "нет", "" | |
| phrase = str(parsed.get("sales_phrase", "")).strip() | |
| product_number = str(parsed.get("product_number", "")).strip() | |
| return phrase, "да", product_number | |
| def metrics_row(label: str, output: str, expects_json: bool) -> list[Any]: | |
| phrase, json_ok, product_number = phrase_from_output(output, expects_json) | |
| lower = phrase.lower() | |
| flags = [] | |
| for fragment in BANNED_CLOSINGS: | |
| if fragment in lower: | |
| flags.append(fragment) | |
| for fragment in BANNED_PROCESS_WORDS: | |
| if fragment in lower: | |
| flags.append(fragment) | |
| if len(phrase) > 250: | |
| flags.append(">250") | |
| return [ | |
| label, | |
| len(phrase), | |
| "да" if phrase.rstrip().endswith("?") else "нет", | |
| "да" if any(fragment in lower for fragment in BANNED_CLOSINGS) else "нет", | |
| json_ok, | |
| product_number, | |
| ", ".join(flags) if flags else "нет", | |
| ] | |
| def run_prompt_pair( | |
| pair: PromptPair, | |
| api_credentials: str, | |
| model: str, | |
| temperature: float, | |
| scope: str, | |
| verify_ssl: bool, | |
| ) -> tuple[str, str]: | |
| credentials = resolve_credentials(api_credentials) | |
| token = request_token(credentials, scope or DEFAULT_SCOPE, verify_ssl) | |
| old_output = chat_completion(token, pair.old_system, pair.old_human, model, temperature, verify_ssl) | |
| new_output = chat_completion(token, pair.new_system, pair.new_human, model, temperature, verify_ssl) | |
| return old_output, new_output | |
| def compare_early( | |
| api_credentials: str, | |
| model: str, | |
| temperature: float, | |
| scope: str, | |
| verify_ssl: bool, | |
| dialog: str, | |
| product_name: str, | |
| product_features: str, | |
| examples: str, | |
| ) -> tuple[str, str, list[list[Any]], str, str]: | |
| pair = build_early_prompts(dialog, product_name, product_features, examples) | |
| try: | |
| old_output, new_output = run_prompt_pair(pair, api_credentials, model, temperature, scope, verify_ssl) | |
| except Exception as exc: | |
| old_output = f"[ОШИБКА] {exc}" | |
| new_output = f"[ОШИБКА] {exc}" | |
| rows = [ | |
| metrics_row("Старый", old_output, expects_json=False), | |
| metrics_row("Новый", new_output, expects_json=False), | |
| ] | |
| return old_output, new_output, rows, pair.old_human, pair.new_human | |
| def compare_after( | |
| api_credentials: str, | |
| model: str, | |
| temperature: float, | |
| scope: str, | |
| verify_ssl: bool, | |
| dialog: str, | |
| old_catalog: str, | |
| new_catalog: str, | |
| ) -> tuple[str, str, list[list[Any]], str, str]: | |
| pair = build_after_prompts(dialog, old_catalog, new_catalog) | |
| try: | |
| old_output, new_output = run_prompt_pair(pair, api_credentials, model, temperature, scope, verify_ssl) | |
| except Exception as exc: | |
| old_output = f"[ОШИБКА] {exc}" | |
| new_output = f"[ОШИБКА] {exc}" | |
| rows = [ | |
| metrics_row("Старый", old_output, expects_json=True), | |
| metrics_row("Новый", new_output, expects_json=True), | |
| ] | |
| return old_output, new_output, rows, pair.old_human, pair.new_human | |
| def build_demo(): | |
| import gradio as gr | |
| def copyable_textbox(**kwargs): | |
| kwargs.update(textbox_copy_kwargs(gr.Textbox)) | |
| return gr.Textbox(**kwargs) | |
| old_catalog, new_catalog = default_after_catalogs() | |
| metric_headers = ["Версия", "Символов", "Финальный вопрос", "Отписка", "JSON", "Продукт", "Флаги"] | |
| with gr.Blocks(title="GigaChat sales prompt A/B") as demo: | |
| gr.Markdown("# GigaChat sales prompt A/B") | |
| with gr.Row(): | |
| api_credentials = gr.Textbox( | |
| label="GigaChat credentials", | |
| type="password", | |
| placeholder="Space Secret GIGACHAT_CREDENTIALS или GC_KEY_CORP", | |
| scale=2, | |
| ) | |
| model = gr.Dropdown(label="Модель", choices=MODEL_CHOICES, value=DEFAULT_MODEL, allow_custom_value=True) | |
| temperature = gr.Slider(label="Temperature", minimum=0, maximum=1.5, value=0.87, step=0.01) | |
| with gr.Accordion("API", open=False): | |
| scope = gr.Textbox(label="Scope", value=DEFAULT_SCOPE) | |
| verify_ssl = gr.Checkbox(label="Проверять SSL-сертификат", value=False) | |
| with gr.Tab("Early proposal"): | |
| early_sample = gr.Dropdown( | |
| label="Сценарий", | |
| choices=list(EARLY_DIALOGS.keys()), | |
| value="Командировки и подотчёт", | |
| ) | |
| early_dialog = gr.Textbox(label="Диалог", value=select_early_dialog("Командировки и подотчёт"), lines=4) | |
| early_sample.change(select_early_dialog, inputs=early_sample, outputs=early_dialog) | |
| with gr.Row(): | |
| product_name = gr.Textbox(label="Продукт", value=DEFAULT_PRODUCT_NAME) | |
| product_features = gr.Textbox(label="Особенности предложения", value=DEFAULT_BIZ_FEATURES, lines=8) | |
| product_examples = gr.Textbox(label="Примеры", value=DEFAULT_BIZ_EXAMPLES, lines=6) | |
| early_run = gr.Button("Сравнить early", variant="primary") | |
| with gr.Row(): | |
| early_old_output = copyable_textbox(label="Старый результат", lines=6) | |
| early_new_output = copyable_textbox(label="Новый результат", lines=6) | |
| early_metrics = gr.Dataframe(headers=metric_headers, label="Метрики", interactive=False) | |
| with gr.Accordion("Собранные промпты", open=False): | |
| early_old_prompt = copyable_textbox(label="Старый human prompt", lines=12) | |
| early_new_prompt = copyable_textbox(label="Новый human prompt", lines=12) | |
| early_run.click( | |
| compare_early, | |
| inputs=[ | |
| api_credentials, | |
| model, | |
| temperature, | |
| scope, | |
| verify_ssl, | |
| early_dialog, | |
| product_name, | |
| product_features, | |
| product_examples, | |
| ], | |
| outputs=[ | |
| early_old_output, | |
| early_new_output, | |
| early_metrics, | |
| early_old_prompt, | |
| early_new_prompt, | |
| ], | |
| ) | |
| with gr.Tab("After fix"): | |
| after_sample = gr.Dropdown(label="Сценарий", choices=list(AFTER_DIALOGS.keys()), value="Подотчёт") | |
| after_dialog = gr.Textbox(label="Диалог", value=select_after_dialog("Подотчёт"), lines=4) | |
| after_sample.change(select_after_dialog, inputs=after_sample, outputs=after_dialog) | |
| with gr.Accordion("Каталоги", open=False): | |
| old_catalog_box = gr.Textbox(label="Старый каталог", value=old_catalog, lines=12) | |
| new_catalog_box = gr.Textbox(label="Новый каталог", value=new_catalog, lines=12) | |
| after_run = gr.Button("Сравнить after_fix", variant="primary") | |
| with gr.Row(): | |
| after_old_output = copyable_textbox(label="Старый JSON", lines=8) | |
| after_new_output = copyable_textbox(label="Новый JSON", lines=8) | |
| after_metrics = gr.Dataframe(headers=metric_headers, label="Метрики", interactive=False) | |
| with gr.Accordion("Собранные промпты", open=False): | |
| after_old_prompt = copyable_textbox(label="Старый human prompt", lines=12) | |
| after_new_prompt = copyable_textbox(label="Новый human prompt", lines=12) | |
| after_run.click( | |
| compare_after, | |
| inputs=[ | |
| api_credentials, | |
| model, | |
| temperature, | |
| scope, | |
| verify_ssl, | |
| after_dialog, | |
| old_catalog_box, | |
| new_catalog_box, | |
| ], | |
| outputs=[ | |
| after_old_output, | |
| after_new_output, | |
| after_metrics, | |
| after_old_prompt, | |
| after_new_prompt, | |
| ], | |
| ) | |
| return demo | |
| try: | |
| demo = build_demo() | |
| except ModuleNotFoundError as exc: | |
| if exc.name != "gradio": | |
| raise | |
| demo = None | |
| if __name__ == "__main__": | |
| if demo is None: | |
| raise RuntimeError("Gradio is not installed. Run: python -m pip install -r hf_space/requirements.txt") | |
| demo.queue(default_concurrency_limit=2).launch() | |