ww
Some checks failed
Flask 提示词大师 - CI/CD 流水线 / 代码质量检查 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 单元测试 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 集成测试 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 构建Docker镜像 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 部署到测试环境 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 部署到生产环境 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 部署监控系统 (push) Has been cancelled

This commit is contained in:
rjb
2026-03-02 00:26:28 +08:00
parent c27fc21c1a
commit 1797b914a5
7 changed files with 380 additions and 15 deletions

View File

@@ -107,6 +107,10 @@ public class MainActivity extends AppCompatActivity {
if (btn != null) { if (btn != null) {
btn.setOnClickListener(v -> viewModel.generate()); btn.setOnClickListener(v -> viewModel.generate());
} }
MaterialButton btnOptimize = binding.btnOptimize;
if (btnOptimize != null) {
btnOptimize.setOnClickListener(v -> viewModel.optimizePrompt());
}
} }
private void setupResultAndActions() { private void setupResultAndActions() {
@@ -135,8 +139,10 @@ public class MainActivity extends AppCompatActivity {
viewModel.getLoadingLiveData().observe(this, loading -> { viewModel.getLoadingLiveData().observe(this, loading -> {
ProgressBar progress = binding.progressBar; ProgressBar progress = binding.progressBar;
MaterialButton btn = binding.btnGenerate; MaterialButton btn = binding.btnGenerate;
MaterialButton btnOpt = binding.btnOptimize;
if (progress != null) progress.setVisibility(loading != null && loading ? View.VISIBLE : View.GONE); if (progress != null) progress.setVisibility(loading != null && loading ? View.VISIBLE : View.GONE);
if (btn != null) btn.setEnabled(loading == null || !loading); if (btn != null) btn.setEnabled(loading == null || !loading);
if (btnOpt != null) btnOpt.setEnabled(loading == null || !loading);
}); });
viewModel.getErrorLiveData().observe(this, error -> { viewModel.getErrorLiveData().observe(this, error -> {

View File

@@ -0,0 +1,98 @@
package com.ruilaizi.example.data.api;
import android.util.Log;
import com.ruilaizi.example.BuildConfig;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/**
* OpenAI 兼容的非流式 Chat Completions 接口。
* 用于意图分析、提示词优化等需要完整响应的场景。默认对接 DeepSeek。
*/
public class OpenAICompletionService {
private static final String TAG = "OpenAICompletion";
private static final String CHAT_URL = "chat/completions";
private static final String MODEL = "deepseek-chat";
private final String baseUrl;
public OpenAICompletionService(String baseUrl) {
this.baseUrl = baseUrl == null || baseUrl.isEmpty()
? BuildConfig.API_BASE_URL
: baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
}
/**
* 同步请求 Chat Completions返回完整 content。
*/
public String chat(String systemPrompt, String userContent, float temperature, int maxTokens, String apiKey) throws Exception {
String urlStr = baseUrl + CHAT_URL;
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(60000);
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", "Bearer " + apiKey);
JSONObject body = new JSONObject();
body.put("model", MODEL);
body.put("stream", false);
body.put("max_tokens", maxTokens);
body.put("temperature", (double) temperature);
JSONArray messages = new JSONArray();
messages.put(new JSONObject().put("role", "system").put("content", systemPrompt));
messages.put(new JSONObject().put("role", "user").put("content", userContent));
body.put("messages", messages);
try (OutputStream os = conn.getOutputStream()) {
os.write(body.toString().getBytes(StandardCharsets.UTF_8));
}
int code = conn.getResponseCode();
if (code != 200) {
String err = readAll(conn.getErrorStream());
throw new RuntimeException("HTTP " + code + ": " + err);
}
String raw = readAll(conn.getInputStream());
JSONObject json = new JSONObject(raw);
JSONArray choices = json.optJSONArray("choices");
if (choices == null || choices.length() == 0) {
throw new RuntimeException("响应无内容");
}
JSONObject msg = choices.getJSONObject(0).optJSONObject("message");
if (msg == null || !msg.has("content")) {
throw new RuntimeException("响应格式错误");
}
String content = msg.optString("content", "").trim();
if (content.isEmpty()) {
throw new RuntimeException("模型返回为空");
}
return content;
}
private static String readAll(java.io.InputStream is) {
if (is == null) return "";
try (BufferedReader r = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = r.readLine()) != null) sb.append(line).append("\n");
return sb.toString();
} catch (Exception e) {
Log.e(TAG, "readAll", e);
return e.getMessage();
}
}
}

View File

@@ -0,0 +1,185 @@
package com.ruilaizi.example.data.prompt;
import android.util.Log;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 两阶段专家提示词生成(参考《生成专业提示词代码逻辑分析》)。
* 第一阶段:意图分析 → 第二阶段:领域专家生成。
*/
public class PromptGenerator {
private static final String TAG = "PromptGenerator";
private static final String INTENT_PROMPT = "你是一位资深的意图分析专家,请分析用户输入的意图和需求。\n\n" +
"你必须严格按照以下JSON格式返回不要添加任何其他内容\n" +
"{\n" +
" \"core_intent\": \"技术\",\n" +
" \"domain\": \"具体专业领域\",\n" +
" \"key_requirements\": [\"需求1\", \"需求2\"],\n" +
" \"expected_output\": \"期望输出的具体形式\",\n" +
" \"constraints\": [\"约束1\", \"约束2\"],\n" +
" \"keywords\": [\"关键词1\", \"关键词2\"]\n" +
"}\n\n" +
"注意:\n" +
"1. 严格遵守JSON格式\n" +
"2. core_intent必须是以下之一技术、创意、分析、咨询\n" +
"3. 数组至少包含1个元素\n" +
"4. 所有字段都必须存在\n" +
"5. 不要包含注释\n" +
"6. 不要添加任何额外的文本";
private static final Map<String, String> EXPERT_TEMPLATES = new HashMap<>();
static {
EXPERT_TEMPLATES.put("技术", "你是一位专业的技术领域提示工程师。基于以下意图分析,生成一个专业的技术任务提示词:\n\n意图分析\n{analysis}\n\n请生成的提示词包含\n1. 明确的技术背景和上下文\n2. 具体的技术要求和规范\n3. 性能和质量标准\n4. 技术约束条件\n5. 预期交付成果\n6. 评估标准\n\n使用专业技术术语确保提示词的可执行性和可验证性。");
EXPERT_TEMPLATES.put("创意", "你是一位专业的创意领域提示工程师。基于以下意图分析,生成一个创意设计提示词:\n\n意图分析\n{analysis}\n\n请生成的提示词包含\n1. 创意方向和灵感来源\n2. 风格和氛围要求\n3. 目标受众定义\n4. 设计元素规范\n5. 创意表现形式\n6. 评估标准\n\n使用专业创意术语确保提示词的创新性和可执行性。");
EXPERT_TEMPLATES.put("分析", "你是一位专业的数据分析提示工程师。基于以下意图分析,生成一个数据分析提示词:\n\n意图分析\n{analysis}\n\n请生成的提示词包含\n1. 分析目标和范围\n2. 数据要求和规范\n3. 分析方法和工具\n4. 输出格式要求\n5. 关键指标定义\n6. 质量控制标准\n\n使用专业分析术语确保提示词的科学性和可操作性。");
EXPERT_TEMPLATES.put("咨询", "你是一位专业的咨询领域提示工程师。基于以下意图分析,生成一个咨询服务提示词:\n\n意图分析\n{analysis}\n\n请生成的提示词包含\n1. 咨询问题界定\n2. 背景信息要求\n3. 分析框架设定\n4. 建议输出格式\n5. 实施考虑因素\n6. 效果评估标准\n\n使用专业咨询术语确保提示词的专业性和实用性。");
}
private static final String FALLBACK_TEMPLATE = "你是一位专业的通用领域提示工程师。基于以下意图分析,生成一个专业的提示词:\n\n意图分析\n{analysis}\n\n请生成的提示词包含\n1. 明确的目标定义\n2. 具体要求和规范\n3. 质量标准\n4. 约束条件\n5. 预期输出\n6. 评估标准\n\n确保提示词的清晰性和可执行性。";
private final OpenAICompletionService completionService;
private final ApiKeyProvider apiKeyProvider;
public PromptGenerator(OpenAICompletionService completionService, ApiKeyProvider apiKeyProvider) {
this.completionService = completionService;
this.apiKeyProvider = apiKeyProvider;
}
/**
* 同步执行两阶段生成(需在后台线程调用)。
*/
public OptimizeResult optimize(String userInput, float temperature, int maxTokens) throws Exception {
String apiKey = apiKeyProvider.getApiKey();
if (apiKey == null || apiKey.isEmpty()) {
throw new Exception("请先在设置中配置 API Key");
}
// 第一阶段:意图分析
String intentRaw = completionService.chat(INTENT_PROMPT, userInput, 0.1f, 1024, apiKey);
intentRaw = intentRaw.replace("```json", "").replace("```", "").trim();
IntentAnalysis intent = parseIntent(intentRaw);
if (intent == null) {
throw new Exception("意图分析解析失败");
}
// 第二阶段:领域专家生成
String template = EXPERT_TEMPLATES.getOrDefault(intent.coreIntent, FALLBACK_TEMPLATE);
String analysisStr = toJsonString(intent);
String expertPrompt = template.replace("{analysis}", analysisStr);
String generated = completionService.chat(expertPrompt, userInput, temperature, maxTokens, apiKey);
if (generated == null || generated.isEmpty()) {
throw new Exception("生成结果为空");
}
return new OptimizeResult(intent, generated.trim());
}
private IntentAnalysis parseIntent(String json) {
try {
JSONObject obj = new JSONObject(json);
IntentAnalysis ia = new IntentAnalysis();
ia.coreIntent = safeStr(obj.opt("core_intent"), "技术");
if (!Arrays.asList("技术", "创意", "分析", "咨询").contains(ia.coreIntent)) {
ia.coreIntent = "技术";
}
ia.domain = safeStr(obj.opt("domain"), "未指定");
ia.expectedOutput = safeStr(obj.opt("expected_output"), "未指定");
ia.keyRequirements = toStrArray(obj.optJSONArray("key_requirements"));
ia.constraints = toStrArray(obj.optJSONArray("constraints"));
ia.keywords = toStrArray(obj.optJSONArray("keywords"));
return ia;
} catch (Exception e) {
Log.e(TAG, "parseIntent failed: " + e.getMessage());
return null;
}
}
private static String safeStr(Object o, String def) {
return o != null ? String.valueOf(o).trim() : def;
}
private static String[] toStrArray(JSONArray arr) {
if (arr == null || arr.length() == 0) return new String[]{"未指定"};
List<String> list = new ArrayList<>();
for (int i = 0; i < arr.length(); i++) {
list.add(String.valueOf(arr.opt(i)));
}
return list.toArray(new String[0]);
}
private static String toJsonString(IntentAnalysis ia) {
try {
JSONObject obj = new JSONObject();
obj.put("core_intent", ia.coreIntent);
obj.put("domain", ia.domain);
obj.put("expected_output", ia.expectedOutput);
obj.put("key_requirements", new JSONArray(ia.keyRequirements != null ? Arrays.asList(ia.keyRequirements) : new ArrayList<>()));
obj.put("constraints", new JSONArray(ia.constraints != null ? Arrays.asList(ia.constraints) : new ArrayList<>()));
obj.put("keywords", new JSONArray(ia.keywords != null ? Arrays.asList(ia.keywords) : new ArrayList<>()));
return obj.toString(2);
} catch (Exception e) {
return "{}";
}
}
private static class IntentAnalysis {
String coreIntent;
String domain;
String expectedOutput;
String[] keyRequirements;
String[] constraints;
String[] keywords;
}
/** 优化结果:意图分析 + 生成的提示词 */
public static class OptimizeResult {
public final String coreIntent;
public final String domain;
public final String expectedOutput;
public final String[] keyRequirements;
public final String[] constraints;
public final String generatedPrompt;
OptimizeResult(IntentAnalysis ia, String generatedPrompt) {
this.coreIntent = ia.coreIntent;
this.domain = ia.domain;
this.expectedOutput = ia.expectedOutput;
this.keyRequirements = ia.keyRequirements != null ? ia.keyRequirements : new String[0];
this.constraints = ia.constraints != null ? ia.constraints : new String[0];
this.generatedPrompt = generatedPrompt;
}
/** 格式化为可展示的完整文本 */
public String toDisplayText() {
StringBuilder sb = new StringBuilder();
sb.append("【意图分析】\n");
sb.append("核心意图:").append(coreIntent).append("\n");
sb.append("专业领域:").append(domain).append("\n");
sb.append("预期输出:").append(expectedOutput).append("\n");
if (keyRequirements != null && keyRequirements.length > 0) {
sb.append("关键需求:").append(String.join("", keyRequirements)).append("\n");
}
if (constraints != null && constraints.length > 0) {
sb.append("约束条件:").append(String.join("", constraints)).append("\n");
}
sb.append("\n【优化后提示词】\n").append(generatedPrompt);
return sb.toString();
}
}
}

View File

@@ -6,24 +6,28 @@ import androidx.lifecycle.LiveData;
import com.ruilaizi.example.data.api.AIService; import com.ruilaizi.example.data.api.AIService;
import com.ruilaizi.example.data.api.ApiKeyProvider; import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
import com.ruilaizi.example.data.api.StreamCallback; import com.ruilaizi.example.data.api.StreamCallback;
import com.ruilaizi.example.data.db.AppDatabase; import com.ruilaizi.example.data.db.AppDatabase;
import com.ruilaizi.example.data.db.GenerationRecord; import com.ruilaizi.example.data.db.GenerationRecord;
import com.ruilaizi.example.data.db.GenerationRecordDao; import com.ruilaizi.example.data.db.GenerationRecordDao;
import com.ruilaizi.example.data.prompt.PromptGenerator;
/** /**
* 生成业务仓库:调用 AI 流式接口,并缓存记录到 Room。 * 生成业务仓库:调用 AI 流式接口、提示词优化,并缓存记录到 Room。
*/ */
public class GenerationRepository { public class GenerationRepository {
private final AIService aiService; private final AIService aiService;
private final ApiKeyProvider apiKeyProvider; private final ApiKeyProvider apiKeyProvider;
private final GenerationRecordDao dao; private final GenerationRecordDao dao;
private final PromptGenerator promptGenerator;
public GenerationRepository(Context context) { public GenerationRepository(Context context) {
apiKeyProvider = new ApiKeyProvider(context); apiKeyProvider = new ApiKeyProvider(context);
aiService = new com.ruilaizi.example.data.api.OpenAIStreamService(null); aiService = new com.ruilaizi.example.data.api.OpenAIStreamService(null);
dao = AppDatabase.getInstance(context).generationRecordDao(); dao = AppDatabase.getInstance(context).generationRecordDao();
promptGenerator = new PromptGenerator(new OpenAICompletionService(null), apiKeyProvider);
} }
public String getApiKey() { public String getApiKey() {
@@ -76,4 +80,11 @@ public class GenerationRepository {
public LiveData<java.util.List<GenerationRecord>> getRecentRecords() { public LiveData<java.util.List<GenerationRecord>> getRecentRecords() {
return dao.getRecentRecords(); return dao.getRecentRecords();
} }
/**
* 智能提示词优化(两阶段专家逻辑)。需在后台线程调用。
*/
public PromptGenerator.OptimizeResult optimizePrompt(String input, float temperature, int maxTokens) throws Exception {
return promptGenerator.optimize(input, temperature, maxTokens);
}
} }

View File

@@ -12,8 +12,12 @@ import androidx.lifecycle.MutableLiveData;
import com.ruilaizi.example.data.api.StreamCallback; import com.ruilaizi.example.data.api.StreamCallback;
import com.ruilaizi.example.data.db.GenerationRecord; import com.ruilaizi.example.data.db.GenerationRecord;
import com.ruilaizi.example.data.prompt.PromptGenerator;
import com.ruilaizi.example.data.repository.GenerationRepository; import com.ruilaizi.example.data.repository.GenerationRepository;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** /**
* 主界面 MVVM ViewModel提示词、参数、生成结果、加载状态及复制/重新生成/续写。 * 主界面 MVVM ViewModel提示词、参数、生成结果、加载状态及复制/重新生成/续写。
*/ */
@@ -37,6 +41,7 @@ public class MainViewModel extends AndroidViewModel {
private int lastTemperatureLevel = 1; private int lastTemperatureLevel = 1;
private final StringBuilder streamingBuffer = new StringBuilder(); private final StringBuilder streamingBuffer = new StringBuilder();
private final ExecutorService executor = Executors.newSingleThreadExecutor();
public MainViewModel(@NonNull Application application) { public MainViewModel(@NonNull Application application) {
super(application); super(application);
@@ -67,6 +72,37 @@ public class MainViewModel extends AndroidViewModel {
temperatureLevelLiveData.setValue(level); temperatureLevelLiveData.setValue(level);
} }
/** 智能提示词优化(两阶段专家逻辑) */
public void optimizePrompt() {
String prompt = promptLiveData.getValue();
if (prompt == null) prompt = "";
prompt = prompt.trim();
if (prompt.isEmpty()) {
snackbarLiveData.setValue("请输入需求描述");
return;
}
if (repository.getApiKey() == null || repository.getApiKey().isEmpty()) {
errorLiveData.setValue("请先在设置中配置 API Key");
return;
}
loadingLiveData.setValue(true);
resultLiveData.setValue("");
errorLiveData.setValue(null);
executor.execute(() -> {
try {
PromptGenerator.OptimizeResult result = repository.optimizePrompt(prompt, 0.7f, 1000);
String display = result.toDisplayText();
resultLiveData.postValue(display);
snackbarLiveData.postValue("提示词优化完成");
} catch (Exception e) {
errorLiveData.postValue(e != null ? e.getMessage() : "优化失败");
} finally {
loadingLiveData.postValue(false);
}
});
}
/** 生成:使用当前 prompt 与参数 */ /** 生成:使用当前 prompt 与参数 */
public void generate() { public void generate() {
String prompt = promptLiveData.getValue(); String prompt = promptLiveData.getValue();
@@ -126,16 +162,22 @@ public class MainViewModel extends AndroidViewModel {
} }
} }
/** 续写:把当前结果作为新 prompt清空结果区可编辑后生成 */ /** 续写:把当前结果作为新 prompt清空结果区可编辑后生成。若为优化结果则只填入优化后的提示词部分。 */
public void continueEdit() { public void continueEdit() {
String result = resultLiveData.getValue(); String result = resultLiveData.getValue();
if (result != null && !result.isEmpty()) { if (result == null || result.isEmpty()) {
setPrompt(result); snackbarLiveData.setValue("暂无生成结果可续写");
return;
}
String toPut = result;
String marker = "【优化后提示词】\n";
int idx = result.indexOf(marker);
if (idx >= 0) {
toPut = result.substring(idx + marker.length()).trim();
}
setPrompt(toPut);
resultLiveData.setValue(""); resultLiveData.setValue("");
snackbarLiveData.setValue("已填入输入框,可修改后点击生成"); snackbarLiveData.setValue("已填入输入框,可修改后点击生成");
} else {
snackbarLiveData.setValue("暂无生成结果可续写");
}
} }
/** 复制结果到剪贴板 */ /** 复制结果到剪贴板 */
@@ -167,4 +209,10 @@ public class MainViewModel extends AndroidViewModel {
public void clearSnackbar() { public void clearSnackbar() {
snackbarLiveData.setValue(null); snackbarLiveData.setValue(null);
} }
@Override
protected void onCleared() {
executor.shutdown();
super.onCleared();
}
} }

View File

@@ -199,15 +199,31 @@
android:textColor="@color/colorTextSecondary" /> android:textColor="@color/colorTextSecondary" />
</LinearLayout> </LinearLayout>
<!-- 生成按钮 --> <!-- 操作按钮区:优化 + 生成 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false"
android:layout_marginBottom="20dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_optimize"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:text="@string/btn_optimize"
android:layout_marginEnd="8dp" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_generate" android:id="@+id/btn_generate"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="56dp" android:layout_height="56dp"
android:layout_weight="1"
android:text="@string/btn_generate" android:text="@string/btn_generate"
app:backgroundTint="@color/colorPrimary" app:backgroundTint="@color/colorPrimary"
android:textColor="@color/colorWhite" android:textColor="@color/colorWhite"
android:layout_marginBottom="20dp" /> android:layout_marginStart="8dp" />
</LinearLayout>
<!-- 加载指示 --> <!-- 加载指示 -->
<ProgressBar <ProgressBar

View File

@@ -16,6 +16,7 @@
<string name="temp_balanced">平衡</string> <string name="temp_balanced">平衡</string>
<string name="temp_creative">创意</string> <string name="temp_creative">创意</string>
<string name="btn_generate">生成</string> <string name="btn_generate">生成</string>
<string name="btn_optimize">优化提示词</string>
<string name="result_placeholder">生成结果将显示在这里…</string> <string name="result_placeholder">生成结果将显示在这里…</string>
<string name="btn_copy">复制</string> <string name="btn_copy">复制</string>
<string name="btn_regenerate">重新生成</string> <string name="btn_regenerate">重新生成</string>