feat(S2): max_tokens dynamic from template, remove hardcoded 500

- PromptTemplate model: add max_tokens (default 500) and example_input fields
- generate_with_llm: read max_tokens from template, accept override param
- api_prompt_generate: accept optional max_tokens with 100-4096 clamp
- Vue: add advanced options toggle with max_tokens input
- Vue: auto-populate max_tokens from selected template

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
renjianbo
2026-05-03 09:46:12 +08:00
parent ba73f8395a
commit eb5056b5f2
5 changed files with 58 additions and 13 deletions

View File

@@ -81,6 +81,8 @@ class PromptTemplate(db.Model):
sub_category = db.Column(db.String(50))
system_prompt = db.Column(db.Text, nullable=False)
is_default = db.Column(db.Boolean, default=False)
max_tokens = db.Column(db.Integer, default=500)
example_input = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
class MealPlan(db.Model):

View File

@@ -49,20 +49,28 @@ def get_system_prompt(template_id=None):
请直接返回优化后的提示词,不要添加任何解释或其他内容。"""
def generate_with_llm(input_text, template_id=None, max_retries=3):
def generate_with_llm(input_text, template_id=None, max_retries=3, max_tokens=None):
"""调用大模型API生成提示词带重试机制"""
import time
system_prompt = get_system_prompt(template_id)
start_time = time.time() # 记录开始时间
# 确定 max_tokens: 参数传入 > 模板配置 > 默认 500
_max_tokens = max_tokens or 500
if not max_tokens and template_id:
template = PromptTemplate.query.get(template_id)
if template and template.max_tokens:
_max_tokens = template.max_tokens
# 记录参数到日志
current_app.logger.info("=== API 调用参数 ===")
current_app.logger.info(f"模板ID: {template_id}")
current_app.logger.info(f"输入文本: {input_text}")
current_app.logger.info(f"系统提示: {system_prompt}")
current_app.logger.info(f"max_tokens: {_max_tokens}")
current_app.logger.info("==================")
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
@@ -72,7 +80,7 @@ def generate_with_llm(input_text, template_id=None, max_retries=3):
{"role": "user", "content": input_text}
],
temperature=0.7,
max_tokens=500,
max_tokens=_max_tokens,
timeout=60 # 设置60秒超时
)
@@ -342,6 +350,7 @@ def get_templates_by_category(category):
'profession': template.profession,
'sub_category': template.sub_category,
'system_prompt': template.system_prompt,
'max_tokens': template.max_tokens or 500,
'sample_input': build_sample_input_text(template),
}
template_list.append(template_dict)
@@ -401,8 +410,18 @@ def api_prompt_generate():
if len(input_text) > 1000:
return jsonify({'success': False, 'message': '需求描述请勿超过 1000 字'}), 400
# 可选 max_tokens 覆盖
_mt = data.get('max_tokens')
_max_tokens = None
if _mt is not None and _mt != '':
try:
_max_tokens = int(_mt)
_max_tokens = max(100, min(4096, _max_tokens))
except (TypeError, ValueError):
pass
try:
generated_text = generate_with_llm(input_text, template_id)
generated_text = generate_with_llm(input_text, template_id, max_tokens=_max_tokens)
if not generated_text or (
isinstance(generated_text, str) and generated_text.startswith('提示词生成失败')
):

View File

@@ -14,6 +14,6 @@ export function fetchTemplatesByCategory(category: string) {
return client.get<TemplatesByCategoryResponse>(path).then((r) => r.data)
}
export function generatePrompt(body: { input_text: string; template_id: number | null }) {
export function generatePrompt(body: { input_text: string; template_id: number | null; max_tokens?: number }) {
return client.post<GeneratePromptResponse>('/api/prompt/generate', body).then((r) => r.data)
}

View File

@@ -7,6 +7,7 @@ export interface PromptTemplateItem {
profession: string | null
sub_category: string | null
system_prompt: string
max_tokens: number
sample_input: string
}

View File

@@ -26,6 +26,8 @@ const selectedTemplateId = ref<number | undefined>(undefined)
const inputText = ref('')
const autoFillSample = ref(true)
const showAdvanced = ref(false)
const maxTokensOverride = ref<number | undefined>(undefined)
const result = ref<{ id: number; input_text: string; generated_text: string } | null>(null)
@@ -100,9 +102,11 @@ watch(filteredTemplates, (list) => {
})
watch(selectedTemplateId, (id) => {
if (!autoFillSample.value || id == null) return
const t = templates.value.find((x) => x.id === id)
if (t?.sample_input) inputText.value = t.sample_input
if (id != null) {
const t = templates.value.find((x) => x.id === id)
if (autoFillSample.value && t?.sample_input) inputText.value = t.sample_input
maxTokensOverride.value = t?.max_tokens || undefined
}
})
async function loadMeta() {
@@ -201,6 +205,7 @@ async function onSubmit() {
const res = await generatePrompt({
input_text: text,
template_id: selectedTemplateId.value,
max_tokens: maxTokensOverride.value,
})
if (!res.success) {
ElMessage.error(res.message || '生成失败')
@@ -356,9 +361,27 @@ onMounted(async () => {
placeholder="请描述目标、背景、约束与期望输出…"
/>
<div class="actions">
<el-button type="primary" size="large" :loading="generating" @click="onSubmit">
生成专业提示词
</el-button>
<el-space>
<el-button type="primary" size="large" :loading="generating" @click="onSubmit">
生成专业提示词
</el-button>
<el-button text type="info" @click="showAdvanced = !showAdvanced">
{{ showAdvanced ? '收起' : '高级选项' }}
</el-button>
</el-space>
</div>
<div v-if="showAdvanced" class="advanced-options">
<el-form-item label="输出长度 (tokens)" label-width="140px">
<el-input-number
v-model="maxTokensOverride"
:min="100"
:max="4096"
:step="100"
placeholder="由模板决定"
style="width: 220px"
/>
<span class="hint">留空由模板决定范围 1004096</span>
</el-form-item>
</div>
</el-card>