前言
在pocketflow的例子中看到了一個基于LLM的簡歷評估程序的例子,感覺還挺好玩的,為了練習一下C#,我最近使用C#重寫了一個。
準備不同的簡歷:
查看效果:
不足之處是現實的簡歷應該是pdf格式的,后面可以考慮轉化為圖片然后用VLM來試試。
C#學習
在使用C#重寫的過程中,學習到的一些東西。
KeyValuePair學習
使用到了KeyValuePair
。
KeyValuePair<TKey, TValue>
是 C# 中一個用于表示鍵值對的結構體(struct),它是一個泛型類型,可以存儲兩個相關聯的值:一個鍵(key)和一個值(value)。
主要特點:
不可變性:KeyValuePair 是一個只讀結構,一旦創建就不能修改其鍵或值。
泛型實現
public struct KeyValuePair<TKey, TValue>
其中 TKey 是鍵的類型,TValue 是值的類型。
使用示例:
// 創建 KeyValuePair
KeyValuePair<string, int> pair = new KeyValuePair<string, int>("Age", 25);// 訪問鍵和值
Console.WriteLine($"Key: {pair.Key}"); // 輸出: Key: Age
Console.WriteLine($"Value: {pair.Value}"); // 輸出: Value: 25// 在集合中使用
List<KeyValuePair<string, int>> pairs = new List<KeyValuePair<string, int>>
{new KeyValuePair<string, int>("John", 25),new KeyValuePair<string, int>("Mary", 30)
};
KeyValuePair 通常在以下場景中使用:
作為 Dictionary 的枚舉結果
Dictionary<string, int> dict = new Dictionary<string, int>();
dict.Add("One", 1);
dict.Add("Two", 2);foreach (KeyValuePair<string, int> kvp in dict)
{Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");
}
方法返回鍵值對
public KeyValuePair<string, int> GetPersonInfo()
{return new KeyValuePair<string, int>("Age", 25);
}
LINQ 操作中
var dict = new Dictionary<string, int>();
// ... 添加一些數據
var filtered = dict.Where(kvp => kvp.Value > 10).Select(kvp => kvp.Key);
需要注意的是,如果需要可修改的鍵值對集合,應該使用 Dictionary<TKey, TValue> 而不是 KeyValuePair 的集合。Dictionary 提供了更多的功能和更好的性能。KeyValuePair 主要用于表示單個鍵值對關系,特別是在遍歷或傳遞數據時。
YAML內容解析
根據這個提示詞:
string prompt = $@"
評估以下簡歷并確定候選人是否符合高級技術職位的要求。
資格標準:
- 至少具有相關領域的學士學位
- 至少3年相關工作經驗
- 與職位相關的強大技術技能簡歷內容:
{content}請以YAML格式返回您的評估:
```yaml
candidate_name: [候選人姓名]
qualifies: [true/false]
reasons:- [資格認定/不認定的第一個原因]- [第二個原因(如果適用)]
```
";
LLM會返回一個YAML格式的內容,如下所示:
需要解析這個YAML格式內容。
public static Dictionary<string, object> ParseSimpleYaml(string yaml){var result = new Dictionary<string, object>();string[] lines = yaml.Split('\n');string currentKey = null;List<string> currentList = null;int currentIndentation = 0;for (int i = 0; i < lines.Length; i++){string line = lines[i].Trim();if (string.IsNullOrEmpty(line) || line.StartsWith("#"))continue;int indentation = lines[i].TakeWhile(c => c == ' ').Count();// Handle list itemsif (line.StartsWith("- ")){if (currentList == null){currentList = new List<string>();result[currentKey] = currentList;}string listItem = line.Substring(2).Trim();currentList.Add(listItem);continue;}// Parse key-value pairsint colonIndex = line.IndexOf(':');if (colonIndex > 0){currentKey = line.Substring(0, colonIndex).Trim();string value = line.Substring(colonIndex + 1).Trim();currentIndentation = indentation;currentList = null;// Check if this is a multi-line value with |if (value == "|"){StringBuilder multiline = new StringBuilder();i++;// Collect all indented lineswhile (i < lines.Length && (lines[i].StartsWith(" ") || string.IsNullOrWhiteSpace(lines[i]))){if (!string.IsNullOrWhiteSpace(lines[i]))multiline.AppendLine(lines[i].Substring(4)); // Remove indentationi++;}i--; // Step back to process the next non-indented line in the outer loopresult[currentKey] = multiline.ToString().Trim();}else if (!string.IsNullOrEmpty(value)){// Simple key-valueresult[currentKey] = value;}}}return result;}
解析結果:
C#條件運算符和空合并運算符
string name = evaluation.TryGetValue("candidate_name", out var candidateNameValue)? candidateNameValue?.ToString() ?? "未知": "未知";
evaluation.TryGetValue(“candidate_name”, out var candidateNameValue)
-
TryGetValue 是 Dictionary 類的一個方法
-
它嘗試獲取鍵為 “candidate_name” 的值
-
如果找到了值,返回 true,并將值存儲在 candidateNameValue 變量中
-
如果沒找到值,返回 false,candidateNameValue 將為默認值
? 條件運算符(三元運算符)
-
格式為:condition ? value IfTrue : value If False
-
如果 TryGetValue 返回 true,執行 :前面的部分
-
如果 TryGetValue 返回 false,執行 : 后面的 “未知”
candidateNameValue?.ToString()
?. 是空條件運算符
-
如果 candidateNameValue 不為 null,則調用 ToString()
-
如果 candidateNameValue 為 null,則返回 null
?? “未知”
?? 是空合并運算符
- 如果左邊的值為 null,則使用右邊的值(“未知”)
動態類型轉換
List<Dictionary<string, object>> evaluations = null;
var objectList = shared["evaluations"] as List<object>;if (objectList != null)
{evaluations = objectList.Select(item => item as Dictionary<string, object>).Where(dict => dict != null).ToList();
}
這種寫法是因為在C#中處理動態類型轉換時需要特別小心,尤其是在處理集合類型時。
-
首先,shared 是一個 Dictionary<string, object>,這意味著存儲在其中的值都是 object 類型。
-
shared[“evaluations”] 實際上存儲的是一個列表,但由于存在字典中時是 object 類型,我們需要安全地將其轉換回實際的類型。
-
代碼使用了兩步轉換的原因是:
- 第一步:var objectList = shared[“evaluations”] as List;
這一步將 object 轉換為 List,因為列表中的每個元素此時仍然是 object 類型
evaluations = objectList.Select(item => item as Dictionary<string, object>).Where(dict => dict != null).ToList();
這一步將列表中的每個 object 轉換為 Dictionary<string, object>,因為每個評估結果實際上是一個字典
使用 as 操作符而不是直接類型轉換(比如 (List)shared[“evaluations”])的原因是:
-
as 操作符在轉換失敗時會返回 null,而不是拋出異常
-
這樣可以安全地進行類型檢查和轉換,避免運行時錯誤
使用 Where(dict => dict != null) 可以過濾掉任何轉換失敗的項,確保最終的列表中只包含有效的字典對象
這種寫法雖然看起來有點復雜,但它是一種安全和健壯的方式來處理動態類型轉換,特別是在處理可能包含不同類型數據的集合時。這種方式可以:
-
避免運行時異常
-
確保類型安全
-
優雅地處理可能的空值或無效數據
最后
全部代碼已上傳至GitHub,地址:https://github.com/Ming-jiayou/PocketFlowSharp/tree/main/PocketFlowSharpSamples.Console/Resume_Qualification_Demo
推薦閱讀
“Pocket Flow,一個僅用 100 行代碼實現的 LLM 框架”
使用PocketFlow構建Web Search Agent
手把手教你使用C#創建一個WebSearchAgent
使用PocketFlowSharp創建一個Human_Evaluation示例