From 1797b914a5194734d6e38d8bff929eb488d83b75 Mon Sep 17 00:00:00 2001 From: rjb <263303411@qq.com> Date: Mon, 2 Mar 2026 00:26:28 +0800 Subject: [PATCH] ww --- .../com/ruilaizi/example/MainActivity.java | 6 + .../data/api/OpenAICompletionService.java | 98 ++++++++++ .../example/data/prompt/PromptGenerator.java | 185 ++++++++++++++++++ .../data/repository/GenerationRepository.java | 13 +- .../ruilaizi/example/ui/MainViewModel.java | 60 +++++- .../app/src/main/res/layout/activity_main.xml | 32 ++- example/app/src/main/res/values/strings.xml | 1 + 7 files changed, 380 insertions(+), 15 deletions(-) create mode 100644 example/app/src/main/java/com/ruilaizi/example/data/api/OpenAICompletionService.java create mode 100644 example/app/src/main/java/com/ruilaizi/example/data/prompt/PromptGenerator.java diff --git a/example/app/src/main/java/com/ruilaizi/example/MainActivity.java b/example/app/src/main/java/com/ruilaizi/example/MainActivity.java index 2ada9df..37ddc23 100644 --- a/example/app/src/main/java/com/ruilaizi/example/MainActivity.java +++ b/example/app/src/main/java/com/ruilaizi/example/MainActivity.java @@ -107,6 +107,10 @@ public class MainActivity extends AppCompatActivity { if (btn != null) { btn.setOnClickListener(v -> viewModel.generate()); } + MaterialButton btnOptimize = binding.btnOptimize; + if (btnOptimize != null) { + btnOptimize.setOnClickListener(v -> viewModel.optimizePrompt()); + } } private void setupResultAndActions() { @@ -135,8 +139,10 @@ public class MainActivity extends AppCompatActivity { viewModel.getLoadingLiveData().observe(this, loading -> { ProgressBar progress = binding.progressBar; MaterialButton btn = binding.btnGenerate; + MaterialButton btnOpt = binding.btnOptimize; if (progress != null) progress.setVisibility(loading != null && loading ? View.VISIBLE : View.GONE); if (btn != null) btn.setEnabled(loading == null || !loading); + if (btnOpt != null) btnOpt.setEnabled(loading == null || !loading); }); viewModel.getErrorLiveData().observe(this, error -> { diff --git a/example/app/src/main/java/com/ruilaizi/example/data/api/OpenAICompletionService.java b/example/app/src/main/java/com/ruilaizi/example/data/api/OpenAICompletionService.java new file mode 100644 index 0000000..ca7f4ba --- /dev/null +++ b/example/app/src/main/java/com/ruilaizi/example/data/api/OpenAICompletionService.java @@ -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(); + } + } +} diff --git a/example/app/src/main/java/com/ruilaizi/example/data/prompt/PromptGenerator.java b/example/app/src/main/java/com/ruilaizi/example/data/prompt/PromptGenerator.java new file mode 100644 index 0000000..cff436a --- /dev/null +++ b/example/app/src/main/java/com/ruilaizi/example/data/prompt/PromptGenerator.java @@ -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 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 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(); + } + } +} diff --git a/example/app/src/main/java/com/ruilaizi/example/data/repository/GenerationRepository.java b/example/app/src/main/java/com/ruilaizi/example/data/repository/GenerationRepository.java index f65807c..fb77adb 100644 --- a/example/app/src/main/java/com/ruilaizi/example/data/repository/GenerationRepository.java +++ b/example/app/src/main/java/com/ruilaizi/example/data/repository/GenerationRepository.java @@ -6,24 +6,28 @@ import androidx.lifecycle.LiveData; import com.ruilaizi.example.data.api.AIService; 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.db.AppDatabase; import com.ruilaizi.example.data.db.GenerationRecord; import com.ruilaizi.example.data.db.GenerationRecordDao; +import com.ruilaizi.example.data.prompt.PromptGenerator; /** - * 生成业务仓库:调用 AI 流式接口,并缓存记录到 Room。 + * 生成业务仓库:调用 AI 流式接口、提示词优化,并缓存记录到 Room。 */ public class GenerationRepository { private final AIService aiService; private final ApiKeyProvider apiKeyProvider; private final GenerationRecordDao dao; + private final PromptGenerator promptGenerator; public GenerationRepository(Context context) { apiKeyProvider = new ApiKeyProvider(context); aiService = new com.ruilaizi.example.data.api.OpenAIStreamService(null); dao = AppDatabase.getInstance(context).generationRecordDao(); + promptGenerator = new PromptGenerator(new OpenAICompletionService(null), apiKeyProvider); } public String getApiKey() { @@ -76,4 +80,11 @@ public class GenerationRepository { public LiveData> getRecentRecords() { return dao.getRecentRecords(); } + + /** + * 智能提示词优化(两阶段专家逻辑)。需在后台线程调用。 + */ + public PromptGenerator.OptimizeResult optimizePrompt(String input, float temperature, int maxTokens) throws Exception { + return promptGenerator.optimize(input, temperature, maxTokens); + } } diff --git a/example/app/src/main/java/com/ruilaizi/example/ui/MainViewModel.java b/example/app/src/main/java/com/ruilaizi/example/ui/MainViewModel.java index cc43dd4..3df733e 100644 --- a/example/app/src/main/java/com/ruilaizi/example/ui/MainViewModel.java +++ b/example/app/src/main/java/com/ruilaizi/example/ui/MainViewModel.java @@ -12,8 +12,12 @@ import androidx.lifecycle.MutableLiveData; import com.ruilaizi.example.data.api.StreamCallback; import com.ruilaizi.example.data.db.GenerationRecord; +import com.ruilaizi.example.data.prompt.PromptGenerator; import com.ruilaizi.example.data.repository.GenerationRepository; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + /** * 主界面 MVVM ViewModel:提示词、参数、生成结果、加载状态及复制/重新生成/续写。 */ @@ -37,6 +41,7 @@ public class MainViewModel extends AndroidViewModel { private int lastTemperatureLevel = 1; private final StringBuilder streamingBuffer = new StringBuilder(); + private final ExecutorService executor = Executors.newSingleThreadExecutor(); public MainViewModel(@NonNull Application application) { super(application); @@ -67,6 +72,37 @@ public class MainViewModel extends AndroidViewModel { 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 与参数 */ public void generate() { String prompt = promptLiveData.getValue(); @@ -126,16 +162,22 @@ public class MainViewModel extends AndroidViewModel { } } - /** 续写:把当前结果作为新 prompt,清空结果区,可编辑后生成 */ + /** 续写:把当前结果作为新 prompt,清空结果区,可编辑后生成。若为优化结果则只填入优化后的提示词部分。 */ public void continueEdit() { String result = resultLiveData.getValue(); - if (result != null && !result.isEmpty()) { - setPrompt(result); - resultLiveData.setValue(""); - snackbarLiveData.setValue("已填入输入框,可修改后点击生成"); - } else { + if (result == null || result.isEmpty()) { 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(""); + snackbarLiveData.setValue("已填入输入框,可修改后点击生成"); } /** 复制结果到剪贴板 */ @@ -167,4 +209,10 @@ public class MainViewModel extends AndroidViewModel { public void clearSnackbar() { snackbarLiveData.setValue(null); } + + @Override + protected void onCleared() { + executor.shutdown(); + super.onCleared(); + } } diff --git a/example/app/src/main/res/layout/activity_main.xml b/example/app/src/main/res/layout/activity_main.xml index 1c4db01..639d8ce 100644 --- a/example/app/src/main/res/layout/activity_main.xml +++ b/example/app/src/main/res/layout/activity_main.xml @@ -199,15 +199,31 @@ android:textColor="@color/colorTextSecondary" /> - - + + android:layout_height="wrap_content" + android:orientation="horizontal" + android:baselineAligned="false" + android:layout_marginBottom="20dp"> + + + 平衡 创意 生成 + 优化提示词 生成结果将显示在这里… 复制 重新生成