在大语言模型高速发展的时代,我们面对困难的语义分析任务,通过构建智能体进行处理是一个流行趋势。本文将介绍如何使用 Visual Basic .NET 开发一个多智能体协作系统,用于分析聊天记录中特定人物的荣格八维人格类型。
本文使用 CC-BY-NC-SA 4.0 协议。转载或者 AI 模型/智能体使用请注明出处并标注作者 Nukepayload2。
背景知识
什么是荣格八维?
荣格八维理论是心理学家卡尔·荣格提出的认知功能理论,后发展为多个分支,其中人气较高的是 MBTI。该理论认为人的认知功能可以分为八种,在不同的位置中担任不同的原型。这些功能随着人的成长而发展,并且具有先天性。
本文的软件工具分析的是什么?
工具分析的是对话中的人使用的人格面具。由于人的八个认知功能担任的原型是先天确定的,为了适应环境,人们会发展并使用人格面具。以树作为比喻,如果先天确定的功能偏好是树根,那么人格面具就是顶部的树枝和树叶。在对话中,人格面具以及其中的气质能被 AI 捕捉到,作为输入进行相应的分析。
智能体协作的意义
在荣格八维分析任务中,单一智能体往往难以处理所有方面。通过多个专门化智能体协作,我们可以:
- 提高分析准确性
- 并行处理不同维度的信息
- 更好地控制分析流程
- 精准提供实时反馈
- 避免超长的提示词导致推理费用失控
需求分析
目标用户场景
想象你是一个心理学研究者,需要分析一段多人对话记录中特定人物的人格特征(使用了怎样的人格面具)。你希望系统能够:
- 从聊天记录中识别目标人物的发言
- 分析该人物在八维功能上的倾向性
- 确定各功能在人格中的可能位置
- 生成详细的分析报告
- 允许在分析过程中提供补充信息
- 实时查看分析过程
核心功能需求
- 聊天记录分析 - 能够解析多人对话记录
- 多维度评估 - 并行分析八种认知功能
- 流程控制 - 按照预定义顺序执行分析步骤
- 实时反馈 - 显示分析过程和中间结果
- 交互式答疑 - 分析完成后回答用户问题
- 中断处理 - 允许用户在分析过程中插入信息
系统架构设计
整体架构
系统采用主从式智能体架构:- 主智能体
- ├── 八维得分分析器 (8个子智能体)
- ├── 八维位置分析器
- ├── 报告生成器
- └── 共享工具系统
- ├── 笔记管理工具
- ├── 待办事项工具
- └── 分析工具
复制代码 数据流设计
- 输入阶段:用户提交聊天记录和目标人物
- 分析阶段:并行调用八个子智能体分析各维度
- 整合阶段:综合分析结果确定功能位置
- 输出阶段:生成报告并进入答疑模式
状态管理
系统的智能体是无状态的,它们通过共享上下文(MbtiAnalysisContext)传递信息。- Public Class MbtiAnalysisContext
- Public Property Note As New ConcurrentDictionary(Of String, String)
- Public Property TodoList As New List(Of TodoItem)
- ' 其它内容省略
- End Class
复制代码 开发工具选择
既然是自己做研究,就选自己最擅长的,最大化研究效率。
- VB 17.13(最新的 .NET SDK 9.x 自带)
- Avalonia UI(使用 Nukepayload2.SourceGenerators.AvaloniaUI 提供设计时支持)
- 一些 AI 辅助编程插件
关键技术实现
1. 工作流控制
在智能体的用户界面加载时,初始化分析器上下文并启动主循环。通过一个无限循环,系统持续运行分析任务,直到用户取消操作。当发生取消操作时,系统会根据情况重置分析器或设置中断请求标志。- Private Async Sub MbtiStatelessAnalyzerView_Loaded() Handles Me.Loaded
- ' 设计时和重入处理(省略)
- ' 启动分析器
- _analyzer.ResetContext()
- Do
- Try
- Await _analyzer.RunAnalysisLoopAsync(_cancelSrc.Token)
- Catch ex As OperationCanceledException When ex.CancellationToken = _cancelSrc.Token
- ' 省略取消处理
- End Try
- Loop
- End Sub
复制代码
智能体内采用项目管理的方式来追踪待办事项。为了确保分析按正确顺序进行,硬编码待办事项列表。- ' 被 ResetContext 调用
- Private Sub InitializeTodoList()
- With _context.TodoList
- .Add(New TodoItem("1", "收集聊天记录和目标用户", "..."))
- .Add(New TodoItem("2", "八维分数统计", "..."))
- .Add(New TodoItem("3", "八维位置的可能性", "..."))
- .Add(New TodoItem("4", "产生报告", "..."))
- End With
- End Sub
复制代码
主循环会按顺序处理这些任务:- Public Async Function RunAnalysisLoopAsync(cancellationToken As CancellationToken) As Task
- Dim result As String
- Do
- result = Await ProcessMessageAsync(cancellationToken)
- If result <> Nothing Then
- _chatHistoryItems.Add(MessageHistoryItem.CreateBotMessage(result))
- End If
- Loop Until cancellationToken.IsCancellationRequested
- End Function
复制代码
核心的任务处理逻辑在ProcessMessageAsync方法中实现:- Private Async Function ProcessMessageAsync(cancellationToken As CancellationToken) As Task(Of String)
- ' 查找第一个未完成的任务
- Dim currentTask As TodoItem = _context.TodoList.FirstOrDefault(Function(it) Not it.IsCompleted)
- ' 处理任务
- If currentTask IsNot Nothing Then
- ' 处理用户中断插嘴(简化)
- ' ...
-
- ' 根据任务类型调用相应的处理方法(简化)
- Select Case currentTask.Title
- Case "收集聊天记录和目标用户"
- Return Await CollectChatRecordsAndTargetAsync(cancellationToken)
- Case "八维分数统计"
- Return Await ProcessFunctionScoresAsync(cancellationToken)
- Case "八维位置的可能性"
- Return Await ProcessFunctionPositionsAsync(cancellationToken)
- Case "产生报告"
- Return Await ProcessGenerateReportAsync(cancellationToken)
- Case Else
- Return "错误:未知任务 " & currentTask.Title
- End Select
- ' 清理用户中断插嘴(简化)
- ' ...
- Else
- ' 所有任务都已完成,进入答疑状态
- Return Await AnswerQuestionsAsync(cancellationToken)
- End If
- End Function
复制代码
子任务处理函数包括:
- CollectChatRecordsAndTargetAsync:负责收集用户提供的聊天记录和目标分析对象
- ProcessFunctionScoresAsync:调用八个并行的子智能体计算目标人物在八维功能上的得分
- ProcessFunctionPositionsAsync:分析各功能在人格中的可能位置
- ProcessGenerateReportAsync:综合所有分析结果生成最终报告
- AnswerQuestionsAsync:所有分析任务完成后,进入交互式答疑模式
这些函数会根据任务类型调用相应的工具(主要是查看笔记、调用子智能体和记录笔记),然后标记任务完成并返回结果。
2. 实时推理显示
系统通过数据绑定和事件处理机制实现实时更新。例如,在XAML视图中定义日志显示区域:- <ItemsControl>
- <ItemsControl.ItemTemplate>
- <DataTemplate DataType="local:MbtiAnalysisLogItem">
- <Border BorderBrush="LightGray" BorderThickness="0,0,0,1" Padding="4">
- <Expander IsExpanded="True" HorizontalAlignment="Stretch">
- <Expander.Header>
- <TextBlock Text="{Binding StepName}" FontWeight="Bold" Foreground="Blue"/>
- </Expander.Header>
- <TextBlock Text="{Binding Text}" TextWrapping="Wrap" Margin="0,2,0,0"/>
- </Expander>
- </Border>
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- </ItemsControl>
复制代码
用了数据绑定,智能体只要修改集合和属性,UI 就能自动更新。- Await My.AI.Chat.ChatAsyncWithRetry(chatHistory,
- Sub(response)
- responseBuilder.Append(response)
- ' 更新消息内容,这会让绑定了属性的控件显示最新的数据
- botMessage.Text = responseBuilder.ToString()
- End Sub,
- cancellationToken)
复制代码
智能体 UI 代码还使用 Handles 子句进行消息变更事件处理,使得滚动条自动滚动到最新项:- Private Sub LogItems_CollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs) Handles LogItems.CollectionChanged
- If e.Action = System.Collections.Specialized.NotifyCollectionChangedAction.Add Then
- LastLogItem = LogItems.ElementAtOrDefault(LogItems.Count - 1)
- AutoScrollToLastItem()
- End If
- End Sub
- Private Sub LastLogItem_PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Handles LastLogItem.PropertyChanged
- If e.PropertyName = NameOf(MbtiAnalysisLogItem.Content) Then
- AutoScrollToLastItem()
- End If
- End Sub
复制代码
聊天栏也需要实时显示推理信息。如法炮制,不再赘述。
3. 工具系统实现
所有功能(包括传统算法和智能体)都封装为工具,通过字典进行管理:- Private ReadOnly _tools As New Dictionary(Of String, AIFunction)
- ' 注册工具
- _tools("record_note") = New RecordNoteTool(_context)
- _tools("view_note") = New ViewNoteTool(_context)
- _tools("function_scores") = New FunctionScoresTool(_context)
- _tools("function_positions") = New FunctionPositionsTool(_context)
- _tools("generate_report") = New GenerateReportTool(_context)
- ' ...其它工具
复制代码
工具基类确保统一的调用接口(基于 Microsoft.Extensions.AI.Abstractions):- Public MustInherit Class AIFunctionBase
- Inherits AIFunction
- ' 模仿 WPF 的 Measure ... MeasureCore ... MeasureOverride 设计,确保与不支持 MyClass 的语言兼容,增强扩展性
- Protected Overrides Function InvokeCoreAsync(arguments As AIFunctionArguments, cancellationToken As CancellationToken) As ValueTask(Of Object)
- Return New ValueTask(Of Object)(InvokeCoreAsyncOverride(arguments, cancellationToken))
- End Function
- Protected MustOverride Function InvokeCoreAsyncOverride(arguments As AIFunctionArguments, cancellationToken As CancellationToken) As Task(Of Object)
- End Class
复制代码
为了限制调用权限,我封装了 My.AI.Chat.SetTools 函数,用于控制当前智能体能用哪些工具。如有必要,子智能体也可以暴露类似的函数。
工具调用的自由度权衡
- 对于业务已经确定的场景,提前写好代码进行强制工具调用(直接找到对应工具,调用 InvokeAsync)。这样即使模型能力不足,也能保证正确性。但是这样的弊端是限制了 AI 的创造力。
- 对于需要灵活的场景,把工具列表提供给 AI,让它自己调用。坏处是不稳定。如果你能使用能力极强的模型,推荐使用这种模式。
- 灵活的极致:项目管理和子智能体的调遣,以及请求用户输入,都是工具调用。开发人员只提供工具和指示。甚至允许部分子智能体的指示,以及允许使用哪些工具,都是负责管控的智能体生成的。
- 稳定的极致:把工作流完全硬编码,智能体之间的任何不协调都提前写好代码处理。
4. 中断处理机制
系统支持两种类型的中断处理机制:
- 插嘴中断:用户在分析过程中提供补充信息
- 完全重置:用户清空当前分析任务,重新开始
插嘴中断
插嘴中断主要在ProcessMessageAsync方法中处理,在 OnCancelInference 触发:- ' 按钮事件处理程序
- Private Sub OnCancelInference() Handles ctlChat.CancelInference
- _cancelSrc.Cancel()
- End Sub
- ' ProcessMessageAsync 里面:
- ' 处理用户中断插嘴
- Dim clearInterruptMessage = False
- If UserInterruptRequested Then
- UserInterruptRequested = False
- clearInterruptMessage = True
- Dim interruptMsg = Await UntilUserInputAsync(cancellationToken)
- RecordNote("用户补充", interruptMsg, False, cancellationToken)
- End If
- ' 调用工具(省略)
- ' 清理用户中断插嘴的信息
- If clearInterruptMessage Then
- RecordNote("用户补充", String.Empty, True, cancellationToken)
- End If
复制代码
完全重置
完全重置的事件处理程序:确认要重置才会调用重置- Private Async Sub OnClearMessageList() Handles ctlChat.ClearMessageList
- If _pendingReset Then Return
- Dim confirm = Await MsgBoxAsync("将清空当前分析任务的所有信息和日志,返回到初始状态,是否继续?",
- MsgBoxButtons.YesNo, "清空", TopLevel.GetTopLevel(Me))
- If confirm Then
- _pendingReset = True
- _cancelSrc.Cancel()
- LogItems.Clear()
- End If
- End Sub
复制代码
在MbtiStatelessAnalyzerView_Loaded方法内的循环中,系统会根据取消原因决定是重置分析器还是设置中断请求标志:- Try
- Await _analyzer.RunAnalysisLoopAsync(_cancelSrc.Token)
- Catch ex As OperationCanceledException When ex.CancellationToken = _cancelSrc.Token
- _cancelSrc = New CancellationTokenSource
- If _pendingReset Then
- _pendingReset = False
- _analyzer.ResetContext()
- Else
- _analyzer.UserInterruptRequested = True
- End If
- End Try
复制代码
5. 答疑模式实现
分析完成后,系统进入交互式答疑模式。这其实就是个普通的对话智能体,它不仅拥有分析报告和原始数据,还能自己决定调用哪些工具来补充上下文(比如查看笔记):- Private Async Function AnswerQuestionsAsync(cancellationToken As CancellationToken) As Task(Of String)
- ' 限制工具集,只允许答疑相关的工具
- My.AI.Chat.SetTools(_qaTools)
-
- ' 构建初始上下文消息(简化)
- Dim contextMessage As String = $"以下是你之前提供的聊天记录和分析结果:
- 用户:{targetSpeaker}
- 聊天记录:{chatRecords}
- 八维分数统计结果:{scoreResults}
- 八维位置分析结果:{positionResults}
- 最终报告:{report}"
-
- ' 一问一答循环。简化的代码是:
- Do
- Dim message As ChatMessage = Await UntilUserInputAsync(cancellationToken)
- ' 获取用户问题内容
- Dim question As String = GetMessageContent(message)
- ' 将用户问题添加到聊天历史
- chatHistory.Add(New ChatMessage(ChatRole.User, question))
- Dim responseBuilder As New StringBuilder
- Await My.AI.Chat.ChatAsyncWithRetry(chatHistory,
- Sub(response)
- responseBuilder.Append(response)
- ' 更新消息内容
- botMessage.Text = responseBuilder.ToString()
- End Sub,
- cancellationToken)
- Dim answer As String = responseBuilder.ToString()
- ' 将助手回答添加到聊天历史
- chatHistory.Add(New ChatMessage(ChatRole.Assistant, answer))
- ' 通知用户输入(确保界面更新)
- _requestUserInput()
- ' 循环继续,等待用户下一次提问
- Loop
- End Function
复制代码
一些技术细节
1. 并行处理优化
在分析八维得分时,系统并行调用八个子智能体:- ' 创建任务数组并行化调用每个分析器的 ProcessMessageAsync 方法
- Dim tasks As Task(Of Integer)() = {
- niAgent.ProcessMessageAsync(personToAnalysis, messagesToAnalysis, reportLog, cancellationToken),
- neAgent.ProcessMessageAsync(personToAnalysis, messagesToAnalysis, reportLog, cancellationToken),
- ' ... 其他五个智能体
- }
- ' 等待所有任务完成
- Dim results As Integer() = Await Task.WhenAll(tasks)
复制代码
这种设计充分利用推理 API 并发的特性,显著缩短分析时间。
2. 错误处理与重试机制
每个子智能体都实现了重试机制,确保分析的可靠性:- Dim retryCount As Integer = 0
- Do
- ' 调用AI分析
- ' ... 使用 My.AI.Chat.ChatAsyncWithRetry
- ' 分析字符串以取得 score 的值
-
- ' 验证结果有效性
- If score >= 0 AndAlso score <= 100 Then
- Exit Do ' 成功提取有效分数,退出循环
- End If
-
- ' 增加重试计数
- retryCount += 1
- If retryCount >= maxRetries Then
- Throw New AILowIQException("重试次数过多,请检查AI模型是否正确配置")
- End If
- Loop
复制代码
对于与推理服务的交互,我封装了 My.AI.Chat.ChatAsyncWithRetry,它也做了错误分类和重试。
3. UI线程安全更新
通过Dispatcher确保UI更新在正确的线程上执行:- Private Sub ReportLog(logItem As MbtiAnalysisLogItem)
- ' 在UI线程上更新日志列表
- Avalonia.Threading.Dispatcher.UIThread.Post(
- Sub()
- LogItems.Add(logItem)
- End Sub)
- End Sub
复制代码
4. 上下文清洗过滤
这一步对于稳定性至关重要。对于用户输入,要通过子智能体挑选有效的输入数据,并确保输入数据符合要求。另外,如果上下文过长需要压缩,用这种办法也能减少数据丢失。
例如,在CollectChatRecordsAndTargetAsync函数中,实现了独立的智能体,它辅助验证用户输入的内容是否为有效的聊天记录格式,并提取要分析的目标用户名称。这种方法确保了后续分析步骤能够获得正确的数据,避免了因输入数据格式不正确或不完整而导致的分析错误。- ' 代码经过简化,只展示核心逻辑
- Private Async Function CollectChatRecordsAndTargetAsync(cancellationToken As CancellationToken) As Task(Of String)
- Dim chatRecordOk = False
- Dim analysisTargetOk = False
- Do
- Dim message As ChatMessage = Await UntilUserInputAsync(cancellationToken)
- Dim messageContent As String = GetMessageContent(message)
- If Not chatRecordOk Then
- ' 调用 AI 判断是否是聊天记录
- Dim isChatRecord As Boolean = Await IsChatRecordAsync(message, cancellationToken)
- If isChatRecord Then
- ' 记录用户的聊天记录到笔记中
- RecordNote("聊天记录", messageContent, True, cancellationToken)
- chatRecordOk = True
- End If
- End If
- If Not analysisTargetOk Then
- ' 提取分析目标
- Dim analysisTarget As String = Await ExtractAnalysisTargetAsync(messageContent, cancellationToken)
- If Not String.IsNullOrEmpty(analysisTarget) Then
- RecordNote("分析目标", analysisTarget, True, cancellationToken)
- analysisTargetOk = True
- End If
- End If
- Loop Until chatRecordOk AndAlso analysisTargetOk
- ' 其它逻辑
- End Function
复制代码
总结
通过这个项目,我展示了如何使用 Visual Basic .NET 构建一个复杂的多智能体协作系统。关键设计决策包括:
- 强制工作流:通过硬编码待办事项列表确保分析按预期顺序进行,部分子智能体会强制调用指定的工具
- 具有图形界面:通过事件处理机制提供分析过程的实时可视化
- 工具化设计:将所有功能封装为工具,便于管理和扩展
- 无状态智能体:通过共享上下文实现智能体间的数据传递
- 实时交互体验:提供中断处理和答疑模式,增强交互性
这个系统不仅能够准确分析人格类型,还提供了良好的可解释性和用户体验。这种设计方式也能套用到其它智能体,而不仅局限于荣格八维分析。
本文使用 CC-BY-NC-SA 4.0 协议。转载或者 AI 模型/智能体使用请注明出处并标注作者 Nukepayload2。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |