refactor: 簡化API設計,移除statistics計算和userLevel參數

- 移除後端statistics計算邏輯,改由前端處理
- 移除userLevel參數,簡化API接口
- 清理DTO模型中的多餘欄位 (Tags, IsIdiom, UserLevel)
- 更新AI模型名稱為gemini-1.5-flash
- 新增完整的AI Prompt設計規格
- 建立AI驅動產品後端技術架構指南

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-22 23:18:11 +08:00
parent 8568d5e500
commit 96bb9e920e
6 changed files with 1845 additions and 89 deletions

View File

@ -5,7 +5,7 @@
- **文件名稱**: AI生成功能後端API規格
- **版本**: v1.0
- **建立日期**: 2025-01-25
- **最後更新**: 2025-01-25
- **最後更新**: 2025-01-25 (修正API規格)
- **負責團隊**: DramaLing後端開發團隊
- **對應前端**: `/app/generate/page.tsx`
@ -57,7 +57,7 @@ External Services:
語言: C# / .NET 8
框架: ASP.NET Core Web API
資料庫: PostgreSQL + Redis (緩存)
AI服務: Google Gemini API
AI服務: Google Gemini 1.5 Flash API (包含結構化Prompt設計)
部署: Docker + Kubernetes
監控: Application Insights
```
@ -79,7 +79,6 @@ Authorization: Bearer {token}
```json
{
"inputText": "She just join the team, so let's cut her some slack until she get used to the workflow.",
"userLevel": "A2",
"analysisMode": "full",
"options": {
"includeGrammarCheck": true,
@ -95,7 +94,6 @@ Authorization: Bearer {token}
| 參數 | 類型 | 必需 | 說明 |
|------|------|------|------|
| inputText | string | 是 | 待分析的英文句子 (最多300字) |
| userLevel | string | 是 | 用戶CEFR等級 (A1-C2) |
| analysisMode | string | 否 | 分析模式: "basic"\|"full" (預設: "full") |
| options | object | 否 | 分析選項配置 |
@ -142,7 +140,6 @@ Authorization: Bearer {token}
"synonyms": ["her"],
"example": "She is a teacher.",
"exampleTranslation": "她是一名老師。",
"tags": ["basic", "pronoun"]
},
"just": {
"word": "just",
@ -155,7 +152,6 @@ Authorization: Bearer {token}
"synonyms": ["recently", "only", "merely"],
"example": "I just arrived.",
"exampleTranslation": "我剛到。",
"tags": ["time", "adverb"]
}
},
"idioms": [
@ -175,20 +171,10 @@ Authorization: Bearer {token}
"exampleTranslation": "對他寬容一點,他是新來的。"
}
],
"statistics": {
"totalWords": 16,
"uniqueWords": 15,
"simpleWords": 8,
"moderateWords": 4,
"difficultWords": 3,
"idioms": 1,
"averageDifficulty": "A2"
},
"metadata": {
"analysisModel": "gemini-pro",
"analysisVersion": "1.0",
"analysisModel": "gemini-1.5-flash",
"analysisVersion": "2.0",
"processingDate": "2025-01-25T10:30:00Z",
"userLevel": "A2"
}
}
}
@ -228,7 +214,6 @@ interface VocabularyAnalysis {
synonyms: string[] // 同義詞
example?: string // 例句
exampleTranslation?: string // 例句翻譯
tags: string[] // 標籤分類
}
```
@ -250,6 +235,48 @@ interface GrammarError {
}
```
### **IdiomDto 模型**
```typescript
interface IdiomDto {
idiom: string // 慣用語本身
translation: string // 中文翻譯
definition: string // 英文定義
pronunciation: string // 發音 (IPA)
difficultyLevel: CEFRLevel // CEFR等級
frequency: FrequencyLevel // 使用頻率
synonyms: string[] // 同義詞或相似表達
example?: string // 例句
exampleTranslation?: string // 例句翻譯
}
```
### **AnalysisMetadata 模型**
```typescript
interface AnalysisMetadata {
analysisModel: string // AI模型名稱
analysisVersion: string // 分析版本
processingDate: string // 處理時間 (ISO 8601)
}
```
### **ApiErrorResponse 模型**
```typescript
interface ApiErrorResponse {
success: boolean // 固定為 false
error: ApiError // 錯誤詳情
timestamp: string // 錯誤時間 (ISO 8601)
requestId: string // 請求ID
}
interface ApiError {
code: string // 錯誤代碼
message: string // 錯誤訊息
details?: object // 錯誤詳情
suggestions: string[] // 建議解決方案
}
```
### **枚舉定義**
```typescript
type CEFRLevel = "A1" | "A2" | "B1" | "B2" | "C1" | "C2"
@ -259,6 +286,151 @@ type AnalysisMode = "basic" | "full"
---
## 🤖 **AI Prompt設計規格**
### **Prompt架構設計**
#### **核心Prompt模板**
```text
You are an English learning assistant. Analyze this sentence for a learner and return ONLY a valid JSON response.
**Input Sentence**: "{inputText}"
**Required JSON Structure:**
{
"sentenceTranslation": "Traditional Chinese translation of the entire sentence",
"hasGrammarErrors": true/false,
"grammarCorrections": [
{
"original": "incorrect text",
"corrected": "correct text",
"type": "error type (tense/subject-verb/preposition/word-order)",
"explanation": "brief explanation in Traditional Chinese"
}
],
"vocabularyAnalysis": {
"word1": {
"word": "the word",
"translation": "Traditional Chinese translation",
"definition": "English definition",
"partOfSpeech": "noun/verb/adjective/etc",
"pronunciation": "/phonetic/",
"difficultyLevel": "A1/A2/B1/B2/C1/C2",
"frequency": "high/medium/low",
"synonyms": ["synonym1", "synonym2"],
"example": "example sentence",
"exampleTranslation": "Traditional Chinese example translation"
}
},
"idioms": [
{
"idiom": "idiomatic expression",
"translation": "Traditional Chinese meaning",
"definition": "English explanation",
"pronunciation": "/phonetic notation/",
"difficultyLevel": "A1/A2/B1/B2/C1/C2",
"frequency": "high/medium/low",
"synonyms": ["synonym1", "synonym2"],
"example": "usage example",
"exampleTranslation": "Traditional Chinese example"
}
]
}
```
### **Prompt指導原則**
#### **分析指導方針**
```yaml
語法檢查:
- 檢測範圍: 時態錯誤、主謂一致、介詞使用、詞序問題
- 修正原則: 提供自然且正確的英語表達
- 解釋語言: 使用繁體中文(台灣標準)
詞彙分析:
- 包含範圍: 所有有意義的詞彙(排除冠詞 a, an, the
- CEFR標準: 準確分配A1-C2等級
- 發音標記: 使用國際音標(IPA)
- 例句品質: 提供實用且常見的例句
慣用語識別:
- 識別目標: 慣用語、片語動詞、固定搭配
- 分類原則: 獨立於vocabularyAnalysis處理
- 解釋詳度: 提供文化背景和使用場景
翻譯品質:
- 語言標準: 繁體中文(台灣用法)
- 自然程度: 符合中文表達習慣
- 準確性: 保持原文語義完整
```
### **Prompt優化策略**
#### **結構化輸出控制**
```text
**IMPORTANT**: Return ONLY the JSON object, no additional text or explanation.
**Quality Assurance:**
1. Ensure all JSON keys are exactly as specified
2. Use Traditional Chinese for all translations
3. Assign accurate CEFR levels based on standard guidelines
4. Separate idioms from regular vocabulary
5. Provide practical examples that learners can use
```
#### **錯誤處理Prompt**
```text
**Error Handling Guidelines:**
- If unable to analyze: Return basic structure with explanatory messages
- If safety filtered: Provide alternative analysis approach
- If ambiguous input: Make reasonable assumptions and proceed
- Always return valid JSON regardless of input complexity
```
### **Prompt版本管理**
#### **版本演進記錄**
```yaml
v1.0 (2025-01-20):
- 基礎prompt結構
- 簡單詞彙分析
v1.5 (2025-01-22):
- 新增慣用語識別
- 優化語法檢查
v2.0 (2025-01-25):
- 結構化JSON輸出
- 完整資料模型支援
- 繁體中文本地化
- 錯誤處理機制
```
#### **A/B測試配置**
```csharp
// Prompt變體測試
public enum PromptVariant
{
Standard, // 標準prompt
DetailedExamples, // 強調例句品質
StrictGrammar, // 加強語法檢查
CulturalContext // 增加文化背景
}
// 動態Prompt選擇
private string GetPromptByVariant(PromptVariant variant, string inputText)
{
return variant switch
{
PromptVariant.Standard => BuildStandardPrompt(inputText),
PromptVariant.DetailedExamples => BuildDetailedPrompt(inputText),
_ => BuildStandardPrompt(inputText)
};
}
```
---
## 🔒 **認證與授權**
### **API認證**
@ -343,7 +515,7 @@ Premium用戶:
"responseTime": 2340,
"inputLength": 89,
"analysisMode": "full",
"aiModel": "gpt-4",
"aiModel": "gemini-1.5-flash",
"processingSteps": {
"grammarCheck": 450,
"vocabularyAnalysis": 1200,
@ -409,18 +581,14 @@ Premium用戶:
測試目的: 驗證完整分析功能
輸入數據:
inputText: "She just join the team, so let's cut her some slack until she get used to the workflow."
userLevel: "A2"
analysisMode: "full"
預期結果:
statusCode: 200
grammarCorrection.hasErrors: true
grammarCorrection.corrections.length: 2
vocabularyAnalysis keys: 17 (16個詞 + 1個慣用語)
statistics.simpleWords: 8
statistics.moderateWords: 4
statistics.difficultWords: 3
statistics.phrases: 1
vocabularyAnalysis keys: 16 (不含慣用語)
idioms.length: 1
```
#### **TC-002: 輸入驗證測試**
@ -623,7 +791,17 @@ AI升級:
---
**文件版本**: v1.0
**文件版本**: v1.1 (更新AI模型和模型定義)
**API版本**: v1
**最後更新**: 2025-01-25
**下次審查**: 2025-02-25
**更新記錄**:
- v1.1: 修正AI模型名稱為Gemini 1.5 Flash
- v1.1: 移除VocabularyAnalysis中tags欄位
- v1.1: 新增IdiomDto和ApiErrorResponse模型定義
- v1.1: 移除userLevel參數簡化API
- v1.1: 統一回應格式範例
- v1.1: 新增完整的AI Prompt設計規格和版本管理
- v1.1: 同步更新後端DTO模型實現移除Tags、IsIdiom、UserLevel欄位
- v1.1: 移除statistics計算邏輯前端自行處理統計

View File

@ -498,7 +498,6 @@ API通信: Fetch API
```json
{
"inputText": "英文句子",
"userLevel": "A2",
"analysisMode": "full"
}
```
@ -508,9 +507,12 @@ API通信: Fetch API
{
"success": true,
"data": {
"WordAnalysis": "詞彙分析對象",
"SentenceMeaning": "句子翻譯",
"GrammarCorrection": "語法修正資料"
"vocabularyAnalysis": "詞彙分析字典對象",
"sentenceMeaning": "句子翻譯字串",
"grammarCorrection": "語法修正資料對象",
"idioms": "慣用語陣列",
"statistics": "統計資料對象",
"metadata": "分析元資料對象"
}
}
```
@ -537,7 +539,7 @@ interface WordAnalysisRequired {
partOfSpeech: string // 必需:詞性
pronunciation: string // 必需:發音
difficultyLevel: string // 必需CEFR等級
isIdiom: boolean // 必需:是否為慣用語
frequency: string // 必需:使用頻率
}
```
@ -727,7 +729,13 @@ interface WordAnalysisOptional {
---
**文件版本**: v1.0
**文件版本**: v1.1 (配合後端API規格更新)
**產品負責人**: DramaLing產品團隊
**最後更新**: 2025-09-21
**下次審查**: 2025-10-21
**最後更新**: 2025-01-25
**下次審查**: 2025-02-25
**更新記錄**:
- v1.1: 移除API請求中的userLevel參數要求
- v1.1: 更新API回應格式為實際實現的格式
- v1.1: 修正資料模型移除isIdiom欄位新增frequency欄位
- v1.1: 保留前端個人化分類邏輯改為從localStorage獲取用戶等級

View File

@ -53,7 +53,7 @@ public class AIController : ControllerBase
// 直接執行 AI 分析,不使用快取
var options = request.Options ?? new AnalysisOptions();
var analysisData = await _geminiService.AnalyzeSentenceAsync(
request.InputText, request.UserLevel, options);
request.InputText, options);
stopwatch.Stop();
analysisData.Metadata.ProcessingDate = DateTime.UtcNow;

View File

@ -8,9 +8,6 @@ public class SentenceAnalysisRequest
[StringLength(300, MinimumLength = 1, ErrorMessage = "輸入文本長度必須在1-300字符之間")]
public string InputText { get; set; } = string.Empty;
[Required]
[RegularExpression("^(A1|A2|B1|B2|C1|C2)$", ErrorMessage = "無效的CEFR等級")]
public string UserLevel { get; set; } = "A2";
public string AnalysisMode { get; set; } = "full";
@ -42,7 +39,6 @@ public class SentenceAnalysisData
public string SentenceMeaning { get; set; } = string.Empty;
public Dictionary<string, VocabularyAnalysisDto> VocabularyAnalysis { get; set; } = new();
public List<IdiomDto> Idioms { get; set; } = new();
public AnalysisStatistics Statistics { get; set; } = new();
public AnalysisMetadata Metadata { get; set; } = new();
}
@ -77,12 +73,10 @@ public class VocabularyAnalysisDto
public string PartOfSpeech { get; set; } = string.Empty;
public string Pronunciation { get; set; } = string.Empty;
public string DifficultyLevel { get; set; } = string.Empty;
public bool IsIdiom { get; set; }
public string Frequency { get; set; } = string.Empty;
public List<string> Synonyms { get; set; } = new();
public string? Example { get; set; }
public string? ExampleTranslation { get; set; }
public List<string> Tags { get; set; } = new();
}
public class IdiomDto
@ -98,23 +92,12 @@ public class IdiomDto
public string? ExampleTranslation { get; set; }
}
public class AnalysisStatistics
{
public int TotalWords { get; set; }
public int UniqueWords { get; set; }
public int SimpleWords { get; set; }
public int ModerateWords { get; set; }
public int DifficultWords { get; set; }
public int Idioms { get; set; }
public string AverageDifficulty { get; set; } = string.Empty;
}
public class AnalysisMetadata
{
public string AnalysisModel { get; set; } = "gpt-4";
public string AnalysisVersion { get; set; } = "1.0";
public string AnalysisModel { get; set; } = "gemini-1.5-flash";
public string AnalysisVersion { get; set; } = "2.0";
public DateTime ProcessingDate { get; set; } = DateTime.UtcNow;
public string UserLevel { get; set; } = string.Empty;
}
public class ApiErrorResponse

View File

@ -6,7 +6,7 @@ namespace DramaLing.Api.Services;
public interface IGeminiService
{
Task<SentenceAnalysisData> AnalyzeSentenceAsync(string inputText, string userLevel, AnalysisOptions options);
Task<SentenceAnalysisData> AnalyzeSentenceAsync(string inputText, AnalysisOptions options);
}
public class GeminiService : IGeminiService
@ -31,20 +31,19 @@ public class GeminiService : IGeminiService
_httpClient.DefaultRequestHeaders.Add("User-Agent", "DramaLing/1.0");
}
public async Task<SentenceAnalysisData> AnalyzeSentenceAsync(string inputText, string userLevel, AnalysisOptions options)
public async Task<SentenceAnalysisData> AnalyzeSentenceAsync(string inputText, AnalysisOptions options)
{
var startTime = DateTime.UtcNow;
try
{
_logger.LogInformation("Starting sentence analysis for text: {Text}, UserLevel: {UserLevel}",
inputText.Substring(0, Math.Min(50, inputText.Length)), userLevel);
_logger.LogInformation("Starting sentence analysis for text: {Text}",
inputText.Substring(0, Math.Min(50, inputText.Length)));
// 符合產品需求規格的結構化 prompt
var prompt = $@"You are an English learning assistant. Analyze this sentence for a {userLevel} CEFR level learner and return ONLY a valid JSON response.
var prompt = $@"You are an English learning assistant. Analyze this sentence and return ONLY a valid JSON response.
**Input Sentence**: ""{inputText}""
**Learner Level**: {userLevel}
**Required JSON Structure:**
{{
@ -113,7 +112,7 @@ public class GeminiService : IGeminiService
}
// 直接使用 AI 的回應創建分析數據
var analysisData = CreateAnalysisFromAIResponse(inputText, userLevel, aiResponse);
var analysisData = CreateAnalysisFromAIResponse(inputText, aiResponse);
var processingTime = (DateTime.UtcNow - startTime).TotalSeconds;
_logger.LogInformation("Sentence analysis completed in {ProcessingTime}s", processingTime);
@ -127,7 +126,7 @@ public class GeminiService : IGeminiService
}
}
private SentenceAnalysisData CreateAnalysisFromAIResponse(string inputText, string userLevel, string aiResponse)
private SentenceAnalysisData CreateAnalysisFromAIResponse(string inputText, string aiResponse)
{
_logger.LogInformation("Creating analysis from AI response: {ResponsePreview}...",
aiResponse.Substring(0, Math.Min(100, aiResponse.Length)));
@ -166,15 +165,12 @@ public class GeminiService : IGeminiService
GrammarCorrection = ConvertGrammarCorrection(aiAnalysis),
Metadata = new AnalysisMetadata
{
UserLevel = userLevel,
ProcessingDate = DateTime.UtcNow,
AnalysisModel = "gemini-1.5-flash",
AnalysisVersion = "2.0"
}
};
// 計算統計
analysisData.Statistics = CalculateStatistics(analysisData.VocabularyAnalysis, analysisData.Idioms, userLevel);
return analysisData;
}
@ -182,7 +178,7 @@ public class GeminiService : IGeminiService
{
_logger.LogError(ex, "Failed to parse AI response as JSON: {Response}", aiResponse);
// 回退到舊的處理方式
return CreateFallbackAnalysis(inputText, userLevel, aiResponse);
return CreateFallbackAnalysis(inputText, aiResponse);
}
}
@ -205,7 +201,6 @@ public class GeminiService : IGeminiService
Synonyms = aiWord.Synonyms ?? new List<string>(),
Example = aiWord.Example,
ExampleTranslation = aiWord.ExampleTranslation,
Tags = new List<string> { "ai-analyzed", "gemini" }
};
}
@ -260,7 +255,7 @@ public class GeminiService : IGeminiService
};
}
private SentenceAnalysisData CreateFallbackAnalysis(string inputText, string userLevel, string aiResponse)
private SentenceAnalysisData CreateFallbackAnalysis(string inputText, string aiResponse)
{
_logger.LogWarning("Using fallback analysis due to JSON parsing failure");
@ -271,12 +266,10 @@ public class GeminiService : IGeminiService
VocabularyAnalysis = CreateBasicVocabularyFromText(inputText, aiResponse),
Metadata = new AnalysisMetadata
{
UserLevel = userLevel,
ProcessingDate = DateTime.UtcNow,
AnalysisModel = "gemini-1.5-flash-fallback",
AnalysisVersion = "1.0"
AnalysisVersion = "2.0"
},
Statistics = new AnalysisStatistics()
};
}
@ -303,7 +296,6 @@ public class GeminiService : IGeminiService
Synonyms = new List<string>(),
Example = null,
ExampleTranslation = null,
Tags = new List<string> { "fallback-analysis" }
};
}
@ -335,21 +327,6 @@ public class GeminiService : IGeminiService
return "B2";
}
private AnalysisStatistics CalculateStatistics(Dictionary<string, VocabularyAnalysisDto> vocabulary, List<IdiomDto> idioms, string userLevel)
{
var stats = new AnalysisStatistics
{
TotalWords = vocabulary.Count,
UniqueWords = vocabulary.Count,
SimpleWords = vocabulary.Count(kvp => kvp.Value.DifficultyLevel == "A1"),
ModerateWords = vocabulary.Count(kvp => kvp.Value.DifficultyLevel == "A2"),
DifficultWords = vocabulary.Count(kvp => !new[] { "A1", "A2" }.Contains(kvp.Value.DifficultyLevel)),
Idioms = idioms.Count, // 基於實際 idioms 陣列計算
AverageDifficulty = userLevel
};
return stats;
}
private async Task<string> CallGeminiAPI(string prompt)
{

File diff suppressed because it is too large Load Diff