Switch prompt test to writer
This commit is contained in:
+39
-24
@@ -922,6 +922,7 @@ async def prompt_test_posts(selected_post_id: int | None = None) -> tuple[list[d
|
|||||||
"""
|
"""
|
||||||
SELECT rp.id, rp.raw_text, rp.original_url, rp.posted_at, rp.created_at, rp.qualification_status,
|
SELECT rp.id, rp.raw_text, rp.original_url, rp.posted_at, rp.created_at, rp.qualification_status,
|
||||||
rp.qualification_score, s.name AS source_name, s.tag AS source_tag,
|
rp.qualification_score, s.name AS source_name, s.tag AS source_tag,
|
||||||
|
rp.qualification_reason,
|
||||||
(SELECT COUNT(*) FROM raw_post_media m WHERE m.raw_post_id=rp.id) AS media_count,
|
(SELECT COUNT(*) FROM raw_post_media m WHERE m.raw_post_id=rp.id) AS media_count,
|
||||||
(SELECT ARRAY_AGG(DISTINCT m.media_type ORDER BY m.media_type)
|
(SELECT ARRAY_AGG(DISTINCT m.media_type ORDER BY m.media_type)
|
||||||
FROM raw_post_media m WHERE m.raw_post_id=rp.id) AS media_types
|
FROM raw_post_media m WHERE m.raw_post_id=rp.id) AS media_types
|
||||||
@@ -946,6 +947,7 @@ async def prompt_test_posts(selected_post_id: int | None = None) -> tuple[list[d
|
|||||||
"""
|
"""
|
||||||
SELECT rp.id, rp.raw_text, rp.original_url, rp.posted_at, rp.created_at, rp.qualification_status,
|
SELECT rp.id, rp.raw_text, rp.original_url, rp.posted_at, rp.created_at, rp.qualification_status,
|
||||||
rp.qualification_score, s.name AS source_name, s.tag AS source_tag,
|
rp.qualification_score, s.name AS source_name, s.tag AS source_tag,
|
||||||
|
rp.qualification_reason,
|
||||||
(SELECT COUNT(*) FROM raw_post_media m WHERE m.raw_post_id=rp.id) AS media_count,
|
(SELECT COUNT(*) FROM raw_post_media m WHERE m.raw_post_id=rp.id) AS media_count,
|
||||||
(SELECT ARRAY_AGG(DISTINCT m.media_type ORDER BY m.media_type)
|
(SELECT ARRAY_AGG(DISTINCT m.media_type ORDER BY m.media_type)
|
||||||
FROM raw_post_media m WHERE m.raw_post_id=rp.id) AS media_types
|
FROM raw_post_media m WHERE m.raw_post_id=rp.id) AS media_types
|
||||||
@@ -968,17 +970,24 @@ async def prompt_test_posts(selected_post_id: int | None = None) -> tuple[list[d
|
|||||||
return posts, selected
|
return posts, selected
|
||||||
|
|
||||||
|
|
||||||
def prompt_test_payload(post: dict[str, Any], max_text_chars: int) -> list[dict[str, Any]]:
|
def prompt_test_payload(post: dict[str, Any], max_text_chars: int) -> dict[str, Any]:
|
||||||
return [
|
return {
|
||||||
{
|
"task": "rewrite_accepted_posts",
|
||||||
"id": int(post["id"]),
|
"categories": [],
|
||||||
"source": post.get("source_name") or "",
|
"posts": [
|
||||||
"original_url": post.get("original_url") or "",
|
{
|
||||||
"media_count": int(post.get("media_count") or 0),
|
"id": int(post["id"]),
|
||||||
"media_types": list(post.get("media_types") or []),
|
"producer_name": post.get("source_name") or "",
|
||||||
"text": str(post.get("raw_text") or "")[:max_text_chars],
|
"producer_tag": normalize_hash_tag(post.get("source_tag") or post.get("source_name") or "", "source"),
|
||||||
}
|
"original_url": post.get("original_url") or "",
|
||||||
]
|
"qualification_score": post.get("qualification_score"),
|
||||||
|
"qualification_reason": post.get("qualification_reason") or "",
|
||||||
|
"media_count": int(post.get("media_count") or 0),
|
||||||
|
"media_types": list(post.get("media_types") or []),
|
||||||
|
"text": str(post.get("raw_text") or "")[:max_text_chars],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def setting_value(settings_rows: list[dict], key: str, default: Any = None) -> Any:
|
def setting_value(settings_rows: list[dict], key: str, default: Any = None) -> Any:
|
||||||
@@ -2268,11 +2277,14 @@ async def prompt_test(request: Request, post_id: int | None = None):
|
|||||||
if not user:
|
if not user:
|
||||||
return redirect("/login")
|
return redirect("/login")
|
||||||
posts, selected = await prompt_test_posts(post_id)
|
posts, selected = await prompt_test_posts(post_id)
|
||||||
max_text_chars = max(100, int(await fetch_setting("ai_qualifier_max_text_chars", 2000) or 2000))
|
categories = await writer_categories()
|
||||||
prompt = str(await fetch_setting("ai_qualifier_prompt", "") or "")
|
max_text_chars = max(200, int(await fetch_setting("ai_writer_max_text_chars", 3500) or 3500))
|
||||||
provider = str(await fetch_setting("ai_qualifier_provider", "openrouter") or "openrouter")
|
prompt = str(await fetch_setting("ai_writer_prompt", "") or "")
|
||||||
model = str(await fetch_setting("ai_qualifier_model", "") or "")
|
provider = str(await fetch_setting("ai_writer_provider", "anthropic") or "anthropic")
|
||||||
|
model = str(await fetch_setting("ai_writer_model", "") or "")
|
||||||
payload = prompt_test_payload(selected, max_text_chars) if selected else []
|
payload = prompt_test_payload(selected, max_text_chars) if selected else []
|
||||||
|
if payload:
|
||||||
|
payload["categories"] = categories
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"prompt_test.html",
|
"prompt_test.html",
|
||||||
base_context(
|
base_context(
|
||||||
@@ -2303,21 +2315,24 @@ async def prompt_test_run(
|
|||||||
return redirect("/login")
|
return redirect("/login")
|
||||||
require_csrf(user, csrf_token)
|
require_csrf(user, csrf_token)
|
||||||
posts, selected = await prompt_test_posts(post_id)
|
posts, selected = await prompt_test_posts(post_id)
|
||||||
max_text_chars = max(100, int(await fetch_setting("ai_qualifier_max_text_chars", 2000) or 2000))
|
categories = await writer_categories()
|
||||||
provider = str(await fetch_setting("ai_qualifier_provider", "openrouter") or "openrouter")
|
max_text_chars = max(200, int(await fetch_setting("ai_writer_max_text_chars", 3500) or 3500))
|
||||||
model = str(await fetch_setting("ai_qualifier_model", "") or "").strip()
|
provider = str(await fetch_setting("ai_writer_provider", "anthropic") or "anthropic")
|
||||||
api_key = str(await fetch_setting("ai_qualifier_api_key", "") or "").strip()
|
model = str(await fetch_setting("ai_writer_model", "") or "").strip()
|
||||||
api_base = str(await fetch_setting("ai_qualifier_api_base", "") or "").strip()
|
api_key = str(await fetch_setting("ai_writer_api_key", "") or "").strip()
|
||||||
temperature = max(0.0, float(await fetch_setting("ai_qualifier_temperature", 0.0) or 0.0))
|
api_base = str(await fetch_setting("ai_writer_api_base", "") or "").strip()
|
||||||
timeout = max(10, int(await fetch_setting("ai_qualifier_timeout_sec", 120) or 120))
|
temperature = max(0.0, float(await fetch_setting("ai_writer_temperature", 0.4) or 0.4))
|
||||||
|
timeout = max(10, int(await fetch_setting("ai_writer_timeout_sec", 180) or 180))
|
||||||
normalized_prompt = prompt.replace("\\r\\n", "\n").replace("\\n", "\n").strip()
|
normalized_prompt = prompt.replace("\\r\\n", "\n").replace("\\n", "\n").strip()
|
||||||
payload = prompt_test_payload(selected, max_text_chars) if selected else []
|
payload = prompt_test_payload(selected, max_text_chars) if selected else []
|
||||||
|
if payload:
|
||||||
|
payload["categories"] = categories
|
||||||
result = None
|
result = None
|
||||||
error = None
|
error = None
|
||||||
if not selected:
|
if not selected:
|
||||||
error = "Пост не найден."
|
error = "Пост не найден."
|
||||||
elif not model or not api_key:
|
elif not model or not api_key:
|
||||||
error = "Не заполнены модель или API-ключ квалификатора в настройках."
|
error = "Не заполнены модель или API-ключ райтера в настройках."
|
||||||
elif not normalized_prompt:
|
elif not normalized_prompt:
|
||||||
error = "Промпт пустой."
|
error = "Промпт пустой."
|
||||||
else:
|
else:
|
||||||
@@ -2355,7 +2370,7 @@ async def prompt_test_run(
|
|||||||
"usage": response_usage(response),
|
"usage": response_usage(response),
|
||||||
"model": kwargs["model"],
|
"model": kwargs["model"],
|
||||||
}
|
}
|
||||||
await audit(user["id"], "prompt_test.run", "raw_post", int(selected["id"]), {"provider": provider, "model": model})
|
await audit(user["id"], "writer_prompt_test.run", "raw_post", int(selected["id"]), {"provider": provider, "model": model})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception("Prompt test failed")
|
logger.exception("Prompt test failed")
|
||||||
error = str(exc)
|
error = str(exc)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="page-head">
|
<div class="page-head">
|
||||||
<div>
|
<div>
|
||||||
<h1>Тест промптов</h1>
|
<h1>Тест промпта райтера</h1>
|
||||||
<p class="muted">Песочница для свободной части <span class="mono">ai_qualifier_prompt</span>. Посты и оценки в БД не меняются.</p>
|
<p class="muted">Песочница для свободной части <span class="mono">ai_writer_prompt</span>. Посты, рерайт и статусы в БД не меняются.</p>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn" href="/workers">Настройки AI</a>
|
<a class="btn" href="/workers">Настройки AI</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,19 +13,19 @@
|
|||||||
<div class="hint-grid">
|
<div class="hint-grid">
|
||||||
<div>
|
<div>
|
||||||
<div class="hint-label">Что тестируем</div>
|
<div class="hint-label">Что тестируем</div>
|
||||||
<p>Отправляется только текст из поля ниже как system prompt. <span class="mono">ai_qualifier_contract</span> специально не добавляется, чтобы можно было быстро проверять идеи, тон и критерии.</p>
|
<p>Отправляется только текст из поля ниже как system prompt. <span class="mono">ai_writer_contract</span> специально не добавляется, чтобы можно было быстро проверять стиль, структуру и редакторские правила.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="hint-label">Что уйдёт в модель</div>
|
<div class="hint-label">Что уйдёт в модель</div>
|
||||||
<p>Один выбранный raw-пост в том же формате, что использует квалификатор: <span class="mono">id</span>, источник, ссылка, количество медиа, типы медиа и обрезанный текст.</p>
|
<p>Один выбранный raw-пост в том же формате, что использует райтер: <span class="mono">categories</span>, <span class="mono">producer_name</span>, <span class="mono">producer_tag</span>, ссылка, AI-оценка, медиа и обрезанный исходный текст.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="hint-label">Что безопасно менять</div>
|
<div class="hint-label">Что безопасно менять</div>
|
||||||
<p>Роль, критерии, примеры хороших/плохих постов, шкалу оценки и формулировки причин. Для рабочего воркера JSON-контракт всё равно живёт отдельно.</p>
|
<p>Голос канала, структуру текста, длину, правила про эмоджи, запрет выдумок, примеры формулировок и логику упоминания производителя/магазина. JSON-контракт рабочего воркера живёт отдельно.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="hint-label">Что не происходит</div>
|
<div class="hint-label">Что не происходит</div>
|
||||||
<p>Статус поста не меняется, batch не создаётся, оценка не сохраняется. Тест только тратит токены текущей модели квалификатора.</p>
|
<p>Рерайт не сохраняется, batch не создаётся, категория и статус поста не меняются. Тест только тратит токены текущей модели райтера.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
@@ -54,13 +54,13 @@
|
|||||||
<strong>#{{ selected.id }} · {{ selected.source_name }}</strong>
|
<strong>#{{ selected.id }} · {{ selected.source_name }}</strong>
|
||||||
<a href="{{ selected.original_url }}" target="_blank" rel="noopener">Оригинал</a>
|
<a href="{{ selected.original_url }}" target="_blank" rel="noopener">Оригинал</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="muted">{{ selected.posted_at_fmt or selected.created_at_fmt }} · {{ selected.media_count }} медиа · {{ selected.media_types or [] }}</div>
|
<div class="muted">{{ selected.posted_at_fmt or selected.created_at_fmt }} · {{ selected.media_count }} медиа · {{ selected.media_types or [] }} · оценка {{ selected.qualification_score or "—" }}</div>
|
||||||
<pre>{{ selected.raw_text }}</pre>
|
<pre>{{ selected.raw_text }}</pre>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<span>ai_qualifier_prompt для теста</span>
|
<span>ai_writer_prompt для теста</span>
|
||||||
<textarea name="prompt" rows="18" spellcheck="false">{{ prompt }}</textarea>
|
<textarea name="prompt" rows="18" spellcheck="false">{{ prompt }}</textarea>
|
||||||
</label>
|
</label>
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<aside class="prompt-test-side">
|
<aside class="prompt-test-side">
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h2>Payload</h2>
|
<h2>Payload</h2>
|
||||||
<p class="muted">Именно это отправится user-сообщением. Текст обрезан по настройке <span class="mono">ai_qualifier_max_text_chars={{ max_text_chars }}</span>.</p>
|
<p class="muted">Именно это отправится user-сообщением. Текст обрезан по настройке <span class="mono">ai_writer_max_text_chars={{ max_text_chars }}</span>.</p>
|
||||||
<pre class="json-box">{{ payload_json }}</pre>
|
<pre class="json-box">{{ payload_json }}</pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user