在大语言模型高速发展的时代,我们面对困难的语义分析任务,通过构建智能体进行处理是一个流行趋势。本文将介绍如何使用 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。