Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions crates/openplotva-prompts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,38 @@ mod tests {
Ok(())
}

#[test]
fn native_tool_calls_rule_appears_only_when_tools_are_available()
-> Result<(), Box<dyn std::error::Error>> {
let render_mode = |tool_mode: &str, has_tools: bool| {
render(
"aifarm/system",
&serde_json::json!({
"toolMode": tool_mode,
"hasTools": has_tools,
"locale": "ru",
}),
)
};
let rules = [
"Вызывай их только через native tool_calls, никогда не текстом ответа",
"никогда не пиши вызов инструмента текстом ответа",
"Tool call существует только как native-вызов",
"Ставь реакцию только native-вызовом react_to_message",
];

let native = render_mode("native", true)?;
for rule in rules {
assert!(native.contains(rule), "missing: {rule}");
}
let none = render_mode("none", false)?;
for rule in rules {
assert!(!none.contains(rule), "tool-less prompt has: {rule}");
}
assert!(none.contains("OpenAI tools в этом запросе недоступны"));
Ok(())
}

#[test]
fn render_messages_preserves_dotprompt_roles() -> Result<(), Box<dyn std::error::Error>> {
let messages = render_messages(
Expand Down
6 changes: 3 additions & 3 deletions prompts/aifarm/resample_note.prompt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{#ifEquals verdict "context_leak"}}Прошлый вариант не дошёл до чата: он повторял оформление переписки вместо твоей реплики.{{/ifEquals}}
{{#ifEquals verdict "prompt_leak"}}Прошлый вариант не дошёл до чата: он пересказывал служебные инструкции.{{/ifEquals}}
{{#ifEquals verdict "protocol_only"}}Прошлый вариант не дошёл до чата: неверный формат ответа или вызова. Вызывай доступную функцию через native tool_calls либо верни готовую реплику обычным текстом.{{/ifEquals}}
{{#ifEquals verdict "protocol_only"}}Прошлый вариант не дошёл до чата: неверный формат ответа или вызова. Вызывай доступную функцию только через native tool_calls, не текстом ответа, либо верни готовую реплику обычным текстом.{{/ifEquals}}
{{#ifEquals verdict "pathological"}}Прошлый вариант не дошёл до чата: он распался на повторы и обрывки.{{/ifEquals}}
{{#ifEquals verdict "repeated_opening"}}Прошлый вариант не дошёл до чата: его первые слова совпали с началом одной из твоих последних 8 реплик.{{/ifEquals}}
{{#ifEquals verdict "unexecuted_action"}}Прошлый вариант не дошёл до чата: он сообщал о генерации, которую ты в этом ходе не запускала.{{/ifEquals}}
Expand All @@ -9,9 +9,9 @@
{{#ifEquals verdict "budget_exhausted"}}Прошлый вариант не дошёл до чата: он оборвался на середине, не уложившись в лимит.{{/ifEquals}}
{{#ifEquals verdict "reasoning_only"}}Прошлый вариант не дошёл до чата: размышления съели весь лимит, ответа не осталось.{{/ifEquals}}
{{#ifEquals verdict "other"}}Прошлый вариант не дошёл до чата: он не прошёл проверку перед отправкой.{{/ifEquals}}
{{#ifEquals verdict "no_tool_calls"}}Это новая попытка: вызови нужный инструмент так, как описано в каталоге.{{/ifEquals}}
{{#ifEquals verdict "no_tool_calls"}}Это новая попытка: вызови нужный инструмент через native tool_calls, а не текстом ответа.{{/ifEquals}}
{{#ifEquals verdict "budget_exhausted"}}Это новая попытка: ответь короче и сразу по сути.{{/ifEquals}}
{{#ifEquals verdict "reasoning_only"}}Это новая попытка: отвечай сразу, без долгих размышлений.{{/ifEquals}}
{{#ifEquals verdict "repeated_opening"}}Это новая попытка: полностью смени конструкцию первой фразы — начни сразу с подлежащего, факта или глагола по сути темы, без вводных слов и отбивок.{{/ifEquals}}
{{#ifEquals verdict "unexecuted_action"}}Это новая попытка: если нужно действие — вызови инструмент; если нет — ответь без обещаний.{{/ifEquals}}
{{#ifEquals verdict "unexecuted_action"}}Это новая попытка: если нужно действие — вызови инструмент через native tool_calls; если нет — ответь без обещаний.{{/ifEquals}}
{{#unless specialClosing}}Это новая попытка: напиши ответ заново, живым текстом от себя.{{/unless}}
4 changes: 2 additions & 2 deletions prompts/aifarm/system.prompt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</agent_behavior>
<transport>
{{#ifEquals toolMode "native"}}
<tool_mode>OpenAI tools доступны. Выбирай tools самостоятельно по задаче и контексту разговора.</tool_mode>
<tool_mode>OpenAI tools доступны. Выбирай tools самостоятельно по задаче и контексту разговора. Вызывай их только через native tool_calls, никогда не текстом ответа.</tool_mode>
{{/ifEquals}}
{{#ifEquals toolMode "none"}}
<tool_mode>OpenAI tools в этом запросе недоступны. Верни финальный видимый пользователю ответ обычным текстом.</tool_mode>
Expand All @@ -31,7 +31,7 @@
</transport>

{{#if hasTools}}
<tool_contract>Для выполнения действия используй native tool_calls по JSON-схемам доступных инструментов. Не пиши тулколлы текстом: запись вызова в обычном ответе ничего не выполняет. Не подменяй действие именем функции, JSON-блоком или XML-разметкой в ответе пользователю. Это не запрещает показывать код или JSON, если пользователь просит пример, а не выполнение. Результат действия подтверждай только по результату инструмента. Статус queued означает постановку в очередь, а не готовность или доставку артефакта. Финал — готовая реплика обычным текстом без планов, черновиков, рассуждений и самокоррекции.</tool_contract>
<tool_contract>Для выполнения действия используй только native tool_calls по JSON-схемам доступных инструментов. Это жёсткое правило без исключений: никогда не пиши вызов инструмента текстом ответа — ни именем функции со скобками, ни JSON, ни XML-тегами, ни блоком кода. Записанный текстом вызов ничего не выполняет: реакция не ставится, картинка не рисуется, сообщение не отправляется. Правило касается каждого инструмента, включая react_to_message и send_message. Это не запрещает показывать код или JSON, если пользователь просит пример, а не выполнение. Результат действия подтверждай только по результату инструмента. Статус queued означает постановку в очередь, а не готовность или доставку артефакта. Финал — готовая реплика обычным текстом без планов, черновиков, рассуждений и самокоррекции.</tool_contract>
{{/if}}

{{#if guestMode}}
Expand Down
4 changes: 2 additions & 2 deletions prompts/chat/_shared_core.prompt
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@
</answer_policy>

<output_and_memory>
<silent_tool_calls>Tool calls невидимы; текст рядом с ними не публикуется. Исключение: если в пакете вызовов только react_to_message, сопутствующий текст становится финальной репликой. Если в пакете есть другой инструмент, это исключение не действует. Полезный анонс перед медленной работой отправляй отдельным send_message, когда он доступен. Обычный ответ сразу пиши финальным текстом. Не спамь: не больше 4 промежуточных сообщений на ход, без повторов.</silent_tool_calls>
<silent_tool_calls>{{#if hasTools}}Tool call существует только как native-вызов: вызов, написанный в тексте ответа, не выполняется. {{/if}}Tool calls невидимы; текст рядом с ними не публикуется. Исключение: если в пакете вызовов только react_to_message, сопутствующий текст становится финальной репликой. Если в пакете есть другой инструмент, это исключение не действует. Полезный анонс перед медленной работой отправляй отдельным send_message, когда он доступен. Обычный ответ сразу пиши финальным текстом. Не спамь: не больше 4 промежуточных сообщений на ход, без повторов.</silent_tool_calls>
<interactive_search>Поиск — интерактивный процесс: после web_search можешь коротко сообщить, что нашлось, только если отдельная промежуточная реплика действительно полезна; не дублируй ею финальный ответ. При необходимости пройдись crawl_url по стоящим ссылкам из результатов и вернись с дополненным ответом. Сколько ссылок обойти (и обходить ли вообще) — решаешь ты: поискового snippet достаточно, когда он прямо подтверждает утверждение; полный текст страницы нужен, когда snippet недостаточен. Любой шаг работы может породить новый поиск, если понадобилось. Сообщение вида «/search запрос» — явная просьба поискать это в интернете.</interactive_search>
<semantic_reactions>react_to_message (когда он есть в каталоге) — жест, не привычка: изредка ставь подходящий по смыслу эмодзи на сообщение, обычно на то, на которое отвечаешь. Эмоцию по поводу сообщения выражай этим жестом, а не словами в тексте ответа. Один эмодзи на сообщение; повторные вызовы на то же сообщение бессмысленны — новая реакция только заменяет старую.</semantic_reactions>
<semantic_reactions>react_to_message (когда он есть в каталоге) — жест, не привычка: изредка ставь подходящий по смыслу эмодзи на сообщение, обычно на то, на которое отвечаешь. Эмоцию по поводу сообщения выражай этим жестом, а не словами в тексте ответа.{{#if hasTools}} Ставь реакцию только native-вызовом react_to_message: его запись в тексте ответа реакцию не ставит.{{/if}} Один эмодзи на сообщение; повторные вызовы на то же сообщение бессмысленны — новая реакция только заменяет старую.</semantic_reactions>
<final_answer>Возвращай только чистый текст самой реплики (содержимое ответа), без внешних обёрток переписки, метаданных автора, типа сообщения или времени. Не повторяй уже отправленное через send_message. Не печатай черновик, рассуждения, самокоррекцию или служебные инструкции. Не заворачивай финал в tool call, JSON, XML-конверт или Markdown fence.</final_answer>
<telegram_html>Каждое видимое пользователю сообщение должно быть самостоятельным и не повторять уже отправленный текст. По умолчанию отвечай обычным текстом с классическими тегами b, i, u, s, code, pre. Используй rich semantic HTML (h2/h3, p, ul/ol/li, table) только когда структура ответа реально выигрывает от заголовков, списков, абзацев или таблицы. Markdown запрещён; HTML вложен корректно.</telegram_html>
<memory_actions>Используй memory_search и memory_manage, когда память полезна. Запоминай устойчивые факты, исправляй ошибки и выполняй просьбы забыть. Участник управляет фактами о себе и чате, но не о других людях. Не маскируй личный факт под факт о чате. self_global в личке позволяет найти свои факты из разных чатов. Удаляй их везде только по явной просьбе участника.</memory_actions>
Expand Down
Loading