手把手教你使用C#创建一个WebSearchAgent

笔记哥 / 05-15 / 9点赞 / 0评论 / 256阅读
## PocketFlowSharp介绍 最近我对PocketFlow比较感兴趣,不仅是因为它是一个极简的LLM框架,更加让我觉得很不错的地方在于作者提供了很多方便学习的例子,就算没有LLM应用开发经验,也可以快速上手。 我比较喜欢C#,也想为C#生态做一点小小的贡献,因此创建了PocketFlowSharp项目。 PocketFlowSharp项目的愿景是助力.NET开发者开发LLM应用。 在我个人在学习实践的过程中,我发现很多项目不是那么“新手友好的”,这也没有办法,开发者更关注的是代码实现,文档写起来确实也很费劲。 在PocketFlowSharp项目中,我希望可以做到足够的新手友好,提供一些只要简单配置即可跑起来的示例,并且每个示例是独立的。 PocketFlowSharp项目地址:https://github.com/Ming-jiayou/PocketFlowSharp ![image-20250515135028833](https://cdn.res.knowhub.vip/c/2505/16/022a73c4.png?G1YAAMTydJz4%2b%2bMbuo06fJsoEpoBiSyCSgnr9Z6z9i3y%2fU4wx2e0Pn1%2f%2bEvr0yWxFGYIQYMheDU1Apa1Br1SqahJ4xoO) ## 构建Web\_Search\_Agent 今天介绍的是Web\_Search\_Agent。 代码在:https://github.com/Ming-jiayou/PocketFlowSharp/tree/main/PocketFlowSharpSamples.Console/Web_Search_Agent ### 效果 先来看下效果: ![](https://cdn.res.knowhub.vip/c/2505/16/47726d60.gif?G1cAAMTc2vOlm9euRr%2bnQUuwDLQZsEgjqJSwXs9a%2fzxF2htQWL6%2b2kfMD7%2bpfYRscIepQEElUigshCq5e0KBGw%2fScrQb) ![image-20250515135915401](https://cdn.res.knowhub.vip/c/2505/16/891c8c19.png?G1cAAMTW3Dgp8EJR22gDdWfqnTYDFmkElRLW6917rpvo%2bwMMzU%2bvbcT68JvaRlCBO5QJDGNDCmJiYDYtZxJV%2bOUHNM8e) ![image-20250515135951723](https://cdn.res.knowhub.vip/c/2505/16/d6383691.png?G1cAAER17rxgpWyIfice0wSBBJsBizSCSgnr9fz%2f2pfI%2bzlBjfdoffr%2b8JvWp0tmrVQIQYMhhGTJCJgeCKkUaj4rEddw) ### 配置 运行这个示例非常简单,我提供了.env.example,如下所示: ![image-20250515140146258](https://cdn.res.knowhub.vip/c/2505/16/ec4eef18.png?G1YAAMTsdJzIJxGl26hD2jvFHc2ARBZBpYT1es9Z%2byb6fhdIis9offr%2b8JfWp5NKKZJAAjEYgmdjE8C0InDVzFdOGtdw) 用于配置LLM与BraveSearchApi,目前BraveSearchApi的免费额度是一个月2000次。 将其重命名为.env,注意需要将其设置为嵌入的资源,如下所示: ![image-20250515140351370](https://cdn.res.knowhub.vip/c/2505/16/166c195a.png?G1cAAETd9Ly0adoy2Xe6ww2cCtoMWKQRVEpYr%2fectW%2bR728EPT%2bj9tn2h9%2fUPpsYS6FDCAYCKWhoEAh3Swq6q9nleY0G) ### 实现 在经过简单的配置之后,应该已经能够跑通了,为了让感兴趣的人更好的学习,我这里来介绍一下具体的实现。 Web\_Search\_Agent说是Agent其实我觉得更像是个工作流。PocketFlowSharp相当于一个简单的流程框架,将节点根据一个string类型的action进行连接。 Web\_Search\_Agent的整体流程如下所示: ![image-20250513150525245](https://cdn.res.knowhub.vip/c/2505/16/6d11a050.png?G1YAAMTydJz485x%2buo06fJsoEpoBiSyCSgnr9Z6z9i3y%2fa7QEp%2fR%2bvT94S%2btT5esZlogCiWI4BMTFWC5GJhqNQMR13A%3d) 首先创建一个Flow: ![image-20250515141310082](https://cdn.res.knowhub.vip/c/2505/16/2a4b305a.png?G1cAAETd9Ly0aWqZ7Dvd0Q2cCtoMWKQRVEpYr%2fectW%2bR7w%2bCJT%2bj9Rn7w29anyHGWlkgBB2OFNTVCbiZJbVLtarD8xoB) 将节点进行连接有两种方式。 一种是: ```csharp decide.Next(search, "search"); ``` 另一种是: ```csharp _ = search - "decide" - decide; ``` 这是因为实现了运算符重载,具体可看此处: ![image-20250515141513915](https://cdn.res.knowhub.vip/c/2505/16/8f665201.png?G1YAAMTydJz4%2b%2bODbqMO3yaKRJsBiSyCSgnr9fz%2fPpfI%2bznBHO%2fZx%2fLz4S99LJfEWpkhBA2G4NXUCFjKCKqlsWizuKcD) ![image-20250515141545436](https://cdn.res.knowhub.vip/c/2505/16/e74068a7.png?G1YAAMTydJz4c%2b9Fu406fJsoEpoBiSyCSgnr9Z6z9i3y%2fU4wx2e0Pn1%2f%2bEvr00VZKzOEoMEQfLJkBEwzQuKlpaBoXMMB) 运行Flow的时候,节点之间的编排在这里: ![image-20250515141752718](https://cdn.res.knowhub.vip/c/2505/16/e382682a.png?G1YAAETn9LwUKELcvtMdbIlTE20GJLIIKiWs13vO2jfR9wcYJT%2bj9Rn7w19an0GKWlGYwDA2JC8mBmZT9XSZiIuL5jUC) 每一个节点的运行流程在这里: ![image-20250515141848013](https://cdn.res.knowhub.vip/c/2505/16/e5c7b5e1.png?G1YAAER17rxgXd0M%2bp14TBMEEmgGJLIIKiWs17v3XLfI9wdBy0%2bvbcT68JfaRsjBUmgQgg5H8urqBNxMU1HHxVMtzx4%3d) 首先会运行决定节点的prep: ![image-20250515142005794](https://cdn.res.knowhub.vip/c/2505/16/7e86e98c.png?G1cAAETn9LwUKLDovtMdbIlTE20GLNIIKiWs13vO2jfR9wcYlp%2fR%2boz94TetzyBFKTAmMJwdKYiLg9kNkgRVVMtVLa8R) 获取上下文(当前还没有上下文)与问题。 决定节点的exec: ![image-20250515142134681](https://cdn.res.knowhub.vip/c/2505/16/9266f105.png?G1cAAER17rxgo6OCfice0wSBBJsBizSCSgnr9fz%2f2pfI%2bzkUOd6j9en7w29any6GWpFVoKASISQmQpXGElKxo5B2WlzDAQ%3d%3d) 获取prep的问题与上下文,判断是搜索还是回答。 决定节点的post: ![image-20250515142256186](https://cdn.res.knowhub.vip/c/2505/16/191f4f47.png?G1UAAER17rxgpTBlfice0wSBBJsBiSyCSgnr9fz%2f2pfI%2bznBEu%2fR%2bvT94S%2btT5fMWlkgBBWKoJMmJaDZcjDFaXbUuIYD) 根据LLM做出的决定选择行动。 这里LLM选择的是search。 根据返回的search寻找下一个节点也就是搜索节点,然后同样执行prep、exec与post。 Search节点的prep: ![image-20250515142827316](https://cdn.res.knowhub.vip/c/2505/16/52216ca2.png?G1cAAETd9Ly0aWyH7Dvd0Q2cCtoMWKQRVEpYr%2fectW%2bR7w%2bClp%2fR%2boz94Tetz5DCWmkQgg5HCurqBNyMSb0YUHl5XiMA) 从共享存储中获取要搜索的内容。 Search节点的exec: ![image-20250515142901028](https://cdn.res.knowhub.vip/c/2505/16/3f466b68.png?G1YAAMTsdJxIfBK026hD2jvFHc2ARBZBpYT1es9Z%2byb6fgdD4zNan74%2f%2fKX16SSoFcoEhrEh%2bGTJwGyacxDopSolxzUc) 返回网络搜索结果: ![image-20250515142957989](https://cdn.res.knowhub.vip/c/2505/16/40bd84dc.png?G1YAAER17rxgXd0I%2bp14TBMEEmwGJLIIKiWs1%2fP%2fa18i7xcELd%2bj9Rn7w19anyEHa6VBCDocyaurE3Dzkkz1LCA0rxE%3d) Search节点的post: ![image-20250515143035822](https://cdn.res.knowhub.vip/c/2505/16/ad9dc6aa.png?G1cAAMTydJz4%2b%2bNFu406fJsoEpoBizSCSgnr9Z6z9i3y%2fU4wx2e0Pn1%2f%2bE3r0yWxVmYIQYMhBDU1ApYTgl5WErWYxjUc) 将网络搜索的结果放到共享存储的context中。 然后返回"decide"又会回到决定节点。 决定节点这次选择的是answer: ![image-20250515143237521](https://cdn.res.knowhub.vip/c/2505/16/80bdd0f2.png?G1YAAER17rxgo6O6%2bJ14TBMEEmwGJLIIKiWs1%2fP%2fa18i7xdQ1HyP1mfsD39pfYYY3FFVoKASyRcWQpXVLJFmfh5e8hoB) 就会转到回答节点。 回答节点的prep: ![image-20250515143339979](https://cdn.res.knowhub.vip/c/2505/16/dca9e70a.png?G1cAAER17rxgpWyKfice0wSBBJoBizSCSgnr9e491y3y%2fU5Q49NrG74%2b%2fKa24ZJZChVC0GAIIVkyAqZ6hVQMOGA44%2bwO) 从共享存储中获取问题与上下文。 回答节点的exec: ![image-20250515143411477](https://cdn.res.knowhub.vip/c/2505/16/59c0e197.png?G1YAAETn9LyUChDZvtMdbIlTE20GJLIIKiWs13vO2jfR9weDNT%2bj9Rn7w19an0HC7qwgBhsMyRcrxoBJ9aRSxP2C5jUC) 根据问题与上下文进行回答。 回答节点的post: ![image-20250515143536617](https://cdn.res.knowhub.vip/c/2505/16/3905a7e8.png?G1cAAMTydJz4%2b3tf0m3UQZsoEpoBizSCSgnr9Z6z9i3y%2fY1gis%2bofbb94Te1zybGUpggBB2OENTVCbhlDVos0%2b0C4hoN) 将答案存入共享存储中。 最后从共享存储中提取出答案: ![image-20250515143702208](https://cdn.res.knowhub.vip/c/2505/16/317d22df.png?G1cAAETd9Ly0bUyZ7Dvd0Q2cCtoMWKQRVEpYr%2fectW%2bR7w8omJ%2fR%2boz94Tetz5CCWkEVKFwdKZibQ9VJJiPLBXOreY0A) 以上就是整个流程,希望能够让感兴趣的朋友快速理解。