mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-05 09:14:58 +03:00
🔥 refactor(ai): убрать дневной лимит агента и выбор валюты организации
Дневной бюджет агента (limits.dailyCostUsd) снят вместе с полем. Проверка работала, а учёт — нет: расход брался из ответа провайдера, а его шлёт только OpenRouter; для Custom и локальной модели оставалась прайс-таблица из двух моделей, и на любой другой стоимость записывалась нулём. То есть на всех провайдерах, кроме OpenRouter, лимит не срабатывал никогда и давал ложное чувство защиты. Общий лимит установки из переменной окружения CHATBALLS_AI_GLOBAL_DAILY_COST_LIMIT_MICROS остаётся. Выбор валюты убран из настроек организации и из формы её создания. Сервер принимал только RUB, то есть в списке был один вариант, а само поле не читается нигде: ни одна сумма в продукте не считается в валюте организации. Колонка в базе остаётся, интерфейс её больше не спрашивает — сервер проставляет значение сам. Второй шаг онбординга больше не велит задавать дневной бюджет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1cff9e2183
commit
eff6d7ac0c
18 files changed
+62
-159
No files matched your search
@@ -128,7 +128,6 @@ def agent_card_payload(channel: Channel, *, knowledge_total: int | None = None)
|
||||
"model": agent.model,
|
||||
"providerIntegrationId": agent.provider_integration_id,
|
||||
"modelParams": agent.model_params,
|
||||
"limits": agent.limits,
|
||||
"answerLanguage": agent.answer_language,
|
||||
"persona": agent.persona,
|
||||
"tone": agent.tone,
|
||||
@@ -232,7 +231,6 @@ def update_agent_card(
|
||||
ai_fields = {
|
||||
"providerIntegrationId",
|
||||
"modelParams",
|
||||
"limits",
|
||||
"persona",
|
||||
"tone",
|
||||
"instructions",
|
||||
@@ -249,11 +247,8 @@ def update_agent_card(
|
||||
):
|
||||
raise ValidationError({"knowledgeIds": t("api.list_of_ids_required")})
|
||||
model_params = body.get("modelParams", agent.model_params)
|
||||
limits = body.get("limits", agent.limits)
|
||||
if not isinstance(model_params, dict):
|
||||
raise ValidationError({"modelParams": t("api.object_required")})
|
||||
if not isinstance(limits, dict):
|
||||
raise ValidationError({"limits": t("api.object_required")})
|
||||
provider_integration_id = body.get(
|
||||
"providerIntegrationId", agent.provider_integration_id
|
||||
)
|
||||
@@ -270,7 +265,6 @@ def update_agent_card(
|
||||
provider_integration_id=provider_integration_id,
|
||||
model_params=model_params,
|
||||
allowed_tools=agent.allowed_tools,
|
||||
limits=limits,
|
||||
persona=str(body.get("persona", agent.persona)),
|
||||
tone=str(body.get("tone", agent.tone)),
|
||||
instructions=str(body.get("instructions", agent.instructions)),
|
||||
|
||||
@@ -55,7 +55,7 @@ def invoke_chat(
|
||||
fallback_model = model or channel.ai_agent.model
|
||||
try:
|
||||
provider, model = _prepare_invocation(channel=channel, requested_model=model)
|
||||
limits.assert_within_limits(channel, channel.ai_agent)
|
||||
limits.assert_within_limits()
|
||||
except limits.LimitExceeded as error:
|
||||
_record_blocked(
|
||||
channel=channel,
|
||||
|
||||
@@ -21,12 +21,14 @@ def daily_cost_micros(channel=None) -> int:
|
||||
return queryset.aggregate(total=Sum("cost_micros"))["total"] or 0
|
||||
|
||||
|
||||
def assert_within_limits(channel, agent) -> None:
|
||||
def assert_within_limits() -> None:
|
||||
"""Единственный лимит расхода — общий по установке, из переменной окружения.
|
||||
|
||||
Дневного бюджета на агенте нет: он считался по прайс-таблице, где всего две
|
||||
модели, и на любой другой расход оставался нулевым — лимит не срабатывал
|
||||
никогда и давал ложное чувство защиты.
|
||||
"""
|
||||
|
||||
global_limit = settings.CHATBALLS_AI_GLOBAL_DAILY_COST_LIMIT_MICROS
|
||||
if global_limit and daily_cost_micros() >= global_limit:
|
||||
raise LimitExceeded("Global daily AI cost limit reached")
|
||||
# Канальный лимит хранится в целых центах USD (dailyCostUsd); расход учитывается
|
||||
# в micro-USD. 1 цент = 10 000 micro-USD.
|
||||
channel_limit = (agent.limits or {}).get("dailyCostUsd")
|
||||
if channel_limit and daily_cost_micros(channel) >= int(channel_limit) * 10_000:
|
||||
raise LimitExceeded("Channel daily AI cost limit reached")
|
||||
@@ -0,0 +1,18 @@
|
||||
# Дневной бюджет агента снят вместе с полем `limits`: расход считался по
|
||||
# прайс-таблице из двух моделей, а для всех остальных оставался нулевым — лимит
|
||||
# не срабатывал никогда. Единственный оставшийся предохранитель — общий лимит
|
||||
# установки из переменной окружения (CHATBALLS_AI_GLOBAL_DAILY_COST_LIMIT_MICROS).
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("ai", "0017_agent_answer_language"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="aiagent",
|
||||
name="limits",
|
||||
),
|
||||
]
|
||||
@@ -417,10 +417,6 @@ class AIAgent(TenantRelationModel):
|
||||
|
||||
allowed_tools = models.JSONField(default=list, blank=True)
|
||||
|
||||
# Единственный поддерживаемый лимит — дневной бюджет dailyCostUsd (центы USD).
|
||||
|
||||
limits = models.JSONField(default=dict, blank=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
@@ -21,7 +21,6 @@ class AgentInput:
|
||||
provider_integration_id: int | None
|
||||
model_params: dict
|
||||
allowed_tools: list
|
||||
limits: dict
|
||||
persona: str
|
||||
tone: str
|
||||
instructions: str
|
||||
@@ -39,20 +38,6 @@ class AgentCreateInput:
|
||||
knowledge_ids: list[int]
|
||||
|
||||
|
||||
# Единственный поддерживаемый лимит агента — дневной бюджет в целых центах USD
|
||||
# (dailyCostUsd). Прочие исторические ключи (dailyCostMicros, dailyBudgetRub,
|
||||
# dailyDialogs, maxMessagesPerDialog) бэкендом не используются и отбрасываются.
|
||||
def _normalize_limits(raw: dict | None) -> dict:
|
||||
if not isinstance(raw, dict):
|
||||
return {}
|
||||
value = raw.get("dailyCostUsd")
|
||||
try:
|
||||
cents = int(value)
|
||||
except (TypeError, ValueError):
|
||||
return {}
|
||||
return {"dailyCostUsd": cents} if cents > 0 else {}
|
||||
|
||||
|
||||
def knowledge_for_agent_ids(
|
||||
*, context: TenantContext, channel: Channel, knowledge_ids: list[int]
|
||||
) -> list[Knowledge]:
|
||||
@@ -146,7 +131,6 @@ def update_agent(*, context: TenantContext, agent: AIAgent, data: AgentInput) ->
|
||||
locked.provider_integration = selection.integration
|
||||
locked.model_params = data.model_params
|
||||
locked.allowed_tools = data.allowed_tools
|
||||
locked.limits = _normalize_limits(data.limits)
|
||||
locked.persona = data.persona
|
||||
locked.tone = data.tone
|
||||
locked.instructions = data.instructions
|
||||
@@ -158,7 +142,6 @@ def update_agent(*, context: TenantContext, agent: AIAgent, data: AgentInput) ->
|
||||
"provider_integration",
|
||||
"model_params",
|
||||
"allowed_tools",
|
||||
"limits",
|
||||
"persona",
|
||||
"tone",
|
||||
"instructions",
|
||||
|
||||
@@ -378,22 +378,28 @@ class ChatInvocationTests(TestCase):
|
||||
|
||||
self.assertNotIn("a@b.com", captured["messages"][0].content)
|
||||
|
||||
def test_limit_blocks_and_records(self) -> None:
|
||||
def test_global_limit_blocks_and_records(self) -> None:
|
||||
"""Единственный лимит расхода — общий по установке, из окружения.
|
||||
|
||||
Дневного бюджета на агенте больше нет: он опирался на прайс-таблицу из
|
||||
двух моделей и на всех прочих не срабатывал.
|
||||
"""
|
||||
|
||||
from django.test import override_settings
|
||||
|
||||
from chatballs.ai import limits as ai_limits
|
||||
from chatballs.ai.invocation import invoke_chat
|
||||
from chatballs.ai.models import LlmInvocation, LlmInvocationStatus
|
||||
from chatballs.ai.provider.base import ChatMessage
|
||||
|
||||
self.agent.limits = {"dailyCostUsd": 1}
|
||||
self.agent.save(update_fields=["limits"])
|
||||
# 1 цент = 10 000 micro-USD; лимит превышен расходом в 10_001 micros.
|
||||
LlmInvocation.objects.create(
|
||||
channel=self.channel, purpose="seed", operation="chat", model="x", cost_micros=10_001,
|
||||
status=LlmInvocationStatus.SUCCESS,
|
||||
)
|
||||
|
||||
with self.assertRaises(ai_limits.LimitExceeded):
|
||||
invoke_chat(channel=self.channel, messages=[ChatMessage(role="user", content="hi")], purpose="agent_chat")
|
||||
with override_settings(CHATBALLS_AI_GLOBAL_DAILY_COST_LIMIT_MICROS=10_000):
|
||||
with self.assertRaises(ai_limits.LimitExceeded):
|
||||
invoke_chat(channel=self.channel, messages=[ChatMessage(role="user", content="hi")], purpose="agent_chat")
|
||||
self.assertTrue(LlmInvocation.objects.filter(channel=self.channel, status=LlmInvocationStatus.BLOCKED).exists())
|
||||
|
||||
def test_invocation_records_used_fragment_ids(self) -> None:
|
||||
|
||||
@@ -82,7 +82,6 @@ def _ensure_agent(context: TenantContext, refs: DemoRefs, item: dict, llm: Integ
|
||||
agent.persona = item.get("persona", "")
|
||||
agent.tone = item.get("tone", "")
|
||||
agent.instructions = item.get("instructions", "")
|
||||
agent.limits = item.get("limits", {})
|
||||
if agent.status in ("ACTIVE", "DISABLED"):
|
||||
agent.provider_integration = llm
|
||||
agent.model = (llm.config or {}).get("default_model", "demo")
|
||||
|
||||
@@ -60,6 +60,8 @@ class OrganizationCreationTests(TestCase):
|
||||
self.assertEqual(created.name, "Вторая компания")
|
||||
self.assertEqual(created.language, "en")
|
||||
self.assertEqual(created.status, "ACTIVE")
|
||||
# Валюту интерфейс не спрашивает и не шлёт: сервер ставит её сам.
|
||||
self.assertEqual(created.currency, "RUB")
|
||||
membership = OrganizationMembership.objects.get(organization=created, user=self.owner)
|
||||
self.assertEqual(membership.role, EmployeeRole.OWNER)
|
||||
self.assertTrue(KnowledgeCategory.objects.filter(organization=created).exists())
|
||||
|
||||
@@ -71,13 +71,6 @@ export function OrganizationSettingsForm({
|
||||
onChange={(timezone) => onChange({ ...organization, timezone })}
|
||||
options={timezones.map((timezone) => [timezone, timezoneLabel(timezone)])}
|
||||
/>
|
||||
<SelectField
|
||||
label={t("admin.currency")}
|
||||
value={organization.currency}
|
||||
disabled={!canManage}
|
||||
onChange={(currency) => onChange({ ...organization, currency })}
|
||||
options={[["RUB", t("admin.russian_rouble_rub")]]}
|
||||
/>
|
||||
</div>
|
||||
{/* Язык — сегментом, как «Тема» в профиле и схема в «Платформе»: вариантов
|
||||
три, и выбор лучше видеть целиком, чем разворачивать список. В сетку
|
||||
|
||||
@@ -22,8 +22,6 @@ import {
|
||||
connectionStatusMeta,
|
||||
connectionSubtitle,
|
||||
createdLabel,
|
||||
dailyCostCents,
|
||||
dailyCostInput,
|
||||
deleteAgent,
|
||||
fetchAgent,
|
||||
knowledgeLine,
|
||||
@@ -534,17 +532,9 @@ function ModelCard({ card, providers, canManage, busy, apply }: {
|
||||
busy: boolean;
|
||||
apply: (patch: AgentPatch) => Promise<boolean>;
|
||||
}) {
|
||||
const [limit, setLimit] = useState(dailyCostInput(card.limits));
|
||||
useEffect(() => setLimit(dailyCostInput(card.limits)), [card.limits]);
|
||||
const missingProvider = card.providerIntegrationId === null;
|
||||
const providerName = providers.find((item) => item.id === card.providerIntegrationId)?.name ?? "";
|
||||
|
||||
function saveLimit() {
|
||||
const cents = dailyCostCents(limit);
|
||||
if (cents === Number(card.limits.dailyCostUsd ?? 0)) return;
|
||||
void apply({ limits: { ...card.limits, dailyCostUsd: cents } });
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="agent-card is-side">
|
||||
<h3>{t("common.model")}</h3>
|
||||
@@ -567,15 +557,6 @@ function ModelCard({ card, providers, canManage, busy, apply }: {
|
||||
<Icon name="search" size={14} strokeWidth={1.8} />
|
||||
</span>
|
||||
</label>
|
||||
<div className="agent-limit">
|
||||
<span>{t("ai.daily_limit")}</span>
|
||||
<span>
|
||||
{canManage
|
||||
? <input value={limit} inputMode="decimal" placeholder="0.00" onChange={(event) => setLimit(event.target.value)} onBlur={saveLimit} />
|
||||
: <span className="agent-limit-value">{limit || "0.00"}</span>}
|
||||
<small>USD</small>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -37,7 +37,6 @@ export type AgentCard = {
|
||||
model: string;
|
||||
providerIntegrationId: number | null;
|
||||
modelParams: Record<string, unknown>;
|
||||
limits: Record<string, unknown>;
|
||||
// Режим языка ответов: MIRROR, ORGANIZATION или код языка.
|
||||
answerLanguage: string;
|
||||
persona: string;
|
||||
@@ -63,7 +62,6 @@ export type AgentPatch = Partial<{
|
||||
tone: string;
|
||||
instructions: string;
|
||||
knowledgeIds: number[];
|
||||
limits: Record<string, unknown>;
|
||||
}>;
|
||||
|
||||
// Страница списка агентов (кадр G1): группа, поиск и страница — на сервере.
|
||||
@@ -255,15 +253,3 @@ export function knowledgeLine(card: AgentCard): string {
|
||||
return `${head} · ${parts.join(" + ")}`;
|
||||
}
|
||||
|
||||
// Дневной бюджет агента хранится в целых центах USD (limits.dailyCostUsd,
|
||||
// ADR-CHATBALLS-0023); на экране — доллары с двумя знаками.
|
||||
export function dailyCostInput(limits: Record<string, unknown>): string {
|
||||
const cents = Number(limits.dailyCostUsd ?? 0);
|
||||
return Number.isFinite(cents) && cents > 0 ? (cents / 100).toFixed(2) : "";
|
||||
}
|
||||
|
||||
export function dailyCostCents(value: string): number {
|
||||
const amount = Number(value.replace(",", ".").trim());
|
||||
if (!Number.isFinite(amount) || amount <= 0) return 0;
|
||||
return Math.round(amount * 100);
|
||||
}
|
||||
@@ -841,15 +841,14 @@
|
||||
.agent-field textarea:focus,
|
||||
.agent-field input:focus,
|
||||
.agent-create-modal .form-field.is-editable input:focus,
|
||||
.agent-create-modal .select-like select:focus {
|
||||
.agent-create-modal .select-box:focus-visible {
|
||||
border-color: var(--primary);
|
||||
background: var(--surface-card);
|
||||
box-shadow: 0 0 0 3px var(--primary-bg);
|
||||
}
|
||||
|
||||
/* Тот же фокус для общего селекта карточки: с боксом (группа) и без (провайдер). */
|
||||
.agent-side-fields .select-like select:focus,
|
||||
.agent-side-fields .select-box:focus-within {
|
||||
/* Тот же фокус для общего селекта карточки. */
|
||||
.agent-side-fields .select-box:focus-visible {
|
||||
border-color: var(--primary);
|
||||
background: var(--surface-card);
|
||||
box-shadow: 0 0 0 3px var(--primary-bg);
|
||||
@@ -1045,54 +1044,6 @@
|
||||
color: var(--n-5);
|
||||
}
|
||||
|
||||
.agent-limit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.agent-limit > span:first-child {
|
||||
color: var(--n-3);
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.agent-limit > span:last-child {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--n-1);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.agent-limit input,
|
||||
.agent-limit-value {
|
||||
display: inline-flex;
|
||||
width: 56px;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--n-7);
|
||||
border-radius: 7px;
|
||||
outline: none;
|
||||
background: var(--n-10);
|
||||
color: var(--n-1);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
.agent-limit input:focus {
|
||||
border-color: var(--primary);
|
||||
background: var(--surface-card);
|
||||
box-shadow: 0 0 0 3px var(--primary-bg);
|
||||
}
|
||||
|
||||
.agent-limit small {
|
||||
color: var(--n-4);
|
||||
}
|
||||
|
||||
/* Подключения. */
|
||||
|
||||
.agent-connection {
|
||||
|
||||
@@ -33,7 +33,6 @@ export function OrganizationCreatePage({ user, onCreated, onBack }: {
|
||||
// Новая организация наследует региональные параметры текущей: чаще всего
|
||||
// человек заводит вторую компанию там же, где первую.
|
||||
timezone: "Europe/Moscow",
|
||||
currency: "RUB",
|
||||
language: "",
|
||||
});
|
||||
const [logo, setLogo] = useState<File | null>(null);
|
||||
@@ -123,12 +122,6 @@ export function OrganizationCreatePage({ user, onCreated, onBack }: {
|
||||
onChange={(timezone) => setDraft({ ...draft, timezone })}
|
||||
options={timezones.map((timezone) => [timezone, timezoneLabel(timezone)])}
|
||||
/>
|
||||
<SelectField
|
||||
label={t("admin.currency")}
|
||||
value={draft.currency}
|
||||
onChange={(currency) => setDraft({ ...draft, currency })}
|
||||
options={[["RUB", t("admin.russian_rouble_rub")]]}
|
||||
/>
|
||||
</div>
|
||||
<div className="appearance-row administration-language">
|
||||
<span>{t("settings.language")}</span>
|
||||
|
||||
@@ -8,13 +8,13 @@ const BASE = "/api/v1/organizations/";
|
||||
export type OrganizationCreateOptions = {
|
||||
timezones: string[];
|
||||
languages: Array<{ code: string; label: string }>;
|
||||
currencies: string[];
|
||||
};
|
||||
|
||||
// Валюты в черновике нет: в продукте нет ни одной суммы в валюте организации,
|
||||
// сервер проставляет её сам.
|
||||
export type OrganizationDraft = {
|
||||
name: string;
|
||||
timezone: string;
|
||||
currency: string;
|
||||
// Пустая строка — «как в установке».
|
||||
language: string;
|
||||
};
|
||||
|
||||
@@ -308,7 +308,6 @@ export const en: Record<MessageKey, Message> = {
|
||||
"ai.created_on": "created {date}",
|
||||
"ai.creating": "Creating…",
|
||||
"ai.customers_reach_agent_through_these": "Customers reach the agent through these. One connection, one agent.",
|
||||
"ai.daily_limit": "Daily limit",
|
||||
"ai.delete_agent": "Delete the agent",
|
||||
"ai.delete_agent_2": "Delete the agent?",
|
||||
"ai.delete_attachment": "Delete the attachment",
|
||||
@@ -1063,24 +1062,24 @@ export const en: Record<MessageKey, Message> = {
|
||||
"onboarding.s2.name": "AI agent",
|
||||
"onboarding.s2.time": "4 min",
|
||||
"onboarding.s2.title": "Create an AI agent",
|
||||
"onboarding.s2.lead": "The agent is who talks to the customer: who they are, how they answer, what rules they follow and how much they may spend per day.",
|
||||
"onboarding.s2.lead": "The agent is who talks to the customer: who they are, how they answer and what rules they follow.",
|
||||
"onboarding.s2.path": "Agents → Create agent",
|
||||
"onboarding.s2.do_1": "Open Agents in the sidebar and click Create agent.",
|
||||
"onboarding.s2.do_2": "Describe the role and tone: who they are to the customer, what they may promise, what they must not do.",
|
||||
"onboarding.s2.do_3": "Pick a model from the connected provider and a daily budget in dollars.",
|
||||
"onboarding.s2.do_3": "Pick a model from the connected provider.",
|
||||
"onboarding.s2.do_4": "Attach knowledge base articles — the agent answers only from those.",
|
||||
"onboarding.s2.do_5": "Switch the status from Draft to Active, otherwise the agent stays silent.",
|
||||
"onboarding.s2.warning": "If there is nothing to answer from, the agent says so honestly and offers to call a human — it will not make things up.",
|
||||
"onboarding.s2.preview": "Agents → Consultant",
|
||||
"onboarding.s2.row1_label": "Model",
|
||||
"onboarding.s2.row1_value": "openai/gpt-4o-mini",
|
||||
"onboarding.s2.row2_label": "Daily budget",
|
||||
"onboarding.s2.row2_value": "$5.00",
|
||||
"onboarding.s2.row3_label": "Knowledge",
|
||||
"onboarding.s2.row3_value": "12 articles",
|
||||
"onboarding.s2.row2_label": "Knowledge",
|
||||
"onboarding.s2.row2_value": "12 articles",
|
||||
"onboarding.s2.row3_label": "Answer language",
|
||||
"onboarding.s2.row3_value": "Same as the customer",
|
||||
"onboarding.s2.chip": "Active",
|
||||
"onboarding.s2.tour_title": "The Agents section",
|
||||
"onboarding.s2.tour_text": "The organization's agents. An agent card is its role, its model, its budget and the knowledge attached to it.",
|
||||
"onboarding.s2.tour_text": "The organization's agents. An agent card is its role, its model and the knowledge attached to it.",
|
||||
"onboarding.s3.name": "Knowledge base",
|
||||
"onboarding.s3.time": "from 10 min",
|
||||
"onboarding.s3.title": "Fill the knowledge base",
|
||||
|
||||
@@ -309,7 +309,6 @@ export const ru = {
|
||||
"ai.created_on": "создан {date}",
|
||||
"ai.creating": "Создание…",
|
||||
"ai.customers_reach_agent_through_these": "Через них клиенты попадают к агенту. Одно подключение — один агент.",
|
||||
"ai.daily_limit": "Лимит в день",
|
||||
"ai.delete_agent": "Удалить агента",
|
||||
"ai.delete_agent_2": "Удалить агента?",
|
||||
"ai.delete_attachment": "Удалить вложение",
|
||||
@@ -1064,24 +1063,24 @@ export const ru = {
|
||||
"onboarding.s2.name": "AI-агент",
|
||||
"onboarding.s2.time": "4 мин",
|
||||
"onboarding.s2.title": "Создайте AI-агента",
|
||||
"onboarding.s2.lead": "Агент — тот, кто говорит с клиентом: кто он, как отвечает, каким правилам подчиняется и сколько может потратить в день.",
|
||||
"onboarding.s2.lead": "Агент — тот, кто говорит с клиентом: кто он, как отвечает и каким правилам подчиняется.",
|
||||
"onboarding.s2.path": "Агенты → Создать агента",
|
||||
"onboarding.s2.do_1": "Откройте «Агенты» в сайдбаре и нажмите «Создать агента».",
|
||||
"onboarding.s2.do_2": "Опишите роль и тон: кто он для клиента, что можно обещать, чего делать нельзя.",
|
||||
"onboarding.s2.do_3": "Выберите модель подключённого провайдера и дневной бюджет в долларах.",
|
||||
"onboarding.s2.do_3": "Выберите модель подключённого провайдера.",
|
||||
"onboarding.s2.do_4": "Прикрепите статьи базы знаний — агент отвечает только из них.",
|
||||
"onboarding.s2.do_5": "Переведите статус из «Черновик» в «Активен», иначе агент молчит.",
|
||||
"onboarding.s2.warning": "Если данных для ответа нет, агент честно это скажет и предложит позвать сотрудника — придумывать он не станет.",
|
||||
"onboarding.s2.preview": "Агенты → Консультант",
|
||||
"onboarding.s2.row1_label": "Модель",
|
||||
"onboarding.s2.row1_value": "openai/gpt-4o-mini",
|
||||
"onboarding.s2.row2_label": "Дневной бюджет",
|
||||
"onboarding.s2.row2_value": "$5.00",
|
||||
"onboarding.s2.row3_label": "Знания",
|
||||
"onboarding.s2.row3_value": "12 статей",
|
||||
"onboarding.s2.row2_label": "Знания",
|
||||
"onboarding.s2.row2_value": "12 статей",
|
||||
"onboarding.s2.row3_label": "Язык ответов",
|
||||
"onboarding.s2.row3_value": "Как у клиента",
|
||||
"onboarding.s2.chip": "Активен",
|
||||
"onboarding.s2.tour_title": "Раздел «Агенты»",
|
||||
"onboarding.s2.tour_text": "Список агентов организации. Карточка агента — это его роль, модель, бюджет и прикреплённые знания.",
|
||||
"onboarding.s2.tour_text": "Список агентов организации. Карточка агента — это его роль, модель и прикреплённые знания.",
|
||||
"onboarding.s3.name": "База знаний",
|
||||
"onboarding.s3.time": "от 10 мин",
|
||||
"onboarding.s3.title": "Наполните базу знаний",
|
||||
|
||||
Reference in new issue
Block a user