From 53d179dd002c06c4eb5e91bf15636c56bd8a23d8 Mon Sep 17 00:00:00 2001 From: renjianbo <18691577328@163.com> Date: Sun, 3 May 2026 09:49:22 +0800 Subject: [PATCH] feat(S1): iterative conversation for multi-round prompt refinement - Conversation model: store dialog context (max 10 rounds), JSON messages - POST /api/prompt/continue: append round, build LLM context from history - GET/DELETE /api/conversation/:id: retrieve or clear conversation - Vue: refine input card below result, round counter, reset button - Vue: continuePrompt API with conversation_id tracking Co-Authored-By: Claude Opus 4.6 --- src/flask_prompt_master/__init__.py | 5 + .../models/conversation.py | 62 +++++++ .../routes/conversation_routes.py | 154 ++++++++++++++++++ vue-app/src/api/modules/prompt.ts | 18 ++ vue-app/src/views/GenerateView.vue | 70 +++++++- 5 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 src/flask_prompt_master/models/conversation.py create mode 100644 src/flask_prompt_master/routes/conversation_routes.py diff --git a/src/flask_prompt_master/__init__.py b/src/flask_prompt_master/__init__.py index 15ee69e..c6ae602 100644 --- a/src/flask_prompt_master/__init__.py +++ b/src/flask_prompt_master/__init__.py @@ -71,6 +71,11 @@ def create_app(config_class=None): from src.flask_prompt_master.routes.history_routes import history_bp app.register_blueprint(history_bp) + # 迭代对话上下文(多轮优化) + from src.flask_prompt_master.models import conversation # noqa: F401 + from src.flask_prompt_master.routes.conversation_routes import conversation_bp + app.register_blueprint(conversation_bp) + # 提示词结构化质量评价(多段文本 + 模型 JSON 评价 + 历史) from src.flask_prompt_master.models import prompt_quality_models # noqa: F401 from src.flask_prompt_master.routes.prompt_quality_routes import prompt_quality_bp diff --git a/src/flask_prompt_master/models/conversation.py b/src/flask_prompt_master/models/conversation.py new file mode 100644 index 0000000..8f0283f --- /dev/null +++ b/src/flask_prompt_master/models/conversation.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +""" +对话上下文模型:支持多轮迭代优化提示词 +""" +from datetime import datetime +from src.flask_prompt_master import db + + +class Conversation(db.Model): + __tablename__ = 'conversation' + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + user_id = db.Column(db.Integer, nullable=False, index=True, comment='用户ID') + scene_type = db.Column(db.String(30), default='prompt', comment='场景类型: prompt/meal/poetry/report') + context_messages = db.Column(db.JSON, nullable=False, default=list, comment='对话上下文 [{role, content}]') + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + MAX_ROUNDS = 10 # 最多保留最近 N 轮对话 + + def to_dict(self): + return { + 'id': self.id, + 'user_id': self.user_id, + 'scene_type': self.scene_type, + 'context_messages': self.context_messages or [], + 'created_at': self.created_at.isoformat() if self.created_at else None, + 'updated_at': self.updated_at.isoformat() if self.updated_at else None, + } + + @classmethod + def append_round(cls, conversation_id, user_id, scene_type, role, content): + """追加一轮对话,若不存在则新建""" + conv = None + if conversation_id: + conv = cls.query.filter_by(id=conversation_id, user_id=user_id).first() + if conv is None: + conv = cls(user_id=user_id, scene_type=scene_type, context_messages=[]) + db.session.add(conv) + + messages = list(conv.context_messages or []) + messages.append({'role': role, 'content': content}) + + # 保留最近 MAX_ROUNDS 轮 + if len(messages) > cls.MAX_ROUNDS: + messages = messages[-cls.MAX_ROUNDS:] + + conv.context_messages = messages + conv.updated_at = datetime.utcnow() + db.session.flush() + return conv + + @classmethod + def build_llm_context(cls, conversation_id, user_id, system_prompt, new_user_msg): + """构建 LLM 调用的 messages: system + history + new_user""" + messages = [{'role': 'system', 'content': system_prompt}] + if conversation_id: + conv = cls.query.filter_by(id=conversation_id, user_id=user_id).first() + if conv and conv.context_messages: + messages.extend(conv.context_messages) + messages.append({'role': 'user', 'content': new_user_msg}) + return messages diff --git a/src/flask_prompt_master/routes/conversation_routes.py b/src/flask_prompt_master/routes/conversation_routes.py new file mode 100644 index 0000000..acbf919 --- /dev/null +++ b/src/flask_prompt_master/routes/conversation_routes.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +""" +迭代对话路由:支持基于历史上下文的多轮提示词优化 +""" +from flask import Blueprint, request, jsonify, current_app +from openai import OpenAI +import os +import logging +from src.flask_prompt_master import db +from src.flask_prompt_master.user_context import get_current_user_id +from src.flask_prompt_master.models.models import Prompt, PromptTemplate +from src.flask_prompt_master.models.conversation import Conversation + +logger = logging.getLogger(__name__) +conversation_bp = Blueprint('conversation', __name__) + +_client = OpenAI( + api_key=os.environ.get('LLM_API_KEY') or '', + base_url=os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1', +) + + +@conversation_bp.route('/api/prompt/continue', methods=['POST']) +def continue_prompt(): + """基于已有结果继续优化提示词(多轮对话)""" + data = request.get_json(silent=True) or {} + conversation_id = data.get('conversation_id') + previous_result = (data.get('previous_result') or '').strip() + refine_instruction = (data.get('refine_instruction') or '').strip() + template_id = data.get('template_id') + + if not previous_result: + return jsonify({'success': False, 'message': '缺少上一轮结果'}), 400 + if not refine_instruction: + return jsonify({'success': False, 'message': '请输入优化指令'}), 400 + + if template_id is not None and template_id != '': + try: + template_id = int(template_id) + except (TypeError, ValueError): + template_id = None + + user_id = get_current_user_id() + + # 获取 system_prompt + system_prompt = _get_system_prompt(template_id) + + try: + # 记录用户的新一轮指令到对话历史 + conv = Conversation.append_round( + conversation_id=conversation_id, + user_id=user_id, + scene_type='prompt', + role='user', + content=f"上一轮结果:\n{previous_result}\n\n优化指令:{refine_instruction}", + ) + + # 构建完整上下文 + current_app.logger.info( + f"迭代对话 conversation_id={conv.id} user_id={user_id} " + f"rounds={len(conv.context_messages)} instruction={refine_instruction[:80]}" + ) + + # 调用 LLM + response = _client.chat.completions.create( + model='deepseek-chat', + messages=Conversation.build_llm_context( + conv.id, user_id, system_prompt, + refine_instruction, + ), + temperature=0.7, + max_tokens=_get_max_tokens(template_id), + timeout=60, + ) + + generated_text = response.choices[0].message.content.strip() + + # 记录助手回复到对话历史 + Conversation.append_round( + conversation_id=conv.id, + user_id=user_id, + scene_type='prompt', + role='assistant', + content=generated_text, + ) + + # 保存到 Prompt 表 + prompt = Prompt( + input_text=refine_instruction, + generated_text=generated_text, + user_id=user_id, + ) + db.session.add(prompt) + db.session.commit() + + return jsonify({ + 'success': True, + 'conversation_id': conv.id, + 'generated_text': generated_text, + 'rounds': len(conv.context_messages) // 2, + 'prompt': { + 'id': prompt.id, + 'input_text': refine_instruction, + 'generated_text': generated_text, + }, + }) + + except Exception as e: + current_app.logger.error(f'continue_prompt error: {e}') + db.session.rollback() + return jsonify({'success': False, 'message': f'优化失败: {str(e)}'}), 500 + + +@conversation_bp.route('/api/conversation/', methods=['GET']) +def get_conversation(conversation_id): + """获取对话历史""" + user_id = get_current_user_id() + conv = Conversation.query.filter_by(id=conversation_id, user_id=user_id).first() + if not conv: + return jsonify({'success': False, 'message': '对话不存在'}), 404 + return jsonify({'success': True, 'conversation': conv.to_dict()}) + + +@conversation_bp.route('/api/conversation/', methods=['DELETE']) +def clear_conversation(conversation_id): + """清除对话上下文(开始新对话)""" + user_id = get_current_user_id() + conv = Conversation.query.filter_by(id=conversation_id, user_id=user_id).first() + if not conv: + return jsonify({'success': False, 'message': '对话不存在'}), 404 + db.session.delete(conv) + db.session.commit() + return jsonify({'success': True, 'message': '对话已清除'}) + + +def _get_system_prompt(template_id): + """获取系统提示词""" + if template_id: + template = PromptTemplate.query.get(template_id) + if template: + return template.system_prompt + default = PromptTemplate.query.filter_by(is_default=True).first() + if default: + return default.system_prompt + return "你是一个专业的提示词工程师,请直接返回优化后的提示词,不要添加任何解释。" + + +def _get_max_tokens(template_id): + """获取 max_tokens""" + if template_id: + template = PromptTemplate.query.get(template_id) + if template and template.max_tokens: + return template.max_tokens + return 500 diff --git a/vue-app/src/api/modules/prompt.ts b/vue-app/src/api/modules/prompt.ts index 51867f6..c277b89 100644 --- a/vue-app/src/api/modules/prompt.ts +++ b/vue-app/src/api/modules/prompt.ts @@ -17,3 +17,21 @@ export function fetchTemplatesByCategory(category: string) { export function generatePrompt(body: { input_text: string; template_id: number | null; max_tokens?: number }) { return client.post('/api/prompt/generate', body).then((r) => r.data) } + +export interface ContinuePromptResponse { + success: boolean + message?: string + conversation_id: number + generated_text: string + rounds: number + prompt?: { id: number; input_text: string; generated_text: string } +} + +export function continuePrompt(body: { + conversation_id?: number | null + previous_result: string + refine_instruction: string + template_id?: number | null +}) { + return client.post('/api/prompt/continue', body).then((r) => r.data) +} diff --git a/vue-app/src/views/GenerateView.vue b/vue-app/src/views/GenerateView.vue index 66e0d41..f8fa50b 100644 --- a/vue-app/src/views/GenerateView.vue +++ b/vue-app/src/views/GenerateView.vue @@ -1,7 +1,7 @@