news 2026/9/16 15:50:39

用 Chainlit 构建多智能体聊天应用:multi_agent_orchestrator 搭配 Bedrock 与 Ollama 的实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 Chainlit 构建多智能体聊天应用:multi_agent_orchestrator 搭配 Bedrock 与 Ollama 的实战指南

用 Chainlit 构建多智能体聊天应用:multi_agent_orchestrator 搭配 Bedrock 与 Ollama 的实战指南

【免费下载链接】agent-squadFlexible and powerful framework for managing multiple AI agents and handling complex conversations项目地址: https://gitcode.com/GitHub_Trending/mu/agent-squad

本指南基于开源仓库agent-squad中的examples/chat-chainlit-app示例,完整讲解如何用 Chainlit(说明:此处仅指 Python 聊天 UI 框架 Chainlit,非仓库内部模块)与multi_agent_orchestrator编排框架搭建一个支持智能路由、流式输出与多轮对话的多智能体聊天应用。读完本文,你将掌握从环境准备、依赖安装、应用启动到核心代码拆解的全流程,并能把一个同时跑在 Amazon Bedrock(云上 Agent)与 Ollama(本地模型)之上的三 Agent 应用完整跑通。

该示例同时展示了本仓库 Python 版编排框架(位于 python/src/multi_agent_orchestrator)的典型用法:用 Bedrock 上的 Claude 模型做意图分类,将用户问题路由到"技术 Agent""旅行 Agent""健康 Agent"之一,其中健康 Agent 由本地 Ollama 的 Llama 3.1 模型驱动,形成"云 + 本地"混合的多 Agent 体系。

示例应用概览:三个 Agent 与一个分类器

在动手前,先看清这个示例要解决什么问题。应用启动后,用户输入任意一句话,系统会先经过一个分类器判断意图,再把请求交给最合适的 Agent 处理:

组件名称底层模型运行位置
分类器BedrockClassifieranthropic.claude-3-haiku-20240307-v1:0Amazon Bedrock
AgentTech Agent(技术)anthropic.claude-3-sonnet-20240229-v1:0Amazon Bedrock
AgentTravel Agent(旅行)anthropic.claude-3-sonnet-20240229-v1:0Amazon Bedrock
AgentHealth Agent(健康)llama3.1:latest本地 Ollama

三个 Agent 的职责描述定义在 examples/chat-chainlit-app/agents.py 中,分类器正是依赖这些描述来做意图匹配(详见后文"分类器的工作原理"一节)。而示例给出的四类测试问题,正好覆盖了三种 Agent 及"多轮追问"场景:

  • 问 Seattle 值得去的地方 → 应路由到Travel Agent(Bedrock);
  • 问 Seattle 有哪些科技公司 → 应路由到Tech Agent(Bedrock);
  • 问 Seattle 什么花粉导致过敏 → 应路由到Health Agent(本地 Ollama);
  • 针对旅行 Agent 的上一轮回答继续追问 → 应保持在同一 Agent 继续多轮对话。

环境准备(Prerequisites)

按照 examples/chat-chainlit-app/README.md 的要求,运行该应用需要满足以下前置条件:

  • Python 3.7 及以上版本,且pip(Python 包安装器)可用;
  • 本地已安装并运行 Ollama,且已拉取 ollamaAgent.py 中指定的模型llama3.1:latest,可通过ollama pull llama3.1:latest提前下载;
  • AWS 凭证与 Bedrock 模型访问权限:由于分类器与两个 Bedrock Agent 都调用bedrock-runtime,需要在本机配置好 AWS 凭证(如环境变量AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_REGION,或使用~/.aws/credentials)。从源码看,bedrock_classifier.py 在未显式传入region时会读取AWS_REGION环境变量,bedrock_llm_agent.py 同样如此;同时请确保你的账号已开通上述 Claude 3 Haiku / Sonnet 模型的调用权限。

安装依赖与启动应用

README 给出的安装与运行步骤如下,本文在保留原步骤的基础上补充了关键说明。

1. 克隆仓库(如尚未完成)

git clone <repository-url> cd <repository-directory>

2. 创建并激活虚拟环境(推荐)

python -m venv venv

激活虚拟环境:

  • Windows:

    venv\Scripts\activate
  • macOS / Linux:

    source venv/bin/activate

3. 安装依赖

进入示例目录并安装依赖:

pip install -r requirements.txt

examples/chat-chainlit-app/requirements.txt 中锁定的依赖如下:

依赖包版本作用
chainlit1.3.2聊天应用前端 UI 与消息流框架
multi_agent_orchestrator0.1.2本仓库 Python 版多 Agent 编排框架(PyPI 包)
ollama0.3.3调用本地 Ollama 服务的 Python 客户端
pydantic2.10.1数据校验,Chainlit 运行时依赖

注意:multi_agent_orchestrator==0.1.2是从 PyPI 安装的发布包,与仓库内 python 目录下的源码对应。如果你希望直接使用仓库源码,也可以将python/目录加入PYTHONPATH或按 python/README.md 的方式安装。

4. 运行应用

chainlit run app.py -w

其中-w(watch)参数表示启用热重载,修改代码后应用自动重启,非常适合开发调试。应用启动后,Chainlit 会在本地开启一个 Web 聊天界面(默认端口8000),直接在浏览器中打开即可对话。

5. 其他注意事项

  • README 提醒:确保multi_agent_orchestrator或其他组件所需的环境变量、配置文件已正确设置(主要指 AWS 凭证,见上文);
  • 如果安装包时遇到问题,请确认 Python 与 pip 版本为最新,并优先在干净的虚拟环境中安装。

核心代码拆解:app.py 中的编排器初始化

应用入口 examples/chat-chainlit-app/app.py 是理解整个编排流程的关键。它依次完成了四件事:初始化分类器、初始化编排器、注册 Agent、绑定 Chainlit 事件。

分类器初始化

custom_bedrock_classifier = BedrockClassifier(BedrockClassifierOptions( model_id='anthropic.claude-3-haiku-20240307-v1:0', inference_config={ 'maxTokens': 500, 'temperature': 0.7, 'topP': 0.9 } ))

这里用 Haiku 模型作为分类器(比 Sonnet 更便宜、更快),并显式配置了采样参数。对照 bedrock_classifier.py 的源码,inference_config各字段的含义与默认值为:

参数示例中的值源码默认值说明
maxTokens5001000生成的最大 token 数
temperature0.70.0采样温度,越高越随机;分类任务通常建议低值以保持确定性
topP0.90.9核采样概率阈值
stopSequences未设置[]停止序列列表

model_id未指定时默认使用anthropic.claude-3-5-sonnet-20240620-v1:0(该常量定义在 types/types.py)。

编排器初始化与配置项说明

orchestrator = MultiAgentOrchestrator(options=OrchestratorConfig( LOG_AGENT_CHAT=True, LOG_CLASSIFIER_CHAT=True, LOG_CLASSIFIER_RAW_OUTPUT=True, LOG_CLASSIFIER_OUTPUT=True, LOG_EXECUTION_TIMES=True, MAX_RETRIES=3, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=False, MAX_MESSAGE_PAIRS_PER_AGENT=10 ), classifier=custom_bedrock_classifier )

OrchestratorConfig的全部字段及源码默认值定义在 types/types.py 中,逐一说明如下:

配置项示例值源码默认值作用
LOG_AGENT_CHATTrueFalse是否打印每个 Agent 的对话历史
LOG_CLASSIFIER_CHATTrueFalse是否打印分类器看到的对话历史
LOG_CLASSIFIER_RAW_OUTPUTTrueFalse是否打印分类器的原始输出
LOG_CLASSIFIER_OUTPUTTrueFalse是否打印"分类意图"结果(命中 Agent、置信度)
LOG_EXECUTION_TIMESTrueFalse是否统计并打印各阶段执行耗时
MAX_RETRIES33分类/调用的最大重试次数
USE_DEFAULT_AGENT_IF_NONE_IDENTIFIEDFalseTrue分类器未选中任何 Agent 时,是否回退到默认 Agent。示例置为False,即未命中时直接返回"无法处理"提示
CLASSIFICATION_ERROR_MESSAGE未设置None分类出错时的提示文本
NO_SELECTED_AGENT_MESSAGE未设置内置兜底文案未选中 Agent 时返回给用户的提示
GENERAL_ROUTING_ERROR_MSG_MESSAGE未设置None路由过程异常时的提示文本
MAX_MESSAGE_PAIRS_PER_AGENT10100每个 Agent 保留的最大消息对数,超出后裁剪历史

从 orchestrator.py 的源码可以看出,options也可以直接传字典,框架会按OrchestratorConfig的字段自动过滤无效键。若不传classifier,框架在检测到bedrock相关依赖可用时会默认创建BedrockClassifier,否则抛出异常要求显式提供分类器。

注册 Agent

orchestrator.add_agent(create_tech_agent()) orchestrator.add_agent(create_travel_agent()) orchestrator.add_agent(create_health_agent())

add_agent会把 Agent 加入内部字典self.agents,并同步调用classifier.set_agents(self.agents)——这一步把每个 Agent 的iddescription注入分类器的提示词模板,是"智能路由"的数据来源(对应 classifier.py 中的set_agents方法)。

Chainlit 事件绑定

@cl.on_chat_start async def start(): cl.user_session.set("user_id", str(uuid.uuid4())) cl.user_session.set("session_id", str(uuid.uuid4())) cl.user_session.set("chat_history", []) @cl.on_message async def main(message: cl.Message): ...
  • on_chat_start:每次新建会话时生成唯一的user_idsession_id,它们会作为编排器路由与历史存储的维度(见后文);
  • on_message:收到用户消息后先发送一个空消息占位(msg.send()),随后调用编排器核心方法:
response: AgentResponse = await orchestrator.route_request(message.content, user_id, session_id, {})

route_request是编排框架的总入口。对照 orchestrator.py 的源码,其内部调用链为:

  1. classify_request(...):拉取该会话的全部 Agent聊天历史,调用分类器判定意图;
  2. 若无选中 Agent:按USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED决定是否回退默认 Agent,否则返回内置的"无法处理"消息;
  3. agent_process_request(...):通过dispatch_to_agent把请求交给选中 Agent 处理,并先后把用户消息、Agent 回复写入会话存储;
  4. finally中按LOG_EXECUTION_TIMES打印各阶段耗时。

流式响应输出

if isinstance(response, AgentResponse) and response.streaming is False: if isinstance(response.output, str): await msg.stream_token(response.output) elif isinstance(response.output, ConversationMessage): await msg.stream_token(response.output.content[0].get('text')) await msg.update()

AgentResponsestreaming字段来自selected_agent.is_streaming_enabled()(对应 agent.py 中AgentResponse数据类与基类的is_streaming_enabled)。由于示例中三个 Agent 均开启了流式,真正的 token 级流式输出由各 Agent 的回调完成,这里只需在结束时update()刷新消息。

三 Agent 定义与流式回调:agents.py

examples/chat-chainlit-app/agents.py 定义了三个工厂函数,并实现了一个把 token 实时转发到 Chainlit 界面的回调类:

class ChainlitAgentCallbacks(AgentCallbacks): def on_llm_new_token(self, token: str) -> None: asyncio.run(cl.user_session.get("current_msg").stream_token(token))

这是示例中最值得借鉴的集成技巧:AgentCallbacks是框架定义的钩子接口(见 agent.py),LLM 每次吐出新 token 时都会被调用。回调里从 Chainlit 的user_session取出当前消息对象,用asyncio.run把 token 流式推送到前端——因为回调是同步的,而stream_token是异步方法,所以需要asyncio.run桥接。注意app.pyon_message中提前执行了cl.user_session.set("current_msg", msg),回调才能取到它。

三个 Agent 的定义要点:

def create_tech_agent(): return BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Agent", streaming=True, description="Specializes in technology areas including software development, hardware, AI, ...", model_id="anthropic.claude-3-sonnet-20240229-v1:0", callbacks=ChainlitAgentCallbacks() ))
  • Tech Agent / Travel Agent:均使用BedrockLLMAgent,模型为 Claude 3 Sonnet,streaming=True开启流式;
  • Health Agent:使用自定义的OllamaAgent,模型为llama3.1:latest,同样开启流式;
  • description是关键:这段文本既是 Agent 的系统提示词素材,也是分类器选择 Agent 的依据。从 bedrock_llm_agent.py 源码可以看到,BedrockLLMAgent会把namedescription拼进默认提示词模板("You are a {name}. {description}...")作为系统提示词;同时分类器模板中的AGENT_DESCRIPTIONS也由各 Agent 的id: description拼接而成。描述写得越精准、边界越清晰,路由准确率越高。

本地 Agent 实现:ollamaAgent.py

健康 Agent 使用的是示例自定义的 examples/chat-chainlit-app/ollamaAgent.py,它继承框架基类Agent并包装 Ollama Python 客户端:

@dataclass class OllamaAgentOptions(AgentOptions): streaming: bool = True model_id: str = "llama3.1:latest" class OllamaAgent(Agent): async def process_request(self, input_text, user_id, session_id, chat_history, additional_params=None): messages = [ {"role": msg.role, "content": msg.content[0]['text']} for msg in chat_history ] messages.append({"role": ParticipantRole.USER.value, "content": input_text}) if self.streaming: return await self.handle_streaming_response(messages) else: response = ollama.chat(model=self.model_id, messages=messages) return ConversationMessage(role=ParticipantRole.ASSISTANT.value, content=[{"text": response['message']['content']}])

要点解读:

  1. 历史消息重组:框架传入的chat_historyConversationMessage列表,需要先转成 Ollama 所需的[{"role": ..., "content": ...}]字典列表,再追加当前用户输入;
  2. 流式分支handle_streaming_response中以stream=True调用ollama.chat,逐段累加内容并同步触发self.callbacks.on_llm_new_token(...)——这正是 Chainlit 界面逐字输出的来源;
  3. 非流式分支:直接取response['message']['content']包装成ConversationMessage

这个文件的价值在于演示了如何为框架扩展一个全新的本地模型 Agent:只要继承Agent、实现process_request,并在流式路径中正确触发回调,即可无缝接入编排器,与 Bedrock Agent 平级共存。

分类器的工作原理:从 AgentMatcher 到工具调用

为什么同一个route_request能自动把"花粉过敏"分给健康 Agent、把"科技公司"分给技术 Agent?答案藏在分类器的提示词与工具调用机制中。

AgentMatcher 系统提示词

classifier.py 内置了一套名为AgentMatcher的系统提示词模板,核心逻辑包括:

  • 根据用户输入从<agents>列表中挑选最合适的 Agent 类型;
  • 特殊处理追问:模板明确要求"如果用户输入是上一轮对话的延续(如 yes、ok、I want to know more、数字答案等),则沿用上一轮选中的 Agent"——这正是 README 中第 4 个测试问题(追问旅行 Agent)能保持在同一 Agent 的原因;
  • 对每个候选输出userinputselected_agentconfidence字段;
  • 模板中{{AGENT_DESCRIPTIONS}}{{HISTORY}}两个占位符,分别由set_agents注入的 Agent 描述列表和set_history格式化的历史消息填充(对应update_system_prompt方法)。

结构化的工具调用

bedrock_classifier.py 在调用模型时定义了一个名为analyzePrompt的 Bedrock tool,输入 Schema 包含userinputselected_agentconfidence三个字段。对 Anthropic 系模型还会设置toolChoice强制模型调用该工具;拿到toolUse后,通过get_agent_by_id(tool_use['input']['selected_agent'])映射回具体 Agent,并携带置信度构造ClassifierResult。也就是说,分类结果不是自由文本,而是经过工具调用产出的结构化数据,这让路由结果稳定、可解析。

路由兜底

在 orchestrator.py 的classify_request中,若selected_agent为空且配置了USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=True,会通过get_fallback_result()回退到default_agent。示例中该选项为False,因此无法识别的问题会得到"无法确定如何处理"的礼貌回复。

会话历史与多轮追问:存储层如何工作

示例没有显式传入storage,因此框架默认使用InMemoryChatStorage(内存存储,见 in_memory_chat_storage.py)。它按user_id#session_id#agent_id作为 key 组织对话,特点包括:

  • 按 Agent 隔离历史:每个 Agent 只看到自己参与过的对话(fetch_chat按 Agent 维度取历史),而分类器通过fetch_all_chats看到该会话下所有 Agent 的完整历史,并给助手消息加上[agent_id]前缀,以便判断当前对话上下文;
  • 去重与裁剪:连续相同角色(如两条相邻 user 消息)不重复保存;保存时按MAX_MESSAGE_PAIRS_PER_AGENT裁剪历史(示例配置为 10 对);
  • 时间戳排序:消息带毫秒时间戳,fetch_all_chats会按时间排序,保证多 Agent 交错对话的顺序正确。

这也解释了 README 中"追问旅行 Agent"测试问题的实现基础:你的追问会与之前旅行 Agent 的上下文一起进入分类器,分类器据历史沿用原 Agent,再把完整历史交给该 Agent 生成连贯的续答。如果你需要持久化(如跨会话保留历史),框架还提供了 DynamoDB 存储 与 SQL 存储 等实现,可参考 storage 概览文档。

完整运行验证与预期效果

按前面步骤启动后,可用 README 给出的四类问题依次验证:

  1. 输入"What are some best places to visit in Seattle?",观察终端日志中Classified Intent显示命中travel-agent,浏览器收到 Claude 3 Sonnet 生成的旅行建议(流式输出);
  2. 输入"What are some cool tech companies in Seattle",应命中tech-agent
  3. 输入"What kind of pollen is causing allergies in Seattle?",应命中health-agent,由本地 Ollama 的 Llama 3.1 流式回答——这也是验证 Ollama 服务与模型是否就绪的最快方式;
  4. 针对第 1 问继续追问(如"Tell me more about the food there"),应保持路由到travel-agent并参考上一轮上下文作答。

调试时善用app.py中开启的日志开关:LOG_CLASSIFIER_OUTPUT=True会打印命中的 Agent 与置信度,LOG_EXECUTION_TIMES=True会打印"意图分类"与"Agent 处理"两个阶段的耗时,方便快速定位路由不准或响应缓慢的问题。

常见问题与排查建议

  • 找不到multi_agent_orchestrator模块:确认requirements.txt已安装,或检查 Python 解释器是否为当前虚拟环境;
  • 分类器报 AWS 相关错误:确认 AWS 凭证已配置且 Bedrock 模型可用,可先单独用boto3调用一次converse验证权限;
  • 健康 Agent 无响应或报连接错误:确认 Ollama 服务已启动(ollama serve)且已拉取llama3.1:latest
  • 路由结果不稳定:尝试调低分类器的temperature(分类任务推荐接近 0),或细化各 Agent 的description边界;
  • 追问没有保持同一 Agent:检查MAX_MESSAGE_PAIRS_PER_AGENT是否过小导致历史被裁剪,历史不足时分类器难以判断上下文。

至此,一个"Bedrock 云上 Agent + Ollama 本地 Agent + Claude 智能路由 + Chainlit 流式界面"的完整多智能体应用就搭建完成了。你可以在此基础上继续探索本仓库的其他 Agent 类型(如 Amazon Bedrock Agent、Bedrock Flows)以及 编排器更多用法,把它扩展成适合自己业务场景的多 Agent 助手。

【免费下载链接】agent-squadFlexible and powerful framework for managing multiple AI agents and handling complex conversations项目地址: https://gitcode.com/GitHub_Trending/mu/agent-squad

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 15:49:20

51job招聘数据爬虫与可视化分析工程实践

简介&#xff1a;本资源是一份高质量的Python爬虫与数据可视化综合实践项目&#xff0c;面向高校计算机、数据科学相关专业学生及初学者&#xff0c;解决课程设计与期末大作业中“从数据采集到分析呈现”全流程实践难题。压缩包共39个文件&#xff0c;含3个核心Python脚本&…

作者头像 李华
网站建设 2026/9/16 15:47:30

ESP8266 NON-OS SDK 开发实战:资源受限嵌入式场景的轻量级方案

简介&#xff1a;本资源是一套面向嵌入式初学者与物联网开发者的ESP8266 NON-OS SDK实战例程合集&#xff0c;聚焦无操作系统环境下的底层Wi-Fi通信与外设控制&#xff0c;解决资源受限场景下高效响应、低功耗联网及JSON数据交互等核心问题。压缩包含804个文件&#xff0c;主体…

作者头像 李华
网站建设 2026/9/16 15:45:51

TMDB电影数据分析实战:从数据清洗到ECharts可视化

简介&#xff1a;基于TMDB数据集的电影数据分析项目资料&#xff0c;面向Python数据可视化学习者与课程设计场景&#xff0c;完整覆盖数据读取、清洗、分析及可视化全流程。压缩包共18个文件&#xff0c;约27.43MB&#xff0c;含ipynb源码、4个csv原始数据、6个html交互图表、d…

作者头像 李华
网站建设 2026/9/16 15:45:49

GRBL固件深度解析:从编译配置到实时运动控制调试

简介&#xff1a;本资源为GRBL开源CNC控制器固件的完整源码包&#xff08;grbl-master主分支&#xff09;&#xff0c;面向DIY数控爱好者、嵌入式初学者及小型雕刻机/激光切割机开发者&#xff0c;解决Arduino平台缺乏成熟运动控制方案的问题。压缩包共63个文件&#xff0c;含3…

作者头像 李华