From ad754443b135ab04dc4e38d9caa58a26603b760c Mon Sep 17 00:00:00 2001 From: kuhaha Date: Thu, 9 Jan 2025 10:25:31 +0800 Subject: [PATCH] feat: add eino cn docs (#1182) --- _typos.toml | 1 + content/zh/docs/eino/_index.md | 68 ++ content/zh/docs/eino/core_modules/_index.md | 19 + .../_index.md | 16 + .../chain_and_graph_orchestration/_index.md | 62 ++ .../call_option_capabilities.md | 306 +++++++++ .../callback_manual.md | 447 ++++++++++++ .../callbacks_common_aspects.md | 405 +++++++++++ .../chain_graph_introduction.md | 636 ++++++++++++++++++ .../orchestration_design_principles.md | 273 ++++++++ .../stream_programming_essentials.md | 228 +++++++ .../eino/core_modules/components/_index.md | 73 ++ .../components/chat_model_guide.md | 432 ++++++++++++ .../components/chat_template_guide.md | 249 +++++++ .../document_loader_guide/_index.md | 272 ++++++++ .../document_parser_interface_guide.md | 212 ++++++ .../components/document_transformer_guide.md | 263 ++++++++ .../components/embedding_guide.md | 276 ++++++++ .../core_modules/components/indexer_guide.md | 265 ++++++++ .../components/retriever_guide.md | 293 ++++++++ .../components/tools_node_guide.md | 204 ++++++ .../flow_integration_components/_index.md | 19 + .../multi_agent_hosting.md | 343 ++++++++++ .../react_agent_manual.md | 473 +++++++++++++ .../docs/eino/ecosystem_integration/_index.md | 53 ++ .../chat_model/_index.md | 10 + .../chat_model/chat_model_ark.md | 237 +++++++ .../chat_model/chat_model_ollama.md | 213 ++++++ .../chat_model/chat_model_openai.md | 235 +++++++ .../ecosystem_integration/document/_index.md | 10 + .../document/loader_amazon_s3.md | 110 +++ .../document/loader_local_file.md | 110 +++ .../document/loader_web_url.md | 156 +++++ .../document/parser_html.md | 149 ++++ .../document/parser_pdf.md | 120 ++++ .../document/splitter_markdown.md | 130 ++++ .../document/splitter_recursive.md | 148 ++++ .../document/splitter_semantic.md | 147 ++++ .../ecosystem_integration/embedding/_index.md | 10 + .../embedding/embedding_ark.md | 102 +++ .../embedding/embedding_openai.md | 104 +++ .../indexer_volc_vikingdb.md | 177 +++++ .../retriever_volc_vikingdb.md | 170 +++++ .../eino/ecosystem_integration/tool/_index.md | 10 + .../tool/tool_duckduckgo_search.md | 109 +++ .../tool/tool_googlesearch.md | 115 ++++ content/zh/docs/eino/overview/_index.md | 416 ++++++++++++ content/zh/docs/eino/quick_start/_index.md | 58 ++ .../eino/quick_start/agent_llm_with_tools.md | 256 +++++++ .../complex_business_logic_orchestration.md | 194 ++++++ .../quick_start/simple_llm_application.md | 218 ++++++ static/img/eino/big_challenge_graph.png | Bin 0 -> 337436 bytes static/img/eino/branch_to_draw_loop.png | Bin 0 -> 196682 bytes static/img/eino/callback_in_graph.png | Bin 0 -> 225959 bytes static/img/eino/chain_append_branch.png | Bin 0 -> 313799 bytes static/img/eino/chain_append_parallel.png | Bin 0 -> 250539 bytes static/img/eino/chatmodel_to_tool.png | Bin 0 -> 39539 bytes static/img/eino/chatmodel_tool_loop.png | Bin 0 -> 88724 bytes static/img/eino/edge_of_parallel.png | Bin 0 -> 155076 bytes static/img/eino/edge_type_validate.png | Bin 0 -> 477650 bytes static/img/eino/eino_component_runnable.png | Bin 0 -> 763566 bytes static/img/eino/eino_process.png | Bin 0 -> 326192 bytes static/img/eino/eino_structure.png | Bin 0 -> 437747 bytes static/img/eino/eino_structure_modules.png | Bin 0 -> 306527 bytes static/img/eino/eino_trace_in_fornax.png | Bin 0 -> 574270 bytes static/img/eino/graph_as_chain_node.png | Bin 0 -> 264897 bytes static/img/eino/graph_explain.png | Bin 0 -> 34963 bytes static/img/eino/graph_nodes.png | Bin 0 -> 89310 bytes .../img/eino/graph_runnable_after_compile.png | Bin 0 -> 179793 bytes .../input_keys_output_keys_in_parallel.png | Bin 0 -> 321308 bytes .../img/eino/input_output_type_validate.png | Bin 0 -> 286639 bytes .../eino/input_type_output_type_in_edge.png | Bin 0 -> 410992 bytes .../eino/invoke_outside_collect_inside.png | Bin 0 -> 81689 bytes .../img/eino/invoke_outside_stream_inside.png | Bin 0 -> 71650 bytes .../eino/invoke_outside_transform_inside.png | Bin 0 -> 105347 bytes .../eino/invoke_stream_transform_collect.png | Bin 0 -> 504311 bytes .../eino/metrics_token_usage_in_fornax.png | Bin 0 -> 273735 bytes static/img/eino/nodes_type_validate.png | Bin 0 -> 111043 bytes static/img/eino/react_agent_graph.png | Bin 0 -> 297403 bytes static/img/eino/recommend_way_of_handler.png | Bin 0 -> 321577 bytes static/img/eino/run_way_branch_in_graph.png | Bin 0 -> 264399 bytes static/img/eino/same_type_of_parallel.png | Bin 0 -> 207048 bytes static/img/eino/stream_copy_in_callback.png | Bin 0 -> 260819 bytes static/img/eino/tool_model_react.png | Bin 0 -> 115208 bytes .../eino/transform_inside_stream_inside.png | Bin 0 -> 112650 bytes .../eino/transform_outside_invoke_inside.png | Bin 0 -> 136659 bytes .../eino/transform_outside_stream_inside.png | Bin 0 -> 124057 bytes static/img/eino/what_is_chain.png | Bin 0 -> 96652 bytes 88 files changed, 9602 insertions(+) create mode 100644 content/zh/docs/eino/_index.md create mode 100644 content/zh/docs/eino/core_modules/_index.md create mode 100644 content/zh/docs/eino/core_modules/application_development_toolchain/_index.md create mode 100644 content/zh/docs/eino/core_modules/chain_and_graph_orchestration/_index.md create mode 100644 content/zh/docs/eino/core_modules/chain_and_graph_orchestration/call_option_capabilities.md create mode 100644 content/zh/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual.md create mode 100644 content/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects.md create mode 100644 content/zh/docs/eino/core_modules/chain_and_graph_orchestration/chain_graph_introduction.md create mode 100644 content/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles.md create mode 100644 content/zh/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials.md create mode 100644 content/zh/docs/eino/core_modules/components/_index.md create mode 100644 content/zh/docs/eino/core_modules/components/chat_model_guide.md create mode 100644 content/zh/docs/eino/core_modules/components/chat_template_guide.md create mode 100644 content/zh/docs/eino/core_modules/components/document_loader_guide/_index.md create mode 100644 content/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide.md create mode 100644 content/zh/docs/eino/core_modules/components/document_transformer_guide.md create mode 100644 content/zh/docs/eino/core_modules/components/embedding_guide.md create mode 100644 content/zh/docs/eino/core_modules/components/indexer_guide.md create mode 100644 content/zh/docs/eino/core_modules/components/retriever_guide.md create mode 100644 content/zh/docs/eino/core_modules/components/tools_node_guide.md create mode 100644 content/zh/docs/eino/core_modules/flow_integration_components/_index.md create mode 100644 content/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting.md create mode 100644 content/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual.md create mode 100644 content/zh/docs/eino/ecosystem_integration/_index.md create mode 100644 content/zh/docs/eino/ecosystem_integration/chat_model/_index.md create mode 100644 content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ark.md create mode 100644 content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ollama.md create mode 100644 content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_openai.md create mode 100644 content/zh/docs/eino/ecosystem_integration/document/_index.md create mode 100644 content/zh/docs/eino/ecosystem_integration/document/loader_amazon_s3.md create mode 100644 content/zh/docs/eino/ecosystem_integration/document/loader_local_file.md create mode 100644 content/zh/docs/eino/ecosystem_integration/document/loader_web_url.md create mode 100644 content/zh/docs/eino/ecosystem_integration/document/parser_html.md create mode 100644 content/zh/docs/eino/ecosystem_integration/document/parser_pdf.md create mode 100644 content/zh/docs/eino/ecosystem_integration/document/splitter_markdown.md create mode 100644 content/zh/docs/eino/ecosystem_integration/document/splitter_recursive.md create mode 100644 content/zh/docs/eino/ecosystem_integration/document/splitter_semantic.md create mode 100644 content/zh/docs/eino/ecosystem_integration/embedding/_index.md create mode 100644 content/zh/docs/eino/ecosystem_integration/embedding/embedding_ark.md create mode 100644 content/zh/docs/eino/ecosystem_integration/embedding/embedding_openai.md create mode 100644 content/zh/docs/eino/ecosystem_integration/indexer_volc_vikingdb.md create mode 100644 content/zh/docs/eino/ecosystem_integration/retriever_volc_vikingdb.md create mode 100644 content/zh/docs/eino/ecosystem_integration/tool/_index.md create mode 100644 content/zh/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search.md create mode 100644 content/zh/docs/eino/ecosystem_integration/tool/tool_googlesearch.md create mode 100644 content/zh/docs/eino/overview/_index.md create mode 100644 content/zh/docs/eino/quick_start/_index.md create mode 100644 content/zh/docs/eino/quick_start/agent_llm_with_tools.md create mode 100644 content/zh/docs/eino/quick_start/complex_business_logic_orchestration.md create mode 100644 content/zh/docs/eino/quick_start/simple_llm_application.md create mode 100644 static/img/eino/big_challenge_graph.png create mode 100644 static/img/eino/branch_to_draw_loop.png create mode 100644 static/img/eino/callback_in_graph.png create mode 100644 static/img/eino/chain_append_branch.png create mode 100644 static/img/eino/chain_append_parallel.png create mode 100644 static/img/eino/chatmodel_to_tool.png create mode 100644 static/img/eino/chatmodel_tool_loop.png create mode 100644 static/img/eino/edge_of_parallel.png create mode 100644 static/img/eino/edge_type_validate.png create mode 100644 static/img/eino/eino_component_runnable.png create mode 100644 static/img/eino/eino_process.png create mode 100644 static/img/eino/eino_structure.png create mode 100644 static/img/eino/eino_structure_modules.png create mode 100644 static/img/eino/eino_trace_in_fornax.png create mode 100644 static/img/eino/graph_as_chain_node.png create mode 100644 static/img/eino/graph_explain.png create mode 100644 static/img/eino/graph_nodes.png create mode 100644 static/img/eino/graph_runnable_after_compile.png create mode 100644 static/img/eino/input_keys_output_keys_in_parallel.png create mode 100644 static/img/eino/input_output_type_validate.png create mode 100644 static/img/eino/input_type_output_type_in_edge.png create mode 100644 static/img/eino/invoke_outside_collect_inside.png create mode 100644 static/img/eino/invoke_outside_stream_inside.png create mode 100644 static/img/eino/invoke_outside_transform_inside.png create mode 100644 static/img/eino/invoke_stream_transform_collect.png create mode 100644 static/img/eino/metrics_token_usage_in_fornax.png create mode 100644 static/img/eino/nodes_type_validate.png create mode 100644 static/img/eino/react_agent_graph.png create mode 100644 static/img/eino/recommend_way_of_handler.png create mode 100644 static/img/eino/run_way_branch_in_graph.png create mode 100644 static/img/eino/same_type_of_parallel.png create mode 100644 static/img/eino/stream_copy_in_callback.png create mode 100644 static/img/eino/tool_model_react.png create mode 100644 static/img/eino/transform_inside_stream_inside.png create mode 100644 static/img/eino/transform_outside_invoke_inside.png create mode 100644 static/img/eino/transform_outside_stream_inside.png create mode 100644 static/img/eino/what_is_chain.png diff --git a/_typos.toml b/_typos.toml index 95d1a74ec8..7b65bdf931 100644 --- a/_typos.toml +++ b/_typos.toml @@ -7,3 +7,4 @@ extend-exclude = ["*.json", "*.js", "static/*", "layouts/*", "assets/*", "i18n/* datas = "datas" mmaped = "mmaped" exten = "exten" +invokable = "invokable" \ No newline at end of file diff --git a/content/zh/docs/eino/_index.md b/content/zh/docs/eino/_index.md new file mode 100644 index 0000000000..aa369d5f60 --- /dev/null +++ b/content/zh/docs/eino/_index.md @@ -0,0 +1,68 @@ +--- +Description: Eino 是基于 Golang 的 AI 应用开发框架 +date: "2025-01-07" +lastmod: "" +linktitle: Eino +menu: + main: + parent: 文档 + weight: 6 +tags: [] +title: Eino 用户手册 +weight: 6 +--- + +> Eino 发音:美 / 'aino /,近似音: i know,有希望应用程序达到 "i know" 的愿景 + +# Eino 是什么 + +> 💡 +> Go AI 集成组件的研发框架。 + +Eino 旨在提供 Golang 语言的 AI 应用开发框架。 Eino 参考了开源社区中诸多优秀的 AI 应用开发框架,例如 LangChain、LangGraph、LlamaIndex 等,提供了更符合 Golang 编程习惯的 AI 应用开发框架。 + +Eino 提供了丰富的辅助 AI 应用开发的**原子组件**、**集成组件**、**组件编排**、**切面扩展**等能力,可以帮助开发者更加简单便捷地开发出架构清晰、易维护、高可用的 AI 应用。 + +以 React Agent 为例: + +- 提供了 ChatModel、ToolNode、PromptTemplate 等常用组件,并且业务可定制、可扩展。 +- 可基于现有组件进行灵活编排,产生集成组件,在业务服务中使用。 + +![](/img/eino/react_agent_graph.png) + +# Eino 组件 + +> Eino 的其中一个目标是:搜集和完善 AI 应用场景下的组件体系,让业务可轻松找到一些通用的 AI 组件,方便业务的迭代。 + +Eino 会围绕 AI 应用的场景,提供具有比较好的抽象的组件,并围绕该抽象提供一些常用的实现。 + +- Eino 组件的抽象定义在:[eino/components](https://github.com/cloudwego/eino/tree/main/components) +- Eino 组件的实现在:[eino-ext/components](https://github.com/cloudwego/eino-ext/tree/main/components) + +# Eino 应用场景 + +得益于 Eino 轻量化和内场亲和属性,用户只需短短数行代码就能给你的存量微服务引入强力的大模型能力,让传统微服务进化出 AI 基因。 + +可能大家听到【Graph 编排】这个词时,第一反应就是将整个应用接口的实现逻辑进行分段、分层的逻辑拆分,并将其转换成可编排的 Node。 这个过程中遇到的最大问题就是**长距离的上下文传递(跨 Node 节点的变量传递)**问题,无论是使用 Graph/Chain 的 State,还是使用 Options 透传,整个编排过程都极其复杂,远没有直接进行函数调用简单。 + +基于当前的 Graph 编排能力,适合编排的场景具有如下几个特点: + +- 整体是围绕模型的语义处理相关能力。这里的语义不限模态 +- 编排产物中有极少数节点是 Session 相关的。整体来看,绝大部分节点没有类似用户/设备等不可枚举地业务实体粒度的处理逻辑 + + - 无论是通过 Graph/Chain 的 State、还是通过 CallOptions,对于读写或透传用户/设备粒度的信息的方式,均不简便 +- 需要公共的切面能力,基于此建设观测、限流、评测等横向治理能力 + +编排的意义是什么: 把长距离的编排元素上下文以固定的范式进行聚合控制和呈现。 + +**整体来说,“Graph 编排”适用的场景是: 业务定制的 AI 集成组件。 ****即把 AI 相关的原子能力,进行灵活编排****,提供简单易用的场景化的 AI 组件。 并且该 AI 组件中,具有统一且完整的横向治理能力。** + +- 推荐的使用方式 + +![](/img/eino/recommend_way_of_handler.png) + +- 挑战较大的方式 -- 【业务全流程的节点编排】 + - Biz Handler 一般重业务逻辑,轻数据流,比较适合函数栈调用的方式进行开发 + - 如果采用图编排的方式进行逻辑划分与组合,会增大业务逻辑开发的难度 + +![](/img/eino/big_challenge_graph.png) diff --git a/content/zh/docs/eino/core_modules/_index.md b/content/zh/docs/eino/core_modules/_index.md new file mode 100644 index 0000000000..1a4347eaa0 --- /dev/null +++ b/content/zh/docs/eino/core_modules/_index.md @@ -0,0 +1,19 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: 核心模块' +weight: 3 +--- + +Eino 中的核心模块有如下几个部分: + +- **Components 组件**:[Eino: Components 组件](/zh/docs/eino/core_modules/components) + Eino 抽象出来的大模型应用中常用的组件,例如 `ChatModel`、`Embedding`、`Retriever` 等,这是实现一个大模型应用搭建的积木,是应用能力的基础,也是复杂逻辑编排时的原子对象。 +- **Chain/Graph 编排**:[Eino: Chain/Graph 编排功能](/zh/docs/eino/core_modules/chain_and_graph_orchestration/chain_graph_introduction) + 多个组件混合使用来实现业务逻辑的串联,Eino 提供 Chain/Graph 的编排方式,把业务逻辑串联的复杂度封装在了 Eino 内部,提供易于理解的业务逻辑编排接口,提供统一的横切面治理能力。 +- **Flow 集成工具 (agents)**: [Eino: Flow 集成组件](/zh/docs/eino/core_modules/flow_integration_components) + Eino 把最常用的大模型应用模式封装成简单、易用的工具,让通用场景的大模型应用开发极致简化,目前提供了 `React Agent` 和 `Host Multi Agent`。 +- **EinoDev 开发辅助工具**:[EinoDev: 应用开发工具链](/zh/docs/eino/core_modules/application_development_toolchain) + Eino 致力于让全码开发大模型应用变得非常简单,EinoDev 则为 Eino 编排提供了 `可视化`、`交互式` 的开发调试方案,所见即所得,让开发者的精力从 `调试地狱` 中释放出来,专注于场景逻辑。 diff --git a/content/zh/docs/eino/core_modules/application_development_toolchain/_index.md b/content/zh/docs/eino/core_modules/application_development_toolchain/_index.md new file mode 100644 index 0000000000..82db2813b8 --- /dev/null +++ b/content/zh/docs/eino/core_modules/application_development_toolchain/_index.md @@ -0,0 +1,16 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'EinoDev: 应用开发工具链' +weight: 4 +--- + +🚧 施工中,敬请期待。。。 + +TODO: + +1. EinoDev 是什么? +2. EinoDev 希望解决的问题是什么? +3. EinoDev 的解决方案是什么? diff --git a/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/_index.md b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/_index.md new file mode 100644 index 0000000000..c1618b63fc --- /dev/null +++ b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/_index.md @@ -0,0 +1,62 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: Chain & Graph 编排功能' +weight: 2 +--- + +在大模型应用中,`Components` 组件 是提供 『原子能力』的最小单元,比如: + +- `ChatModel` 提供了大模型的对话能力 +- `Embedding` 提供了基于语义的文本向量化能力 +- `Retriever` 提供了关联内容召回的能力 +- `ToolsNode` 提供了执行外部工具的能力 + +> 详细的组件介绍可以参考: [Eino: Components 组件](/zh/docs/eino/core_modules/components) + +一个大模型应用,除了需要这些原子能力之外,还需要根据场景化的业务逻辑,**对这些原子能力进行组合、串联**,这就是 **『编排』**。 + +大模型应用的开发有其自身典型的特征: 自定义的业务逻辑本身不会很复杂,几乎主要都是对『原子能力』的组合串联。 + +传统代码开发过程中,业务逻辑用 “代码的执行逻辑” 来表达,迁移到大模型应用开发中时,最直接想到的方法就是 “自行调用组件,自行把结果作为下一组件的输入进行调用”。这样的结果,就是 `代码杂乱`、`很难复用`、`没有切面能力`…… + +当开发者们追求代码『**优雅**』和『**整洁之道**』时,就发现把传统代码组织方式用到大模型应用中时有着巨大的鸿沟。 + +Eino 的初衷是让大模型应用开发变得非常简单,就一定要让应用的代码逻辑 “简单” “直观” “优雅” “健壮”。 + +Eino 对「编排」有着这样的洞察: + +- 编排要成为在业务逻辑之上的清晰的一层,**不能让业务逻辑融入到编排中**。 +- 大模型应用的核心是 “对提供原子能力的组件” 进行组合串联,**组件是编排的 “第一公民”**。 +- 抽象视角看编排:编排是在构建一张网络,数据则在这个网络中流动,网络的每个节点都对流动的数据有格式/内容的要求,一个能顺畅流动的数据网络,关键就是 “**上下游节点间的数据格式是否对齐**?”。 +- 业务场景的复杂度会反映在编排产物的复杂性上,只有**横向的治理能力**才能让复杂场景不失控。 +- 大模型是会持续保持高速发展的,大模型应用也是,只有**具备扩展能力的应用才拥有生命力**。 + +于是,Eino 提供了 “基于 Graph 模型 (node + edge) 的,以**组件**为原子节点的,以**上下游类型对齐**为基础的编排” 的解决方案。 + +具体来说,实现了如下特性: + +- 一切以 “组件” 为核心,规范了业务功能的封装方式,让**职责划分变得清晰**,让**复用**变成自然而然 + + - 详细信息参考:[Eino: Components 组件](/zh/docs/eino/core_modules/components) +- 业务逻辑复杂度封装到组件内部,编排层拥有更全局的视角,让**逻辑层次变得非常清晰** +- 提供了切面能力,callback 机制支持了基于节点的**统一治理能力** + + - 详细信息参考:[Eino: 公共切面 - Callbacks](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects) +- 提供了 call option 的机制,**扩展性**是快速迭代中的系统最基本的诉求 + + - 详细信息参考:[Eino: CallOption 能力与规范](/zh/docs/eino/core_modules/chain_and_graph_orchestration/call_option_capabilities) +- 提供了 “类型对齐” 的开发方式的强化,降低开发者心智负担,把 golang 的**类型安全**特性发挥出来 + + - 详细信息参考:[Eino: 编排的设计理念](/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles) +- 提供了 “**流的自动转换**” 能力,让 “流” 在「编排系统的复杂性来源榜」中除名 + + - 详细信息参考:[Eino 流式编程要点](/zh/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials) + +Graph 本身是强大且语义完备的,可以用这项底层几乎绘制出所有的 “数据流动网络”,比如 “分支”、“并行”、“循环”。 + +但 Graph 并不是没有缺点的,基于 “点” “边” 模型的 Graph 在使用时,要求开发者要使用 `graph.AddXXXNode()` 和 `graph.AddEdge()` 两个接口来创建一个数据通道,强大但是略显复杂。 + +而在现实的大多数业务场景中,往往仅需要 “按顺序串联” 即可,因此,Eino 封装了接口更易于使用的 `Chain`。Chain 是对 Graph 的封装,除了 “环” 之外,Chain 暴露了几乎所有 Graph 的能力。 diff --git a/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/call_option_capabilities.md b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/call_option_capabilities.md new file mode 100644 index 0000000000..9c405c1fbd --- /dev/null +++ b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/call_option_capabilities.md @@ -0,0 +1,306 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: CallOption 能力与规范' +weight: 0 +--- + +**CallOption**: 对 Graph 编译产物进行调用时,直接传递数据给特定的一组节点(Component、Implementation、Node)的渠道 + +## 组件 CallOption 形态 + +组件 CallOption 配置,有两个粒度: + +- 组件的抽象(Abstract/Interface)统一定义的 CallOption 配置【组件抽象 CallOption】 +- 组件的实现(Type/Implementation)定义的该类型组件专用的 CallOption 配置【组件实现 CallOption】 + +以 ChatModel 这个 Component 为例,介绍 CallOption 的形态 + +### Model 抽象与实现的目录 + +``` +// 抽象所在代码位置 +eino/components/model +├── interface.go +├── **option.go** // Component 抽象粒度的 CallOption 入参 + +// 抽象实现所在代码位置 +eino-ext/components/model +├── maas +│   ├── **call_option.go** +│   └── Implementation.go +├── openai +│   ├── **call_option.go** // Component 的一种实现的 CallOption 入参 +│   ├── Implementation.go +``` + +### Model 抽象 + +如上所述,在定义组件的 CallOption 时,需要区分【组件抽象 CallOption】、【组件实现 CallOption】两种场景。 而是否要提供 【组件实现 CallOption】,则是由 组件抽象 来决定的。 + +组件抽象提供的 CallOption 扩展能力如下(以 Model 为例,其他组件类似): + +```go +package model + +type ChatModel interface { + Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error) + Stream(ctx context.Context, input []*schema.Message, opts ...Option) ( + *schema.StreamReader[*schema.Message], error) + + // BindTools bind tools to the model. + // BindTools before requesting ChatModel generally. + // notice the non-atomic problem of BindTools and Generate. + BindTools(tools []*schema.ToolInfo) error +} + +// 此结构体是【组件抽象CallOption】的统一定义。 组件的实现可根据自己的需要取用【组件抽象CallOption】的信息 +type Options struct { + Temperature float32 + MaxTokens int + Model string + TopP float32 +} + +// Option is the call option for ChatModel component. +type Option struct { + // 此字段是为【组件抽象CallOption】服务的 apply 方法,例如 WithTemperature + // 如果组件抽象不想提供【组件抽象CallOption】,可不提供此字段,同时不提供 GetCommonOptions() 方法 + apply func(opts *Options) + + // 此字段是为【组件实现CallOption】服务的 apply 方法。并假设 apply 方法为:func(*T) + // 如果组件抽象不想提供【组件实现CallOption】,可不提供此字段,同时不提供 GetImplSpecificOptions() 方法 + implSpecificOptFn any +} + +func WithTemperature(temperature float32) Option { + return Option{ + apply: func(opts *Options) { + opts.Temperature = temperature + }, + } +} + +func WithMaxTokens(maxTokens int) Option { + return Option{ + apply: func(opts *Options) { + opts.MaxTokens = maxTokens + }, + } +} + +func WithModel(name string) Option { + return Option{ + apply: func(opts *Options) { + opts.Model = name + }, + } +} + +func WithTopP(topP float32) Option { + return Option{ + apply: func(opts *Options) { + opts.TopP = topP + }, + } +} + +// GetCommonOptions extract model Options from Option list, optionally providing a base Options with default values. +func GetCommonOptions(base *Options, opts ...Option) *Options { + if base == nil { + base = &Options{} + } + + for i := range opts { + opt := opts[i] + if opt.apply != nil { + opt.apply(base) + } + } + + return base +} + +// 组件实现方基于此方法,封装自己的Option函数:func WithXXX(xxx string) Option{} +func WrapImplSpecificOptFn[T any](optFn func(*T)) Option { + return Option{ + implSpecificOptFn: optFn, + } +} + +// GetImplSpecificOptions provides tool author the ability to extract their own custom options from the unified Option type. +// T: the type of the impl specific options struct. +// This function should be used within the tool implementation's InvokableRun or StreamableRun functions. +// It is recommended to provide a base T as the first argument, within which the tool author can provide default values for the impl specific options. +func GetImplSpecificOptions[T any](base *T, opts ...Option) *T { + if base == nil { + base = new(T) + } + + for i := range opts { + opt := opts[i] + if opt.implSpecificOptFn != nil { + optFn, ok := opt.implSpecificOptFn.(func(*T)) + if ok { + optFn(base) + } + } + } + + return base +} +``` + +### OpenAI 实现 + +> 组件的实现均类似 OpenAI 的实现 +> 注:此处为样例,eino-ext/components/model 中暂时没有此场景 + +```go +package openai + +import ( + "github.com/cloudwego/eino/components/model" +) + +// openAIOptions 实现粒度的 CallOption 配置 +type requestOptions struct { + APIKey string + Stop []string + PresencePenalty float32 +} + +// openai 下的 WithXX() 方法,只能对 openai 这一种实现生效 +func WithAPIKey(apiKey string) model.Option { + return model.WrapImplSpecificOptFn[requestOptions](func(o *requestOptions) { + o.APIKey = apiKey + }) +} + +func WithStop(stop []string) model.Option { + return model.WrapImplSpecificOptFn[requestOptions](func(o *requestOptions) { + o.Stop = stop + }) +} + +func WithPresencePenalty(presencePenalty float32) model.Option { + return model.WrapImplSpecificOptFn[requestOptions](func(o *requestOptions) { + o.PresencePenalty = presencePenalty + }) +} +``` + +model/openai/Implementation.go + +```go +type ChatModel struct {} + +func (cm *ChatModel) Generate(ctx context.Context, in []*schema.Message, opts ...model.Option) ( + + cmOpts := model.GetCommonOptions(&model.Options{ + Model: "gpt-3.5-turbo", + MaxTokens: 1024, + Temperature: 0.7, + }, opts...) + + implOpts := model.GetImplSpecificOptions[requestOptions](&requestOptions{ + Stop: nil, + PresencePenalty: 1, + }, opts...) + + // 在这里开发 OpenAI 的 Model 逻辑 + _ = cmOpts + _ = implOpts +} + +func (cm *ChatModel) Stream(ctx context.Context, in []*schema.Message, + opts ...model.Option) (outStream *schema.StreamReader[*schema.Message], err error) { + // 同 Generate 接口 +} +``` + +### Graph 编译产物 + +> Graph 的编译产物是 Runnable[I, O] + +option.go + +```go +type Option struct { + options []any + nodeHandler []callbacks.Handler + keys []string + + graphHandler []callbacks.Handler + graphOption []GraphRunOption +} + +// 可指定 NodeKey 定点生效 CallOption +func (o Option) DesignateNode(key ...string) Option { + o.keys = append(o.keys, key...) + return o +} + +func WithChatModelOption(opts ...model.Option) Option { + o := make([]any, 0, len(opts)) + for i := range opts { + o = append(o, opts[i]) + } + return Option{ + options: o, + keys: make([]string, 0), + } +} +``` + +runnable.go + +```go +type Runnable[I, O any] interface { + Invoke(ctx context.Context, input I, opts ...Option) (output O, err error) + Stream(ctx context.Context, input I, opts ...Option) (output *schema.StreamReader[O], err error) + Collect(ctx context.Context, input *schema.StreamReader[I], opts ...Option) (output O, err error) + Transform(ctx context.Context, input *schema.StreamReader[I], opts ...Option) (output *schema.StreamReader[O], err error) +} +``` + +Graph 调用 + +```go +g := NewGraph[map[string]any, *schema.Message]() + +_nodeOfModel := &openai.ChatModel{}_ + +err = g.AddChatModelNode("openAIModel", _nodeOfModel_) + +r, err := g.Compile() + +// 默认情况下,WithXXX() 的 Option 方法是按照 Component 的类型进行分发的 +// 同一个 WithXXX() 会对同一种 Component 的不同实例同时生效 +// 必要情况下可通过指定 NodeKey,仅针对一个 Node 生效 WithXXX() 方法 +out, err = r.Invoke(ctx, in, WithChatModelOption( + **openai.**WithAKSK("ak", "sk"), + **openai**.WithURL("url"), + ), + // 这组 CallOption 仅针对 openAIModel 这个节点生效 + WithChatModelOption( + **model.**WithModel("gpt-3.5-turto"), + openai.WithAPIKey("xxxx"), + ).DesignateNode("openAIModel"), + ) +``` + +## CallOption 产品形态 + +每个节点的 CallOption 最终都会映射到 Graph 编排产物的 CallOption 入参上。 因此说 CallOption 是对 Graph 编译产物进行调用时,直接传递数据给特定的一组节点(Component、Implementation、Node)的渠道 + +![](/img/eino/graph_runnable_after_compile.png) + +需要明确几个点: + +- 每个节点的 CallOption 是否需要再 Graph 的 UI 上直观体现 +- Graph 部署后,产生的服务接口,需要有能指定 CallOption 的入参。 + + - 这个 CallOption 是否需要由 Graph 编排者再次加工后暴露,还是直接裸暴露其中每个节点的 CallOption diff --git a/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual.md b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual.md new file mode 100644 index 0000000000..768fd7c6a1 --- /dev/null +++ b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual.md @@ -0,0 +1,447 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: Callback 用户手册' +weight: 0 +--- + +> 💡 +> TL;DR +> 长文,用意是“明确的、无歧义的、充分的”说明 Eino Callback 设计、实现和使用方式的各方面,可用作解决某个具体问题的工具参考,也可以作为入门后想要更进一步了解细节的一个途径。 +> 快速入门请移步 :[Eino: 公共切面 - Callbacks](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects) + +# 解决的问题 + +Component(包括 Lambda)、Graph 编排共同解决“把业务逻辑定义出来”的问题。而 logging, tracing, metrics, 上屏展示等横切面性质的功能,需要有机制把功能注入到 Component(包括 Lambda)、Graph 中。 + +另一方面,用户可能想拿到某个具体 Component 实现的执行过程中的中间信息,比如 VikingDBRetriever 额外给出查询的 DB Name,ArkChatModel 额外给出请求的 temperature 等参数。需要有机制把中间状态透出。 + +Callbacks 支持“**横切面功能注入**”和“**中间状态透出**”,具体是:用户提供、注册“function”(Callback Handler),Component 和 Graph 在固定的“时机”(或者说切面、位点)回调这些 function,给出对应的信息。 + +# 核心概念 + +核心概念串起来,就是:Eino 中的 Component 和 Graph 等**实体**,在固定的**时机** (Callback Timing),回调用户提供的 **function** (Callback Handler),并把**自己是谁** (RunInfo),以及**当时发生了什么** (Callback Input & Output) 传出去。 + +## 触发实体 + +Component(包括官方定义的组件类型和 Lambda),Graph Node(以及 Chain Node),Graph 自身(以及 Chain)。这三类实体,都有横切面功能注入、中间状态透出的需求,因此都会触发 callback。具体见下面的“[触发方式](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual)”一节。 + +## 触发时机 + +```go +// CallbackTiming enumerates all the timing of callback aspects. +type CallbackTiming = callbacks.CallbackTiming + +const ( + TimingOnStart CallbackTiming = iota // 进入并开始执行 + TimingOnEnd // 成功完成即将 return + TimingOnError // 失败并即将 return err + TimingOnStartWithStreamInput // OnStart,但是输入是 StreamReader + TimingOnEndWithStreamOutput // OnEnd,但是输出是 StreamReader +) +``` + +不同的触发实体,在不同场景下,是触发 OnStart 还是 OnStartWithStreamInput (OnEnd/OnEndWithStreamOutput 同理),具体的规则,详见下面的“[触发方式](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual)”一节。 + +## Callback Handler + +```go +type Handler interface { + OnStart(ctx context.Context, info *RunInfo, input CallbackInput) context.Context + OnEnd(ctx context.Context, info *RunInfo, output CallbackOutput) context.Context + OnError(ctx context.Context, info *RunInfo, err error) context.Context + OnStartWithStreamInput(ctx context.Context, info *RunInfo, + input *schema.StreamReader[CallbackInput]) context.Context + OnEndWithStreamOutput(ctx context.Context, info *RunInfo, + output *schema.StreamReader[CallbackOutput]) context.Context +} +``` + +一个 Handler 是一个实现了上面 5 个方法(对应 5 个触发时机)的结构体。每个方法都会接收三个信息: + +- Context: 用于**接收同一个 Handler 的前序触发时机**可能设置的定制信息。 +- RunInfo: 触发回调的实体元信息。 +- Input/Output/InputStream/OutputStream: 触发回调时的业务信息。 + +并都会返回新的 Context:用于**同一个 Handler 的不同触发时机之间**传递信息。 + +如果一个 Handler,不想关注所有的 5 个触发时机,只想关注一部分,比如只关注 OnStart,建议使用 `NewHandlerBuilder().OnStartFn(...).Build()`。如果不想关注所有的组件类型,只想关注特定组件,比如 ChatModel,建议使用 `NewHandlerHelper().ChatModel(...).Handler()`,可以只接收 ChatModel 的回调并拿到一个具体类型的 CallbackInput/CallbackOutput。具体见“[Handler 实现方式](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual)”一节。 + +不同 Handler 之间,触发顺序**没有**保证。 + +## RunInfo + +描述了触发 Callback 的实体自身的元信息。 + +```go +// RunInfo contains information about the running component that triggers callbacks. +type RunInfo struct { + Name string // the 'Name' with semantic meaning for the running component, specified by end-user + Type string // the specific implementation 'Type' of the component, e.g. 'OpenAI' + Component components.Component // the component abstract type, e.g. 'ChatModel' +} +``` + +- Name:有业务含义的名称,需用户指定,不指定就是空字符串。对不同的触发实体: + + - Component:在 Graph 中时,用 Node Name。在 Graph 外单独的使用时,用户手动设置。详见“注入 RunInfo” 和 “单独使用 Component” + - Graph Node:用 Node Name `func WithNodeName(n string) GraphAddNodeOpt` + - Graph 自身: + - 顶层图用 Graph Name `func WithGraphName(graphName string) GraphCompileOption` + - 内部嵌套图,会用加入到上级图时添加的 Node Name +- Type:组件具体实现来规定: + + - 有接口的 Component:如果实现了 Typer 接口,用 GetType() 方法的结果。否则用反射获取 Struct/Func 名。 + - Lambda:如果用 `func WithLambdaType(t string) LambdaOpt` 指定了 Type,用这个,否则是空字符串。 + - Graph Node:用内部 Component/Lambda/Graph 的值。 + - Graph 自身:空字符串。 +- Component: + + - 有接口的 Component:是啥接口,就是啥 + - Lambda:固定值 Lambda + - Graph Node: 用内部的 Component/Lambda/Graph 的值。 + - Graph 自身:固定值 Graph / Chain. (之前曾有 StateGraph / StateChain ,现已整合到 Graph / Chain 中) + +## Callback Input & Output + +本质是任意类型,因为不同的 Component 的输入输出、内部状态完全不同。 + +```go +type CallbackInput any +type CallbackOutput any +``` + +具体到某个组件,有更具体的类型,比如 Chat Model + +```go +// CallbackInput is the input for the model callback. +type CallbackInput struct { + // Messages is the messages to be sent to the model. + Messages []*schema.Message + // Tools is the tools to be used in the model. + Tools []*schema.ToolInfo + // ToolChoice is the tool choice, which controls the tool to be used in the model. + ToolChoice any // string / *schema.ToolInfo + // Config is the config for the model. + Config *Config + // Extra is the extra information for the callback. + Extra map[string]any +} + +// CallbackOutput is the output for the model callback. +type CallbackOutput struct { + // Message is the message generated by the model. + Message *schema.Message + // Config is the config for the model. + Config *Config + // TokenUsage is the token usage of this request. + TokenUsage *TokenUsage + // Extra is the extra information for the callback. + Extra map[string]any +} +``` + +在 Chat Model 的具体实现,比如 OpenAI Chat Model 中,建议组件作者向 Callback Handler 中传入具体的 Input/Output 类型,而不是 Any。这样可以透出更具体的、定制化的中间状态信息。 + +如果是 Graph Node 来触发 Callback,因为 Node 拿不到组件内部中间状态信息,只能拿到组件接口中规定的输入和输出,所以给 Callback Handler 的只能是这些。对 Chat Model,就是 []*schema.Message 和 *schema.Message。 + +Graph 自身触发 Callback 时,输入输出就是 Graph 整体的输入和输出。 + +# 注入 Handler + +Handler 需要注入到 Context 中才能被触发。 + +## 全局注入 Handler + +通过 `callbacks.InitCallbackHandlers` 注入全局的 Handler。注入后,所有的触发回调行为,都会自动触发这些全局的 Handler。典型的场景是 tracing,logging 等全局一致、业务场景无关的功能。 + +不是并发安全的。建议在服务初始化时注入一次。 + +## 向 Graph 中注入 Handler + +通过 `compose.WithCallbacks` 在 graph 运行时注入 Handler,这些 Handler 会在 graph 的本次运行整体上生效,包括 Graph 内各 Node 和 Graph 自身(以及各内嵌的 graph)。 + +通过 `compose.WithCallbacks(...).DesignateNode(...)` 向顶层 Graph 的某个 Node 注入 Handler。当这个 Node 自身是个内嵌的 Graph 时,会注入到这个内嵌 Graph 自身和其内部的各 Node。 + +通过 `compose.WithCallbacks(...).DesignateNodeForPath(...)` 向内部嵌套的 Graph 的某个 Node 注入 Handler。 + +## 在 Graph 外注入 Handler + +不想使用 Graph,但却想使用 Callback,则: + +通过 `InitCallbacks(ctx context.Context, info *RunInfo, handlers ...Handler)` 获取一个新的 Context 并注入 Handlers 以及 RunInfo。 + +## Handler 继承 + +与子 Context 继承父 Context 中的所有 Values 相同,子 Context 也会继承父 Context 中的所有 Handlers。举个例子,Graph 运行时传入的 Context 中如果已经有了 Handler,则这些 Handlers 都会被整个 Graph 的这次运行继承和生效。 + +# 注入 RunInfo + +RunInfo 也需要注入到 Context 中,才会在触发回调时给到 Handler。 + +## Graph 托管 RunInfo + +Graph 会为内部所有的 Node 自动注入 RunInfo。机制是每个 Node 的运行,都是一个新的子 Context,Graph 向这个新的 Context 中注入对应 Node 的 RunInfo。 + +## 在 Graph 外注入 RunInfo + +不想使用 Graph,但却想使用 Callback,则: + +通过 `InitCallbacks(ctx context.Context, info *RunInfo, handlers ...Handler)` 获取一个新的 Context 并注入 Handlers 以及 RunInfo。 + +通过 `ReuseHandlers(ctx context.Context, info *RunInfo)` 来获取一个新的 Context,复用之前 Context 中的 Handler,并设置新的 RunInfo。 + +# 触发方式 + +## 组件实现内部触发(Component Callback) + +在组件实现的代码中,调用 callbacks 包中的 `OnStart(), OnEnd(), OnError(), OnStartWithStreamInput(), OnEndWithStreamInput()`。以 Ark 的 ChatModel 实现为例,在 Generate 方法中: + +```go +func (cm *ChatModel) Generate(ctx context.Context, in []*schema.Message, opts ...fmodel.Option) ( + outMsg *schema.Message, err error) { + + defer func() { + if err != nil { + _ = callbacks.OnError(ctx, err) + } + }() + + // omit multiple lines... instantiate req conf + + ctx = callbacks.OnStart(ctx, &fmodel.CallbackInput{ + Messages: in, + Tools: append(cm.rawTools), // join tool info from call options + ToolChoice: nil, // not support in api + Config: reqConf, + }) + + // omit multiple lines... invoke Ark chat API and get the response + + _ = callbacks.OnEnd(ctx, &fmodel.CallbackOutput{ + Message: outMsg, + Config: reqConf, + TokenUsage: toModelCallbackUsage(outMsg.ResponseMeta), + }) + + return outMsg, nil +} +``` + +在 Stream 方法中: + +```go +func (cm *ChatModel) Stream(ctx context.Context, in []*schema.Message, opts ...fmodel.Option) ( // byted_s_too_many_lines_in_func + outStream *schema.StreamReader[*schema.Message], err error) { + + defer func() { + if err != nil { + _ = callbacks.OnError(ctx, err) + } + }() + + // omit multiple lines... instantiate req conf + + ctx = callbacks.OnStart(ctx, &fmodel.CallbackInput{ + Messages: in, + Tools: append(cm.rawTools), // join tool info from call options + ToolChoice: nil, // not support in api + Config: reqConf, + }) + + // omit multiple lines... make request to Ark API and convert response stream to StreamReader[model.*CallbackOutput] + + _, sr = callbacks.OnEndWithStreamOutput(ctx, sr) + + return schema.StreamReaderWithConvert(sr, + func(src *fmodel.CallbackOutput) (*schema.Message, error) { + if src.Message == nil { + return nil, schema.ErrNoValue + } + + return src.Message, nil + }, + ), nil +} +``` + +可以看到 Generate 调用时,触发的是 OnEnd,而 Stream 调用时,触发的是 OneEndWithStreamOutput: + +组件实现内部触发 Callbacks 时: + +- **当组件输入为 StreamReader 时,触发 OnStartWithStreamInput,否则触发 OnStart** +- **当组件输出为 StreamReader 时,触发 OnEndWithStreamOutput,否则触发 OnEnd** + +内部实现了 callback 触发的组件,应当实现 Checker 接口,IsCallbacksEnabled 返回 true,向外部传达“我内部实现了 callback 触发”的信息: + +```go +// Checker tells callback aspect status of component's implementation +// When the Checker interface is implemented and returns true, the framework will not start the default aspect. +// Instead, the component will decide the callback execution location and the information to be injected. +type Checker interface { + IsCallbacksEnabled() bool +} +``` + +如果一个组件实现,没有实现 Checker 接口,或者 IsCallbacksEnabled 返回 false,可以认为该组件内部没有触发回调,需要 Graph Node 来负责注入和触发(在 Graph 内使用时)。 + +## Graph Node 触发(Node Callback) + +当一个 Component 被编排入 Graph 时,成为一个 Node。这时,如果 Component 自身会触发 callback,Node 就复用 Component 的 callback 处理。否则,Node 会在 Component 外面埋上 callback handler 触发点位。这些点位与 Component 自身的流式范式对应。比如一个 ChatModelNode,会在 Generate 方法外面埋上 OnStart/OnEnd/OnError,同时会在 Stream 方法外面埋上 OnStart/OnEndWithStreamOutput/OnError。 + +在 Graph 运行时,各组件会以 Invoke 或 Transform 范式运行,又会根据组件具体实现的业务流式范式,调用对应的组件方法。比如 Graph 以 Invoke 运行,Chat Model Node 会以 Invoke 运行,调用 Generate 方法。而当 Graph 以 Stream 运行,Chat Model Node 会以 Transform 运行,但 Chat Model 的业务流式范式中没有 Transform,会自动降级成调用 Stream 方法。因此: + +**Graph Node 具体触发哪个位点(OnStart 还是 OnStartWithStreamInput),取决于组件实现的业务流式范式和 Graph 运行方式两个因素。** + +关于 Eino 流式编程的详细介绍,参见 [Eino 流式编程要点](/zh/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials) + +## Graph 自身触发(Graph Callback) + +Graph 在自身的开始、结束、err 的时机触发 Callback Handler。如果 Graph 以 Invoke 形式调用,触发 OnStart/OnEnd/OnError。如果以 Stream/Collect/Transform 形式调用,触发 OnStartWithStreamInput/OnEndWithStreamOutput/OnError。这是因为 **Graph 内部会始终以 Invoke 或 Transform 执行**。参见 [Eino 流式编程要点](/zh/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials) + +值得注意的是:graph 也是 component 的一种,因此 graph callback 也是 component callback 的一种特殊形式。根据 Node Callback 的定义,当 Node 内部的 component 实现了对触发时机的感知和处理时,Node 会直接复用 Component 的实现,不会再实现 Node Callback。这意味着当一个 graph 通过 AddGraphNode 的方式加入到另外一个 Graph 中作为一个 Node 时,这个 Node 会复用内部 graph 的 graph callback。 + +# 解析 Callback Input & Output + +从上文得知,Callback Input & Output 的底层是 Any,只是不同组件类型在具体触发回调时,可能会传入自己特定的类型。并且 Callback Handler 的接口定义中,各方法的入参也是 Any 类型的 Callback Input & Output。 + +因此,具体的 Handler 实现中,需要做两个事情: + +1. 根据 RunInfo 判断当前触发回调的是哪个组件类型,比如 RunInfo.Component == "ChatModel",或者 RunInfo.Type == "xxx Chat Model"。 +2. 把 any 类型的 Callback Input & Output 转成对应的具体类型,以 RunInfo.Component == "ChatModel" 为例: + +```go +// ConvCallbackInput converts the callback input to the model callback input. +func ConvCallbackInput(src callbacks.CallbackInput) *CallbackInput { + switch t := src.(type) { + case *CallbackInput: // when callback is triggered within component implementation, the input is usually already a typed *model.CallbackInput + return t + case []*schema.Message: // when callback is injected by graph node, not the component implementation itself, the input is the input of Chat Model interface, which is []*schema.Message + return &CallbackInput{ + Messages: t, + } + default: + return nil + } +} + +// ConvCallbackOutput converts the callback output to the model callback output. +func ConvCallbackOutput(src callbacks.CallbackOutput) *CallbackOutput { + switch t := src.(type) { + case *CallbackOutput: // when callback is triggered within component implementation, the output is usually already a typed *model.CallbackOutput + return t + case *schema.Message: // when callback is injected by graph node, not the component implementation itself, the output is the output of Chat Model interface, which is *schema.Message + return &CallbackOutput{ + Message: t, + } + default: + return nil + } +} +``` + +如果 Handler 里面需要增加 switch case 来判断 RunInfo.Component,并且对每一个 case,需要调对应的转换函数把 Any 转成具体类型,确实有些复杂。为了减少写胶水代码的重复劳动,我们提供了两种实现 Handler 的便捷工具函数。 + +# Handler 实现方式 + +除了直接实现 Handler 接口外,Eino 提供了两种 Handler 的便捷实现工具。 + +## HandlerHelper + +如果用户的 Handler 只关注特定类型的组件,比如 ReactAgent 的场景,只关注 ChatModel 和 Tool,建议使用 HandlerHelper 来快速创建具体类型的 Callback Handler: + +```go +handler := NewHandlerHelper().ChatModel(modelHandler).Tool(toolHandler).Handler() +``` + +其中 modelHandler 是 Chat Model 组件对 callback handler 的进一步封装: + +```go +// ModelCallbackHandler is the handler for the model callback. +type ModelCallbackHandler struct { + OnStart func(ctx context.Context, runInfo *callbacks.RunInfo, input *model.CallbackInput) context.Context + OnEnd func(ctx context.Context, runInfo *callbacks.RunInfo, output *model.CallbackOutput) context.Context + OnEndWithStreamOutput func(ctx context.Context, runInfo *callbacks.RunInfo, output *schema.StreamReader[*model.CallbackOutput]) context.Context + OnError func(ctx context.Context, runInfo *callbacks.RunInfo, err error) context.Context +} +``` + +上面的 ModelCallbackHandler,封装了三个操作: + +1. 不再需要判断 RunInfo.Component 来选择属于 ChatModel 触发的回调,而是已经自动做了过滤。 +2. 只要求实现 Chat Model 这个组件支持的触发时机,这里去掉了不支持的 OnStartWithStreamInput。同时,如果用户只关注 Chat Model 支持的四个时机的某几个,比如只有 OnStart,也可以只实现 OnStart。 +3. Input / Output 不再是 Any 类型,而是已经转化好的 model.CallbackInput, model.CallbackOutput。 + +HandlerHelper 支持全部的官方组件,目前的列表是:ChatModel, ChatTemplate, Retriever, Indexer, Embedding, Document.Loader, Document.Transformer, Tool, ToolsNode. + +针对 Lambda,Graph,Chain 这些输入输出类型不确定的“组件”,也可以使用 HandlerHelper,但是只能做到上面的第 1 点,即按照组件类型做自动的过滤,2、3 点依然需要用户自己实现: + +```go +handler := NewHandlerHelper().Lambda(callbacks.Handler).Graph(callbacks.Handler)...Handler() +``` + +这时,NewHandlerHelper().Lambda() 需要传入 callbacks.Handler 可以用下面的 HandlerBuilder 来实现。 + +## HandlerBuilder + +如果用户的 Handler 需要关注多个组件类型,但却只需要关注部分的触发时机,可以使用 HandlerBuilder: + +```go +handler := NewHandlerBuilder().OnStartFn(fn)...Build() +``` + +# 最佳实践 + +## 在 Graph 中使用 + +- 积极使用 Global Handlers,注册始终生效的 Handlers。 +- 通过 WithHandlers option 在运行时注入 Handler,通过 DesignateNode 或 DesignateNodeByPath 指定生效的 Node / 嵌套的内部 Graph / 内部 Graph 的 Node。 + +## 在 Graph 外使用 + +依然可以积极使用 Global Handlers。但需要在调用 InitCallbacks 后 global handlers 才会生效。InitCallbacks 的入参中不需要传入 Global Handlers,会自动注入。 + +需要注意的是,如果在 Graph 外使用的 Component,内部并没有实现 + +### 单个 Component + +使用 InitCallbacks 注入 RunInfo 和 Handlers。RunInfo 的各字段需自行设置。 + +### 多个 component 并列 + +在每个并列的 component 执行前,分别调用 InitCallbacks 注入各自的 RunInfo 和 Handlers。注意: + +- 多次调用 InitCallbacks,传入的 Context 应当相同,因为各组件是并列关系 +- 每次调用 InitCallbacks,返回的 Context,应当传入对应的 Component 内,但不应当传入其他的 Component 内。 + +### 多个 component 嵌套 + +在顶层 Component 执行前,调用 InitCallbacks 注入 RunInfo 和 Handlers,并把返回的 Context 传入顶层 Component 内。 + +在内部 Component 执行前,分情况讨论: + +- 如果 Handlers 与顶层 Component 相同,调用 ReuseHandlers,注入新的 RunInfo,并把返回的 Context 传入内部 Component 中。 +- 如果 Handlers 与顶层 Component 不同,调用 InitCallbacks,注入新的 RunInfo 和新的(全量)Handlers,并把返回的 Context 传入内部 Component 中。 + +## Handler 内读写 input & output + +Handler 内不建议修改 input / output。原因是: + +- callbacks 类似单向的信息传递通道,component -> Handler。 +- 对 input / output 的修改,如果多个 handler 同时(异步)进行,有并发问题;如果先 copy 再修改,则失去了修改的意义,因为修改内容 component 内不可见。 + +在 Handler 内不能写 input / output 的前提下,一般情况下,在 Handler 内读 input / output 是并发安全的。一个特殊情况是上下游节点之间以 StreamReader 形式传递数据时,可能上游的 OnEndWithStreamOutput 还在异步处理流,下游的节点内部已经开始处理业务,这是 Handler 的异步处理与节点内部处理是并发的。这带来一个额外的要求:**组件实现内部,也不建议直接修改输入流中的数据,如果有修改后继续向下游输出的需求,需要先 copy,修改 copy 后的数据,再向下传递。** + +在满足这两个条件后,读就变成并发安全的了,因此 Handler 可以直接同步或异步读取 input / output 内的内容。 + +总结起来:**无论是组件内部还是 Handler 内部,都不建议直接修改输入的业务信息。** + +## Handler 间传递信息 + +同一个 Handler 的不同时机之间,可通过 ctx 传递信息,如 OnStart 中通过 context.WithValue 返回一个新的 context,在 OnEnd 中从 context 中再取出这个 value。 + +不同 Handler 之间,没有执行顺序的保证,因此不建议通过上面的机制在不同 Handler 间传递信息。本质上是无法保证某一个 Handler 返回的 context,一定会进入下一个 Handler 的函数执行中。 + +如果需要在不同 Handler 之间传递信息,建议的方式是在最外层的 context(如 graph 执行时传入的 context)中,设置一个全局的、请求维度的变量作为公共信息的存取空间,在各个 Handler 中按需读取和更新这个公共变量。在有 stream 的情况下,可能需要格外注意和保证这个公共变量的并发安全。 diff --git a/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects.md b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects.md new file mode 100644 index 0000000000..80e0fcf238 --- /dev/null +++ b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects.md @@ -0,0 +1,405 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: 公共切面 - Callbacks' +weight: 0 +--- + +# 切面 + +Eino 切面(Eino Callback)是 Eino 框架对开发提供的扩展 Eino 框架,丰富横向治理的能力 + +可以从以下几个方面,认识 Eino 切面: + +- 切面位置:即能在哪些点位添加切面逻辑 +- 切面注入:可通过哪些方式,注入对应的切面逻辑 +- 切面角色 + + - 切面机制:由 Eino 框架提供了,扩展 Eino 自身功能的机制 + - 切面扩展者:基于 Eino 的扩展能力,设计和提供各种各样的独立于 Graph 执行的扩展能力 + - 例如:Fornax Trace、Fornax 指标 等 + - 切面使用者:真正使用切面扩展能力的终端业务 + - 例如:针对业务编排的 Graph 图,添加 Fornax Trace、Fornax 指标 两种切面能力。方便对编排产物的执行进行观测 + +## 切面位置 + +![](/img/eino/callback_in_graph.png) + +- 组件切面(Component Callback):组件自带的切面 + + - 实现在组件内部的切面执行点位,而不是在加入到 Node 时,由 Node 在组件之外添加的切面点位 + - 组件切面既可应用于 **Graph 编排场景**,也可应用于**组件的独立使用**场景 + - 组件切面、节点切面一般二选一。 + - 当组件声明自己提供组件切面时,节点切面会被自动关闭。 +- 节点切面(Node Callback):组件加入到节点时,由 Node 在组件之外添加的切面点位 + + - 由于是在组件之外,只能见到组件的 input/output,无法感知组件运行时的内部状态 + - 仅可应用于 **Graph 编排场景**,在组件独立使用时,无法生效 +- 图切面(Graph Callback):把整张图视为一个整体,在图的前后添加的切面点位 + +切面生效位置有三种: + +- **所有图**的 Graph Callback 和 Node Callback 生效 +- **指定图及其嵌套子图**的 Graph Callback 和 Node Callback 生效 +- **指定图的指定节点**的 Node Callback 生效 + +## 切面注入 + +切面的注入方式,有以下三种: + +- 全局注入:对所有图的所有节点生效 +- 请求粒度注入:在请求时注入,仅对本次请求所经过的节点均生效 +- 组件实例注入:针对实现了 Component Callback 的组件,可针对该组件实例,单独注入切面 + +> 注意:不同的注入时机,其对应的 Callback 的生效位置有所不同 + +### 全局注入(进程粒度) + +- 生效位置: **所有图**的所有节点或组件的 Callback 均生效 + 所有图的 Graph Callback 生效 + + - 针对节点、组件哪一个生效的问题,取决于组件是否声明自己实现了 Component Callback。若声明,则仅 Component Callback 生效;若未声明,则 Node Callback 生效 +- 生效时机: Graph 编译产物的**任意一次执行** +- 注入方式: + + - 通过 callbacks.InitCallbackHandlers() 注入。一个进程中,仅最后一次调用生效。建议放在 main 入口函数中进行初始化 + +```go +import "github.com/cloudwego/eino/callbacks" + +func main() { + // 设置全局生效的 Callback Handlers。 + // 一个进程内仅最后一次调用的 callbacks.InitCallbackHandlers() 会生效 + callbacks.InitCallbackHandlers([]callbacks.Handler{&loggerCallbacks{}}) +} + +type loggerCallbacks struct{ + *callbacks.HandlerBuilder +} + +func (l *loggerCallbacks) OnStart(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context { + logs.Infof("name: %v, type: %v, component: %v, input: %v", info.Name, info.Type, info.Component, input) + return ctx +} + +func (l *loggerCallbacks) OnEnd(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) context.Context { + logs.Infof("name: %v, type: %v, component: %v, output: %v", info.Name, info.Type, info.Component, output) + return ctx +} +``` + +### 请求粒度注入 + +- 生效的切面位置: **本地调用涉及图及其嵌套子图**的 Graph Callback 和 Node Callback +- 生效时机: Graph 编译产物的本次调用 +- 注入时机: + + - 调用 Graph 编译产物 Runnable 方法时,例如调用 Invoke()、Stream()方法,通过 Graph Call Option 传入。 + - GraphCallOption:compose.WithCallbacks(handler) + +**Graph 示例:** + +```go +package main + +import ( + "context" + + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +func main() { + + ctx := context.Background() + + // create an instance of your implementation of callbacks.Handler + handler := &callbacks.HandlerBuilder{} + + // create an instance of Graph + // input type is 1st Graph Node's input type, that is ChatTemplate's input type: map[string]any + // output type is last Graph Node's output type, that is ToolsNode's output type: []*schema.Message + g := compose.NewGraph[[]*schema.Message, *schema.Message]() + + // add node and edge here + // err = g.AddChatModelNode("chat_model", chatTpl) + // if err != nil { + // logs.Errorf("AddChatTemplateNode failed, err=%v", err) + // return + // } + + // compile Graph[I, O] to Runnable[I, O] + r, err := g.Compile() + if err != nil { + logs.Errorf("Compile failed, err=%v", err) + return + } + + _, err = r.Invoke(ctx, + []*schema.Message{ + schema.SystemMessage("you are a helpful assistant"), + schema.UserMessage("我叫 zhangsan, 邮箱是 zhangsan@bytedance.com, 帮我推荐一处房产"), + }, + compose.WithCallbacks(handler), + ) + + _, err = r.Stream(ctx, []*schema.Message{ + schema.SystemMessage("you are a helpful assistant"), + schema.UserMessage("我叫 zhangsan, 邮箱是 zhangsan@bytedance.com, 帮我推荐一处房产"), + }, + compose.WithCallbacks(handler), + ) +} +``` + +**Chain 示例:** + +> 使用方式和 Graph 是一模一样的 + +```go +package main + +import ( + "context" + + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +func main() { + + ctx := context.Background() + + // create an instance of your implementation of callbacks.Handler + handler := &callbacks.HandlerBuilder{} + + // create an instance of Graph + // input type is 1st Graph Node's input type, that is ChatTemplate's input type: map[string]any + // output type is last Graph Node's output type, that is ToolsNode's output type: []*schema.Message + ch := compose.NewChain[[]*schema.Message, *schema.Message]() + + // append node here + // err = ch.AppendChatModel(chatTpl) + // if err != nil { + // logs.Errorf("AddChatTemplateNode failed, err=%v", err) + // return + // } + + // compile Graph[I, O] to Runnable[I, O] + r, err := ch.Compile() + if err != nil { + logs.Errorf("Compile failed, err=%v", err) + return + } + + _, err = r.Invoke(ctx, + []*schema.Message{ + schema.SystemMessage("you are a helpful assistant"), + schema.UserMessage("我叫 zhangsan, 邮箱是 zhangsan@bytedance.com, 帮我推荐一处房产"), + }, + compose.WithCallbacks(handler), + ) + + _, err = r.Stream(ctx, []*schema.Message{ + schema.SystemMessage("you are a helpful assistant"), + schema.UserMessage("我叫 zhangsan, 邮箱是 zhangsan@bytedance.com, 帮我推荐一处房产"), + }, + compose.WithCallbacks(handler), + ) + +} +``` + +# 定制切面 + +从定制切面的场景出发,会有两种定制需求: + +- **切面数据消费**:消费各切面点位上报的切面信息,实现业务定制化的横向治理能力 +- **组件切面上报**:实现一个组件时,定制切面上报的点位,定制上报的切面数据内容 + +## 切面数据消费 + +1. 切面数据的消费接口定义 + - CallbackInput 在接口定义中是一个 any, 推荐采用组件抽象定义的结构体进行上报。以 model 组件为例,推荐使用 model.CallbackInput{} + - CallbackOutput 同 CallbackInput,推荐采用组件抽象定义的结构体上报。 + - 采用推荐的预定义的结构体,有利于切面消费数据时,解析和理解数据内容 + +```go +// RunInfo is the info of run node. +type RunInfo struct { + Name string + Type string + Component components.Component +} + +// CallbackInput is the input of the callback. +// the type of input is defined by the component. +// using type Assert or convert func to convert the input to the right type you want. +// e.g. +// +// CallbackInput in components/model/interface.go is: +// type CallbackInput struct { +// Messages []*schema.Message +// Config *Config +// Extra map[string]any +// } +// +// and provide a func of model.ConvCallbackInput() to convert CallbackInput to *model.CallbackInput +// in callback handler, you can use the following code to get the input: +// +// modelCallbackInput := model.ConvCallbackInput(in) +// if modelCallbackInput == nil { +// // is not a model callback input, just ignore it +// return +// } +type CallbackInput any + +type CallbackOutput any + +type Handler interface { + OnStart(ctx context.Context, info *RunInfo, input CallbackInput) context.Context + OnEnd(ctx context.Context, info *RunInfo, output CallbackOutput) context.Context + + OnError(ctx context.Context, info *RunInfo, err error) context.Context + + OnStartWithStreamInput(ctx context.Context, info *RunInfo, + input *schema.StreamReader[CallbackInput]) context.Context + OnEndWithStreamOutput(ctx context.Context, info *RunInfo, + output *schema.StreamReader[CallbackOutput]) context.Context +} +``` + +1. 定义一个结构体,实现上面的 Handler 接口 + - **WARN****:OnStartWithStreamInput、OnEndWithStreamOutput 中的两个 input/output 流****必须要要关闭****,否则会导致流无法关闭回收,从而导致 Goroutine 或内存缓****慢泄露****。 ** + - 即在这两个函数中一定要调用:input.Close()、output.Close() + - 考虑到用户可能仅消费部分接口,并且对应的两个流式接口又必须要求 Close(),推荐采用实例化 callbacks.HandlerBuilder{} 的方式,可选实现其中的某几个 OnXXXFn 字段 + +```go +import ( + "context" + + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components/model" +) + +var callbackHandler1 = &callbacks.HandlerBuilder{ + + OnErrorFn: func(ctx context.Context, info *callbacks.RunInfo, err error) context.Context { + return ctx + }, + + OnStartFn: func(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context { + // 以 ChatModel 为例,将 input 转换为 model.CallbackInput{} + in := model.ConvCallbackInput(info) + _ = in + return ctx + }, + + OnStartWithStreamInputFn: func(ctx context.Context, info *callbacks.RunInfo, input *schema.StreamReader[callbacks.CallbackInput]) context.Context { + // 必须要关闭这个流,否则会导致 Goroutine 溢出 + defer input.Close() + // implement + return ctx + }, + + OnEndWithStreamOutputFn: func(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[callbacks.CallbackOutput]) context.Context { + // 必须要关闭这个流,否则会导致 Goroutine 溢出 + defer output.Close() + // implement + return ctx + }, +} +``` + +1. 按照 [切面注入](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects) 章节,选择合适的方式,将定制的消费切面注入到 Graph/Chain 即可 + +### WARN:Callback 流切记要 Close + +以存在 ChatModel 这种具有真流输出的节点为例,当存在 Callback 切面时,ChatModel 的输出流: + +所以此时需要将流进行复制,其复制关系如下: + +![](/img/eino/stream_copy_in_callback.png) + +- 如果其中一个 Callback n 没有 Close 对应的流,因此 Stream Coper 可能一直阻塞生产,无法退出,从而导致 Stream Coper 的资源无法及时释放。 + +## 组件切面上报 + +当用户定制实现一个组件时,可能因为需要 定制切面上报点位、定制切面上报内容等原因,导致不使用 Node Callback,而是选择定制实现 Component Callback。 + +组件切面定制上报逻辑的示例,以 ChatModel 为例: + +```go +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "runtime/debug" + "time" + + + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/schema" +) + + +type ChatModel struct {} + +func (cm *ChatModel) Generate(ctx context.Context, in []*schema.Message, opts ...model.Option) ( + outMsg *schema.Message, err error) { + + var ( + // 从 ctx 中获取 Node 执行时,由 Eino 框架注入的 CallbackManager(cbm) + cbm, cbmOK = callbacks.ManagerFromCtx(ctx) + ) + + defer func() { + // 如果 cbm 存在,并且产生错误,则在此处上报错误信息 + if err != nil && cbmOK { + _ = cbm.OnError(ctx, err) + } + }() + + // TODO: 在这里处理用户请求参数 + + // 如果 cbm 存在,则在组件逻辑真正执行前,上报请求信息 + if cbmOK { + ctx = cbm.OnStart(ctx, &model.CallbackInput{ + Messages: in, + Config: reqConf, + }) + } + + resp, err := cm.cli.CreateChatCompletion(ctx, *req) + if err != nil { + return nil, err + } + + // TODO: 在这里处理响应信息,并实现转换 + + // 在组件逻辑执行结束后,上报组件的处理结果 + if cbmOK { + _ = cbm.OnEnd(ctx, &model.CallbackOutput{ + Message: outMsg, + Config: reqConf, + TokenUsage: usage, + }) + } + + return outMsg, nil +} +``` + +注:当涉及到流式数据的切面上报时,需要将原来的流,Copy 出两份,一份作为输出,一份留作为 Callback 消费(CallbackManager 会根据 Callback Handler 的数量再次进行 Copy)。 针对流的相关操作,可参考 [Stream 流](/zh/docs/eino/overview) 章节 + +# 常见问题 + +- 调试环境问题 +- 采样率问题 +- 为什么出现了 goroutine 泄露? diff --git a/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/chain_graph_introduction.md b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/chain_graph_introduction.md new file mode 100644 index 0000000000..06f5d56353 --- /dev/null +++ b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/chain_graph_introduction.md @@ -0,0 +1,636 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: Chain/Graph 编排介绍' +weight: 1 +--- + +# Graph 编排 + +## Graph + +```go +import ( + "github.com/cloudwego/eino/compose" +) + +const ( + _nodeOfModel _= "model" + _nodeOfPrompt _= "prompt" +) + +func main() { + ctx := context.Background() + g := compose.NewGraph[map[string]any, *schema.Message]() + + pt := prompt.FromMessages( + schema.HumanMessage("what's the weather in {location}?"), + ) + + err := g.AddChatTemplateNode(_nodeOfPrompt_, pt) + assert.NoError(t, err) + + cm := &chatModel{ + msgs: []*schema.Message{ + { + Role: schema._AI_, + Content: "the weather is good", + }, + }, + } + + err = g.AddChatModelNode(_nodeOfModel_, cm, WithNodeName("MockChatModel")) + assert.NoError(t, err) + + err = g.AddEdge(_START_, _nodeOfPrompt_) + assert.NoError(t, err) + + err = g.AddEdge(_nodeOfPrompt_, _nodeOfModel_) + assert.NoError(t, err) + + err = g.AddEdge(_nodeOfModel_, _END_) + assert.NoError(t, err) + + r, err := g.Compile(WithMaxRunSteps(10)) + assert.NoError(t, err) + + in := map[string]any{"location": "beijing"} + ret, err := r.Invoke(ctx, in) + assert.NoError(t, err) + t.Logf("invoke result: %v", ret) + + // stream + s, err := r.Stream(ctx, in) + assert.NoError(t, err) +} +``` + +## ToolCallAgent + +```bash +go get github.com/cloudwego/eino-ext/components/model/openai@latest +go get github.com/cloudwego/eino@latest +``` + +```go +package main + +import ( + "context" + "os" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/components/tool/utils" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +func main() { + + accessKey := os.Getenv("OPENAI_API_KEY") + + ctx := context.Background() + + callbacks.InitCallbackHandlers([]callbacks.Handler{&loggerCallbacks{}}) + + const ( + _messageHistories _= "message_histories" + _messageUserQuery _= "user_query" + ) + + // 1. create an instance of ChatTemplate as 1st Graph Node + systemTpl := `你是一名房产经纪人,结合用户的薪酬和工作,使用 user_info API,为其提供相关的房产信息。邮箱是必须的` + chatTpl := prompt.FromMessages(schema._FString_, + schema.SystemMessage(systemTpl), + schema.MessagesPlaceholder(_messageHistories_, true), + schema.MessagesPlaceholder(_messageUserQuery_, false), + ) + + baseURL := "https://search.bytedance.net/gpt/openapi/online/multimodal/crawl" + + modelConf := &openai.ChatModelConfig{ + BaseURL: baseURL, + APIKey: accessKey, + ByAzure: true, + Model: "gpt-4o-2024-05-13", + Temperature: 0.7, + APIVersion: "2024-06-01", + } + + // 2. create an instance of ChatModel as 2nd Graph Node + chatModel, err := openai.NewChatModel(ctx, modelConf) + if err != nil { + logs.Errorf("NewChatModel failed, err=%v", err) + return + } + + // 3. create an instance of tool.InvokableTool for Intent recognition and execution + userInfoTool := utils.NewTool( + &schema.ToolInfo{ + Name: "user_info", + Desc: "根据用户的姓名和邮箱,查询用户的公司、职位、薪酬信息", + Params: map[string]*schema.ParameterInfo{ + "name": { + Type: "string", + Desc: "用户的姓名", + }, + "email": { + Type: "string", + Desc: "用户的邮箱", + }, + }, + }, + func(ctx context.Context, input *userInfoRequest) (output *userInfoResponse, err error) { + return &userInfoResponse{ + Name: input.Name, + Email: input.Email, + Company: "Bytedance", + Position: "CEO", + Salary: "9999", + }, nil + }) + + info, err := userInfoTool.Info(ctx) + if err != nil { + logs.Errorf("Get ToolInfo failed, err=%v", err) + return + } + // 4. bind ToolInfo to ChatModel. ToolInfo will remain in effect until the next BindTools. + err = chatModel.BindForcedTools([]*schema.ToolInfo{info}) + if err != nil { + logs.Errorf("BindForcedTools failed, err=%v", err) + return + } + + // 5. create an instance of ToolsNode as 3rd Graph Node + toolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{ + Tools: []tool.BaseTool{userInfoTool}, + }) + if err != nil { + logs.Errorf("NewToolNode failed, err=%v", err) + return + } + + const ( + _nodeKeyOfTemplate _= "template" + _nodeKeyOfChatModel _= "chat_model" + _nodeKeyOfTools _= "tools" + ) + + // 6. create an instance of Graph + // input type is 1st Graph Node's input type, that is ChatTemplate's input type: map[string]any + // output type is last Graph Node's output type, that is ToolsNode's output type: []*schema.Message + g := compose.NewGraph[map[string]any, []*schema.Message]() + + // 7. add ChatTemplate into graph + err = g.AddChatTemplateNode(_nodeKeyOfTemplate_, chatTpl) + if err != nil { + logs.Errorf("AddChatTemplateNode failed, err=%v", err) + return + } + + // 8. add ChatModel into graph + err = g.AddChatModelNode(_nodeKeyOfChatModel_, chatModel) + if err != nil { + logs.Errorf("AddChatModelNode failed, err=%v", err) + return + } + + // 9. add ToolsNode into graph + err = g.AddToolsNode(_nodeKeyOfTools_, toolsNode) + if err != nil { + logs.Errorf("AddToolsNode failed, err=%v", err) + return + } + + // 10. add connection between nodes + err = g.AddEdge(compose._START_, _nodeKeyOfTemplate_) + if err != nil { + logs.Errorf("AddEdge failed,start=%v, end=%v, err=%v", compose._START_, _nodeKeyOfTemplate_, err) + return + } + + err = g.AddEdge(_nodeKeyOfTemplate_, _nodeKeyOfChatModel_) + if err != nil { + logs.Errorf("AddEdge failed,start=%v, end=%v, err=%v", _nodeKeyOfTemplate_, _nodeKeyOfChatModel_, err) + return + } + + err = g.AddEdge(_nodeKeyOfChatModel_, _nodeKeyOfTools_) + if err != nil { + logs.Errorf("AddEdge failed,start=%v, end=%v, err=%v", _nodeKeyOfChatModel_, _nodeKeyOfTools_, err) + return + } + + err = g.AddEdge(_nodeKeyOfTools_, compose._END_) + if err != nil { + logs.Errorf("AddEdge failed,start=%v, end=%v, err=%v", _nodeKeyOfTools_, compose._END_, err) + return + } + + // 9. compile Graph[I, O] to Runnable[I, O] + r, err := g.Compile(ctx) + if err != nil { + logs.Errorf("Compile failed, err=%v", err) + return + } + + out, err := r.Invoke(ctx, map[string]any{ + _messageHistories_: []*schema.Message{}, + _messageUserQuery_: []*schema.Message{ + schema.UserMessage("我叫 zhangsan, 邮箱是 zhangsan@bytedance.com, 帮我推荐一处房产"), + }, + }) + if err != nil { + logs.Errorf("Invoke failed, err=%v", err) + return + } + logs.Infof("Generation: %v Messages", len(out)) + for _, msg := range out { + logs.Infof(" %v", msg) + } +} + +type userInfoRequest struct { + Name string `json:"name"` + Email string `json:"email"` +} + +type userInfoResponse struct { + Name string `json:"name"` + Email string `json:"email"` + Company string `json:"company"` + Position string `json:"position"` + Salary string `json:"salary"` +} + +type loggerCallbacks struct{} + +func (l *loggerCallbacks) OnStart(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context { + logs.Infof("name: %v, type: %v, component: %v, input: %v", info.Name, info.Type, info.Component, input) + return ctx +} + +func (l *loggerCallbacks) OnEnd(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) context.Context { + logs.Infof("name: %v, type: %v, component: %v, output: %v", info.Name, info.Type, info.Component, output) + return ctx +} + +func (l *loggerCallbacks) OnError(ctx context.Context, info *callbacks.RunInfo, err error) context.Context { + logs.Infof("name: %v, type: %v, component: %v, error: %v", info.Name, info.Type, info.Component, err) + return ctx +} + +func (l *loggerCallbacks) OnStartWithStreamInput(ctx context.Context, info *callbacks.RunInfo, input *schema.StreamReader[callbacks.CallbackInput]) context.Context { + return ctx +} + +func (l *loggerCallbacks) OnEndWithStreamOutput(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[callbacks.CallbackOutput]) context.Context { + return ctx +} +``` + +## State Graph + +```go +package main + +import ( + "context" + "errors" + "io" + "log" + "strings" + "unicode/utf8" + + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + const ( + _nodeOfL1 _= "invokable" + _nodeOfL2 _= "streamable" + _nodeOfL3 _= "transformable" + ) + + type testState struct { + ms []string + } + + gen := func(ctx context.Context) *testState { + return &testState{} + } + + sg := compose.NewGraph[string, string](compose.WithGenLocalState(gen)) + + l1 := compose.InvokableLambda(func(ctx context.Context, in string) (out string, err error) { + return "InvokableLambda: " + in, nil + }) + + l1StateToInput := func(ctx context.Context, in string, state *testState) (string, error) { + state.ms = append(state.ms, in) + return in, nil + } + + l1StateToOutput := func(ctx context.Context, out string, state *testState) (string, error) { + state.ms = append(state.ms, out) + return out, nil + } + + err := sg.AddLambdaNode(_nodeOfL1_, l1, + compose.WithStatePreHandler(l1StateToInput), compose.WithStatePostHandler(l1StateToOutput)) + if err != nil { + log.Printf("sg.AddLambdaNode failed, err=%v", err) + return + } + + l2 := compose.StreamableLambda(func(ctx context.Context, input string) (output *schema.StreamReader[string], err error) { + outStr := "StreamableLambda: " + input + + sr, sw := schema.Pipe[string](utf8.RuneCountInString(outStr)) + + go func() { + for _, field := range strings.Fields(outStr) { + sw.Send(field+" ", nil) + } + sw.Close() + }() + + return sr, nil + }) + + l2StateToOutput := func(ctx context.Context, out string, state *testState) (string, error) { + state.ms = append(state.ms, out) + return out, nil + } + + err = sg.AddLambdaNode(_nodeOfL2_, l2, compose.WithStatePostHandler(l2StateToOutput)) + if err != nil { + log.Printf("sg.AddLambdaNode failed, err=%v", err) + return + } + + l3 := compose.TransformableLambda(func(ctx context.Context, input *schema.StreamReader[string]) ( + output *schema.StreamReader[string], err error) { + + prefix := "TransformableLambda: " + sr, sw := schema.Pipe[string](20) + + go func() { + for _, field := range strings.Fields(prefix) { + sw.Send(field+" ", nil) + } + + for { + chunk, err := input.Recv() + if err != nil { + if err == io.EOF { + break + } + // _TODO: how to trace this kind of error in the goroutine of processing sw_ +_ _sw.Send(chunk, err) + break + } + + sw.Send(chunk, nil) + + } + sw.Close() + }() + + return sr, nil + }) + + l3StateToOutput := func(ctx context.Context, out string, state *testState) (string, error) { + state.ms = append(state.ms, out) + log.Printf("state result: ") + for idx, m := range state.ms { + log.Printf(" %vth: %v", idx, m) + } + return out, nil + } + + err = sg.AddLambdaNode(_nodeOfL3_, l3, compose.WithStatePostHandler(l3StateToOutput)) + if err != nil { + log.Printf("sg.AddLambdaNode failed, err=%v", err) + return + } + + err = sg.AddEdge(compose._START_, _nodeOfL1_) + if err != nil { + log.Printf("sg.AddEdge failed, err=%v", err) + return + } + + err = sg.AddEdge(_nodeOfL1_, _nodeOfL2_) + if err != nil { + log.Printf("sg.AddEdge failed, err=%v", err) + return + } + + err = sg.AddEdge(_nodeOfL2_, _nodeOfL3_) + if err != nil { + log.Printf("sg.AddEdge failed, err=%v", err) + return + } + + err = sg.AddEdge(_nodeOfL3_, compose._END_) + if err != nil { + log.Printf("sg.AddEdge failed, err=%v", err) + return + } + + run, err := sg.Compile(ctx) + if err != nil { + log.Printf("sg.Compile failed, err=%v", err) + return + } + + out, err := run.Invoke(ctx, "how are you") + if err != nil { + log.Printf("run.Invoke failed, err=%v", err) + return + } + log.Printf("invoke result: %v", out) + + stream, err := run.Stream(ctx, "how are you") + if err != nil { + log.Printf("run.Stream failed, err=%v", err) + return + } + + for { + + chunk, err := stream.Recv() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + log.Printf("stream.Recv() failed, err=%v", err) + break + } + + log.Printf(chunk) + } + stream.Close() + + sr, sw := schema.Pipe[string](1) + sw.Send("how are you", nil) + sw.Close() + + stream, err = run.Transform(ctx, sr) + if err != nil { + log.Printf("run.Transform failed, err=%v", err) + return + } + + for { + + chunk, err := stream.Recv() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + log.Printf("stream.Recv() failed, err=%v", err) + break + } + + log.Printf(chunk) + } + stream.Close() +} +``` + +# Chain + +> Chain 可以视为是 Graph 的简化封装 + +```go +package main + +import ( + "context" + "fmt" + "log" + "math/rand" + "os" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +func init() { + apiKey := os.Getenv("OPENAI_API_KEY") + baseURL := os.Getenv("OPENAI_BASE_URL") // using azure + + if apiKey == "" || baseURL != "" { + return + } +} + +func main() { + ctx := context.Background() + // 分支节点 + const _randLimit _= 2 + branchCond := func(ctx context.Context, input map[string]any) (string, error) { // nolint: byted_all_nil_return + if rand.Intn(_randLimit_) == 1 { + return "b1", nil + } + + return "b2", nil + } + + b1 := compose.InvokableLambda(func(ctx context.Context, kvs map[string]any) (map[string]any, error) { + fmt.Println("hello in branch lambda 01") + if kvs == nil { + return nil, fmt.Errorf("nil map") + } + + kvs["role"] = "cat" + return kvs, nil + }) + + b2 := compose.InvokableLambda(func(ctx context.Context, kvs map[string]any) (map[string]any, error) { + fmt.Println("hello in branch lambda 02") + if kvs == nil { + return nil, fmt.Errorf("nil map") + } + + kvs["role"] = "dog" + return kvs, nil + }) + + // 并发节点 + parallel := compose.NewParallel() + parallel. + AddLambda("role", compose.InvokableLambda(func(ctx context.Context, kvs map[string]any) (string, error) { + // may be change role to others by input kvs, for example (dentist/doctor...) + role, ok := kvs["role"].(string) + if !ok || role == "" { + role = "bird" + } + + return role, nil + })). + AddLambda("input", compose.InvokableLambda(func(ctx context.Context, kvs map[string]any) (string, error) { + return "你的叫声是怎样的?", nil + })) + + // 顺序节点 + cm, err := openai.NewChatModel(context.Background(), nil) + if err != nil { + log.Panic(err) + return + } + + rolePlayerChain := compose.NewChain[map[string]any, *schema.Message]() + rolePlayerChain. + AppendChatTemplate(prompt.FromMessages(schema._FString_, schema.SystemMessage(`You are a {role}.`), schema.UserMessage(`{input}`))). + AppendChatModel(cm) + + // =========== 构建 chain =========== + chain := compose.NewChain[map[string]any, string]() + chain. + AppendLambda(compose.InvokableLambda(func(ctx context.Context, kvs map[string]any) (map[string]any, error) { + // do some logic to prepare kv as input val for next node + // just pass through + fmt.Println("in view lambda: ", kvs) + return kvs, nil + })). + AppendBranch(compose.NewChainBranch(branchCond).AddLambda("b1", b1).AddLambda("b2", b2)). // nolint: byted_use_receiver_without_nilcheck + AppendParallel(parallel). + AppendGraph(rolePlayerChain). + AppendLambda(compose.InvokableLambda(func(ctx context.Context, m *schema.Message) (string, error) { + // do some logic to check the output or something + fmt.Println("in view of messages: ", m.Content) + + return m.Content, nil + })) + + // compile + r, err := chain.Compile(ctx) + if err != nil { + log.Panic(err) + return + } + + output, err := r.Invoke(context.Background(), map[string]any{}) + if err != nil { + log.Panic(err) + return + } + + fmt.Println("output is : ", output) + +} +``` diff --git a/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles.md b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles.md new file mode 100644 index 0000000000..2aebeb5cf1 --- /dev/null +++ b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles.md @@ -0,0 +1,273 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: 编排的设计理念' +weight: 2 +--- + +大模型应用编排方案中,最火的就是 langchain 以及 langgraph,其官方提供了 python 和 ts 的 sdk,这两门语言以其灵活性著称,灵活性给 sdk 的开发带来了极大的便利,但同时,也给 sdk 的使用者带来了极大的困扰和心智负担。 + +golang 作为一门 极其简单 的编程语言,确定的 `静态类型` 是让其变得简单的重要原因之一,而 eino,则保持了这一重要特性: `确定的类型` + `Compile 时类型检查`。 + +## 以上下游 `类型对齐` 为基本准则 + +eino 的最基础编排方式为 graph,以及简化的封装 chain。不论是哪种编排方式,其本质都是 `逻辑节点` + `上下游关系` 。在编排的产物运行时,都是从一个逻辑节点运行,然后下一步运行和这个节点相连的下一个节点。 + +这之间蕴含了一个基本假设:**前一个运行节点的输出值,可以作为下一个节点的输入值。** + +在 golang 中,要实现这个假设,有两个基本方案: + +1. 把不同节点的输入输出都变成一种更泛化的类型,例如 `any` 、`map[string]any` 等。 + + 1. 老版本的 eino 就是采用泛化成 any 的方案,但对应的代价是: 开发者在写代码时,需要显式转换成具体类型才能使用。这会极大增加开发者的心智负担,因此最终放弃此方案。 + 2. langchain 的方案可以看做是全程传递 `map[string]any`,各个逻辑节点根据自己的需要,用对应的 key 去取对应的 value。在 langchaingo 的实现中,即是按照这种方式实现,但同样,golang 中的 any 要被使用依然要使用 `类型断言` 才可使用。这种方案在开发者使用时依然有很大的心智负担。 +2. 每一个节点的输入输出类型保持开发者的预期,在 Compile 阶段保证上下游的类型是一致的。 + +方案 2 即是 eino 最终选定的方案。这种方案是编排时最容易被理解的,整个过程就像是 `搭积木` 一样,每一个积木突出的部分和凹陷的部分有各自的规格,仅有规格匹配了才能成为上下游关系。 + +就如下图: + +![](/img/eino/edge_type_validate.png) + +对于一个编排而言,只有下游能识别和处理上游的输出,这个编排才能正常运行。 这个基本假设在 eino 中被清晰地表达了出来,让开发者在用 eino 做编排时,能够有十足的信心清楚编排的逻辑是如何运行和流转的,而不是从一系列的 any 中去猜测传过来的值是否正确。 + +### graph 中的类型对齐 + +#### edge + +在 graph 中,一个节点的输出将顺着 `边(edge)` 流向下一节点,因此,用边连接的节点间必须要类型对齐。 + +如下图: + +> 这是一个模拟 ① 直接和大模型对话 ② 使用 RAG 模式 的场景,最后结果可用于对比两种模式的效果 + +![](/img/eino/input_output_type_validate.png) + +图中绿色的部分,就是普通的 Edge 连接,其要求上游的输出必须能 `assign` 给下游,可以接收的类型有: + +① 上下游类型相同: 例如上游输出 *schema.Message 下游输入也是 *schema.Message + +② 下游接收接口,上游实现了该接口: 例如上游结构体实现了 Format() 接口,下游接收的是一个 interface{ Format() } + +③ 下游接受方为 any: 空接口,可以认为任何上游都可以转成一个 any 传给下游 + +④ 上游是 any,下游是具体类型: 当上游可被转换成下游类型时,详细描述可见: [Eino: 编排的设计理念](/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles) + +图中黄色的部分,则是 eino 提供的另一个类型转换的机制,即: 若下游接收的类型是 `map[string]any`,可以使用 `graph.AddXXXNode(node_key, xxx, compose.WithOutputKey("outkey")` 的方式指定给下游输入的一个 key。 + +同理,若上游是 `map[string]any` ,则可以使用 `graph.AddXXXNode(node_key, xxx, compose.WithInputKey("inkey")` 来获取上游输出的其中一个 key 的 value。 + +一般在多条边汇聚到某一个节点时,这种机制使用起来较为方便。 + +#### branch + +如果一个节点后面连接了多个 edge,则每条 edge 的下游节点都会运行一次。branch 则是另一种机制: 一个 branch 后接了 n 个节点,但仅会运行 condition 返回的那个 node key 对应的节点。同一个 branch 后的节点,必须要类型对齐。 + +如下图: + +> 这是一个模拟 react agent 的运行逻辑 + +可以看到,一个 branch 本身拥有一个 `condition`, 这个 function 的输入必须和上游类型对齐。同时,一个 branch 后所接的各个节点,也必须和 condition 一样,要能接收上游的输出。 + +### chain 中的类型对齐 + +#### chain + +从抽象角度看,chain 就是一个 `链条`,如下所示: + +![](/img/eino/what_is_chain.png) + +逻辑节点的类型可以分为 3 类: + +- 可编排组件 (例如 chat model、 chat template、 retriever、 lambda、graph 等等) +- branch 节点 +- parallel 节点 + +可以看到,在 chain 的视角下,不论是简单的节点(eg: chat model) 还是复杂的节点 (eg: graph、branch、parallel),都是一样的,在运行过程中,一步的执行就是一个节点的运行。 + +也因此,chain 的上下游节点间,类型必须是对齐的,如下: + +```go +func TestChain() { + chain := compose.NewChain[map[string]interface,string]() + + nodeTemplate := &fakeChatTemplate{} // input: map[string]any, output: []*schema.Message + + nodeHistoryLambda := &fakeLambda{} // input: []*schema.Message, output: []*schema.Message + + nodeChatModel := &fakeChatModel{} // input: []*schema.Message, output: *schema.Message + + nodeConvertResLambda := &fakeLambda{} // input: *schema.Message, output: string + + chain. + AppendChatTemplate(nodeTemplate). + AppendLambda(nodeHistoryLambda). + AppendChatModel(nodeChatModel). + AppendLambda(nodeConvertResLambda) +} +``` + +上面的逻辑用图来表示如下: + +![](/img/eino/nodes_type_validate.png) + +若上下游的类型没有对齐,chain 会在 chain.Compile() 时返回错误。而 graph 会在 graph.AddXXXNode() 时就报错。 + +#### parallel + +parallel 在 chain 中是一类特殊的节点,从 chain 的角度看 parallel 和其他的节点没啥区别。在 parallel 内部,其基本拓扑结构如下: + +![](/img/eino/same_type_of_parallel.png) + +graph 中的多 edge 形成的结构其中一种就是这个,这里的基本假设是: 一个 parallel 的每一条边上有且仅有一个节点。当然,这一个节点也可以是 graph。但注意,目前框架没有直接提供在 parallel 中嵌套 branch 或 parallel 的能力。 + +在 parallel 中的每个节点,由于其上游节点是同一个,因此他们都要和上游节点的输出类型对齐,比如图中上游节点输出了 `*schema.Message` ,则每个节点都要能接收这个类型。接收的方式和 graph 中的一致,通常可以用 `相同类型` 、`接口定义` 、`any`、`input key option` 的方式。 + +parallel 节点的输出一定是一个 `map[string]any`,其中的 key 则是在 `parallel.AddXXX(output_key, xxx, opts...)` 时指定的 output_key。 + +一个 parallel 的构建例子如下: + +```go +func TestParallel() { + chain := compose.NewChain[map[string]any, map[string]*schema.Message]() + templateNode := &fakeTemplateNode{} // input: map[string]any, output: []*schema.Message + + parallel := compose.NewParallel() + model01 := &fakeChatModel{} // input: []*schema.Message, output: *schema.Message + model02 := &fakeChatModel{} // input: []*schema.Message, output: *schema.Message + model03 := &fakeChatModel{} // input: []*schema.Message, output: *schema.Message + + parallel. + AddChatModel("outkey_01", model01). + AddChatModel("outkey_02", model02). + AddChatModel("outkey_03", model03) + + lambdaNode := &fakeLambdaNode{} // input: map[string]any, output: map[string]*schema.Message + + chain. + AppendParallel(parallel). + AppendLambda(lambdaNode) +} +``` + +一个 parallel 在 chain 中的视角如下: + +> 图中是模拟同一个提问,由不同的大模型去回答,结果可用于对比效果 + +![](/img/eino/graph_as_chain_node.png) + +> 需要注意的是,这个结构只是逻辑上的视角,由于 chain 本身也是用 graph 实现的,parallel 在底层 graph 中会平铺到图中。 + +#### branch + +chain 的 branch 和 graph 中的 branch 类似,branch 中的所有节点都要和上游节点的类型对齐,此处不再赘述。 + +### invoke 和 stream 下的类型对齐方式 + +在 eino 中,编排的结果是 graph 或 chain,若要运行,则需要使用 `Compile()` 来生成一个 `Runnable` 接口。 + +Runnable 的一个重要作用就是提供了 `invoke`、`stream`、`collect`、`transform` 几种调用方式的降级兼容。 + +> 上述几种调用方式的介绍以及详细的 Runnable 介绍可以查看: [Eino: 基础概念介绍](/zh/docs/eino/overview) + +以我们最常见的 invoke 和 stream 模式为例,其接口签名如下: + +```go +type Runnable[I, O any] interface { + Invoke(ctx context.Context, input I, opts ...Option) (output O, err error) + Stream(ctx context.Context, input I, opts ...Option) (output *schema.StreamReader[O], err error) +} +``` + +以 chat model 的场景为例,Runnable 加上 I,O 类型后签名如下: + +```go +type Runnable interface { + Invoke(ctx context.Context, input []*schema.Message, opts ...Option) (output *schema.Message, err error) + Stream(ctx context.Context, input []*schema.Message, opts ...Option) (output *schema.StreamReader[*schema.Message], err error) +} +``` + +在 Invoke 模式下,返回的 output 的类型为 `*schema.Message`; 在 Stream 模式下,其返回的 output 类型必须为 `*schema.StreamReader[*schema.Message]`。也就是 stream 的每一帧的类型和 invoke 的结果类型是相同的。 + +一般来说,Stream 得到的每一帧合并起来应当和 invoke 的结果相同,在上面这个场景中,也即要求: + +```go +func TestInvokeAndStream() { + var r Runnable[[]*schema.Message, *schema.Message] + + reader, err := r.Stream(...) + allFrames := make([]*schema.Message, 0) + for { + frame, err := reader.Recev() + ... + allFrames = append(allFrames, frame) + ... + } + + invokeRes, err := r.Invoke(...) + + // allFrames 合并后需要和 invokeRes 相同 +} +``` + +在 stream 模式下,`合并帧` 是一个非常常见的操作,例如在和大模型的交互中,可以把已经接收到的所有帧合并,得到一个完整的输出。 + +另外,在框架中,当一个仅提供了 Stream 接口的节点,被编排后使用 Invoke 调用,框架则会把 Stream 降级为 Invoke,此时的操作是 底层调用开发者提供的 Stream 接口,获取完整的帧后,把所有帧合并,得到的结果再流转到下一节点。 这个过程中,也是使用的 `合并帧` 。 + +框架内已经内置支持了如下类型的合并: + +- `*schema.Message`: 详情见 `schema.ConcatMessages()` +- `string`: 实现逻辑等同于 `+=` +- `[]*schema.Message`: 详情见 `compose.concatMessageArray()` +- `Map`: 把相同 key 的 val 进行合并,合并逻辑同上,若存在无法合并的类型,则失败 (ps: 不是覆盖) + +对于自定义类型,则需要开发者自行实现 concat 方法,并使用 `compose.RegisterStreamChunkConcatFunc()` 注册到全局的合并函数中。 + +示例如下: + +```go +// 假设我们自己的结构体如下 +type tStreamConcatItemForTest struct { + s string +} + +// 实现一个合并的方法 +func concatTStreamForTest(items []*tStreamConcatItemForTest) (*tStreamConcatItemForTest, error) { + var s string + for _, item := range items { + s += item.s + } + + return &tStreamConcatItemForTest{s: s}, nil +} + +func init() { + // 注册到全局的合并方法中 + compose.RegisterStreamChunkConcatFunc(concatTStreamForTest) +} +``` + +### 类型对齐在运行时检查的场景 + +eino 的 Graph 类型对齐检查,会在 `err = graph.AddEdge("node1", "node2")` 时检查两个节点类型是否匹配,也就能在 `构建 graph 的过程`,或 `Compile 的过程` 发现类型不匹配的错误,这适用于 [Eino: 编排的设计理念](/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles) 中所列举的 ① ② ③ 条规则。 + +当上游节点的输出为 `any` 时,若下游节点为确定的类型 (结构体、接口等等),则上游有可能可以转成下游类型 (类型断言),但只能在 `运行过程` 才能清楚能否转换成功,该场景的类型检查移到了运行过程中。 + +其结构可见下图: + +![](/img/eino/input_type_output_type_in_edge.png) + +这种场景适用于开发者能自行处理好上下游类型对齐的情况,可根据不同类型选择下游执行节点。 + +## 大模型场景的编排 + +eino 在编排中的是以大模型应用为核心场景的编排系统,因此在 eino 的编排设计中,是直接把 `component` 作为了编排的直接主体,封装了在大模型应用中最常用的组件,详细的 API 查看: [Eino: 基础概念介绍](/zh/docs/eino/overview) + +大多数情况下,业务的实现应当把自己的组件实现为上述组件中的一种,或者直接使用 eino-ext 中已经封装好的组件。 + +当然,除了上述标准的组件外,还有很多场景我们需要实现一些自定义的代码逻辑,在 eino 中,这就是 `Lambda` 组件。这是一个很泛化的组件,可以基于这个基础组件实现几乎所有的需求,实际上,在 eino 内部,上述的组件也都是使用 lambda 来实现的。 + +> 更多信息可以参考: [Eino: Components 抽象&实现](/zh/docs/eino/core_modules/components) diff --git a/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials.md b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials.md new file mode 100644 index 0000000000..9c7e11a222 --- /dev/null +++ b/content/zh/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials.md @@ -0,0 +1,228 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: Eino 流式编程要点 +weight: 0 +--- + +> 💡 +> 建议先看:[Eino: 基础概念介绍](/zh/docs/eino/overview) [Eino: 编排的设计理念](/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles) + +# 编排流式概述 + +![](/img/eino/eino_component_runnable.png) + +编排流式的 Graph 时,需要考虑的几个关键要素: + +- 组件/Lambda 中包含哪几种 Lambda 算子: 从 Invoke、Stream、Collect、Transform 中任选 +- 编排拓扑图中,上下游节点的输入、输出是否同为流或同为非流。 +- 如果上下游节点的流类型无法匹配。 需要借助 流化、合包 两个操作 + + - 流化(Streaming):将 T 流化成单 Chunk 的 Stream[T] + - 合包(Concat):将 Stream[T] 合并成一个完整的 T。Stream[T] 中的每一“帧”是这个完整 T 的一部分。 + +# Eino 流式编程的内涵 + +- 有的组件,天然支持分“帧”来输出,每次输出一个完整出参的一部分,即“流式”输出。流式输出完成后,需要下游把这些“帧”拼接(concat)成完整的出参。典型的例子,是 LLM。 +- 有的组件,天然支持分“帧”来输入,接收到不完整的入参时,就能开始有意义的业务处理,甚至完成业务处理的过程。比如 react agent 中用来判断是调 tool 还是结束运行的 branch 里面,拿到 LLM 的流式输出,从第一个帧里面就可以通过判断 message 是否包含 tool call 来做出决策。 +- 因此,一个组件,从入参角度看,有“非流式”入参和“流式”入参两种,从出参角度看,有“非流式”出参和“流式”出参两种。 +- 组合起来,有四种可能的流式编程范式 + +# 单个组件角度的流式 + +Eino 是个 "component first" 的框架,组件可以独立使用。定组件接口的时候,需要考虑流式编程的问题吗?简单的答案是不需要。复杂的答案是“以业务真实场景为准”。 + +## 组件自身的业务范式 + +一个典型的组件,比如 Chat Model,Retriever 等,根据实际的业务语义定接口就行,如果实际上支持某种流式的范式,就实现那一种流式范式,如果实际上某种流式范式没有真正的业务场景,那就不需要实现。比如 + +- Chat Model,除了 Invoke 这种非流式的范式外,还天然支持 Stream 这种流式范式,因此 Chat Model 的接口中,实现了 Generate 和 Stream 两个接口。但是 Collect 和 Transform 没有对应的真实业务场景,所以就没有实现相应的接口: + +```go +type ChatModel interface { + Generate(ctx context.Context**, **input []*schema.Message**, **opts ...Option) (*schema.Message**, **error) + Stream(ctx context.Context**, **input []*schema.Message**, **opts ...Option) ( + *schema.StreamReader[*schema.Message]**, **error) +} +``` + +- Retriever,除了 Invoke 这种非流式的范式外,另外三种流式范式都没有真实的业务场景,因此只实现了 Retrieve 一个接口: + +```go +type Retriever interface { + Retrieve(ctx context.Context**, **query string**, **opts ...Option) ([]*schema.Document**, **error) +} +``` + +## 组件具体支持的范式 + + + + + + + + + + + + + + + + + + + + +
组件名称
是否实现 Invoke
是否实现 Stream
是否实现 Collect
是否实现 Transform
Chat model
yes
yes
no
no
Chat template
yes
no
no
no
Retriever
yes
no
no
no
Indexer
yes
no
no
no
Embedder
yes
no
no
no
Document Loader
yes
no
no
no
Document Transformer
yes
no
no
no
Tool
yes
yes
no
no
+ +Eino 官方组件中,除了 Chat Model 和 Tool 额外支持 stream 外,其他所有组件都只支持 invoke。组件具体介绍参见:[[更新中]Eino: Components 抽象&实现](/zh/docs/eino/core_modules/components) + +Collect 和 Transform 两种流式范式,目前只在编排场景有用到。 + +# 多个组件编排角度的流式 + +## 组件在编排中的流式范式 + +一个组件,单独使用时,入参和出参的流式范式是框定的,不可能超出组件定义的接口范围。 + +- 比如 Chat Model,入参只可能是非流式的 []Message,出参则可能是非流式的 Message 或者流式的 StreamReader[Message],因为 Chat Model 只实现了 Invoke 和 Stream 两个范式。 + +但是,一个组件,一旦处在多个组件组合使用的“编排”场景中,它的入参和出参就没那么固定了,而是取决于这个组件在编排场景中的“上游输出”和“下游输入”。比如 React Agent 的典型编排示意图: + +![](/img/eino/chatmodel_to_tool.png) + +上图中,如果 Tool 是个 StreamableTool,也就是输出是 StreamReader[Message],则 Tool -> ChatModel 就可能是流式的输出。但是 Chat Model 并没有接收流式输入的业务场景,也没有对应的接口。这时 Eino 框架会自动帮助 ChatModel 补足接收流式输入的能力: + +![](/img/eino/chatmodel_tool_loop.png) + +上面的 Concat message stream 是 Eino 框架自动提供的能力,即使不是 message,是任意的 T,只要满足特定的条件,Eino 框架都会自动去做这个 StreamReader[T] 到 T 的转化,这个条件是:**在编排中,当一个组件的上游输出是 StreamReader[T],但是组件只提供了 T 作为输入的业务接口时,框架会自动将 StreamReader[T] concat 成 T,再输入给这个组件。** + +> 💡 +> 框架自动将 StreamReader[T] concat 成 T 的过程,可能需要用户提供一个 Concat function。详见 [Eino: 编排的设计理念](/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles#share-FaVnd9E2foy4fAxtbTqcsgq3n5f) 中关于“合并帧”的章节。 + +另一方面,考虑一个相反的例子。还是 React Agent,这次是一个更完整的编排示意图: + +![](/img/eino/tool_model_react.png) + +在上图中,branch 接收 chat model 输出的 message,并根据 message 中是否包含 tool call,来选择直接结束 agent 本次运行并将 message 输出,还是调用 Tool 并将调用结果再次给 Chat Model 循环处理。由于这个 Branch 可以通过 message stream 的首个帧就完成逻辑判断,因此我们给这个 Branch 定义的是 Collect 接口,即流式输入,非流式输出: + +```go +compose.NewStreamGraphBranch(func(ctx context.Context**, **sr *schema.StreamReader[*schema.Message]) (endNode string**, **err error) { + msg**, **err := sr.Recv() + if err != nil { + return ""**, **err + } + defer sr.Close() + + if len(msg.ToolCalls) == **0 **{ + return compose._END_**, **nil + } + + return nodeKeyTools**, **nil +} +``` + +ReactAgent 有两个接口,Generate 和 Stream,分别实现了 Invoke 和 Stream 的流式编程范式。当一个 ReactAgent 以 Stream 的方式被调用时,Chat Model 的输出是 StreamReader[Message],因此 Branch 的输入是 StreamReader[Message],符合这个 Branch condition 的函数签名定义,不需要做任何的转换就可以运行。 + +但是,当这个 ReactAgent 以 Generate 的方式被调用时,Chat Model 的输出是 Message,因此 Branch 的输入也会是 Message,不符合 Branch Condition 的 StreamReader[Message] 的函数签名定义。这时,Eino 框架会自动将 Message 装箱成 StreamReader[Message],再传给 Branch,而这个 StreamReader 里面只会有一个帧。 + +> 💡 +> 这种只有一个帧的流,俗称“假流”,因为它并没有带来流式的实际好处即“首包延迟低”,而是仅仅为了满足流式出入参接口签名的要求而做的简单装箱。 + +总结起来,就是:**在编排中,当一个组件的上游输出是 T,但是组件只提供了 StreamReader[T] 作为输入的业务接口时,框架会自动将 T 装箱成 StreamReader[T] 的单帧流,再输入给这个组件。** + +## 编排辅助元素的流式范式 + +上面提到的 Branch,并不是一个可单独使用的组件,而是只在编排场景中才有意义的“编排辅助元素”,类似的仅编排场景有意义的“组件”,还有一些,详见下图: + + + + + + + + + + + + + + +
组件名称
使用场景
是否实现 Invoke
是否实现 Stream
是否实现 Collect
是否实现 Transform
Branch
根据上游输出,在一组下游 Node 中动态选择一个
- 只能在接收到完整入参后才能判断的,实现 Invoke
- 可以在接收部分帧后做判断的,实现 Collect
- 两者只能实现一个
yes
no
yes
no
StatePreHandler
Graph中,进入 Node 前修改 State 或/与 Input。可支持流式。
yes
no
no
yes
StatePostHandler
Graph中,Node 完成后修改 State 或/与 Output。可支持流式
yes
no
no
yes
Passthrough
在并行情况下,为了打平每个并行分支的 Node 个数,可以给 Node 个数少的分支加 Passthrough 节点。Passthrough 节点的输入输出相同,跟随上游节点的输出或跟随下游节点的输入(预期应当相同)。
yes
no
no
yes
Lambda
封装官方组件未定义的业务逻辑。业务逻辑是哪种范式,就选择对应的那种流式范式来实现。
yes
yes
yes
yes
+ +另外还有一种只有编排场景才有意义的“组件”,就是把编排产物作为一个整体来看待,比如编排后的 Chain,Graph。这些整体的编排产物,既可以作为“组件”来单独调用,也可以作为节点加入到更上级的编排产物中。 + +# 编排整体角度的流式 + +## 编排产物的“业务”范式 + +既然整体的编排产物,可以被看做一个“组件”,那从组件的视角可以提出问题:编排产物这个“组件”,有没有像 Chat Model 等组件那样的,符合“业务场景”的接口范式?答案是既“有”也“没有”。 + +- “没有”:整体而言,Graph,Chain 等编排产物,自身是没有业务属性的,只为抽象的编排服务的,因此也就没有符合业务场景的接口范式。同时,编排需要支持各种范式的业务场景。所以,Eino 中代表编排产物的 Runnable[I, O] 接口,不做选择也无法选择,提供了所有流式范式的方法: + +```go +type Runnable[I**, **O any] interface { + Invoke(ctx context.Context**, **input I**, **opts ...Option) (output O**, **err error) + Stream(ctx context.Context**, **input I**, **opts ...Option) (output *schema.StreamReader[O]**, **err error) + Collect(ctx context.Context**, **input *schema.StreamReader[I]**, **opts ...Option) (output O**, **err error) + Transform(ctx context.Context**, **input *schema.StreamReader[I]**, **opts ...Option) (output *schema.StreamReader[O]**, **err error) +} +``` + +- “有”:具体而言,某一个具体的 Graph、Chain,一定是承载了具体的业务逻辑的,因此也就一定有适合那个特定业务场景的流式范式。比如类似 React Agent 的 Graph,匹配的业务场景是 Invoke 和 Stream,因此这个 Graph 在调用时,符合逻辑的调用方式是 Invoke 和 Stream。虽然编排产物本身接口 Runnable[I, O] 中有 Collect 和 Transform 的方法,但是正常的业务场景不需要使用。 + +## 编排产物内部各组件在运行时的范式 + +从另一个角度看,既然编排产物整体可以被看做“组件”,那“组件”必然有自己的内部实现,比如 ChatModel 的内部实现逻辑,可能是把入参的 []Message 转化成各个模型的 API request,之后调用模型的 API,获取 response 后再转化成出参的 Message。那么类比的话,Graph 这个“组件”的内部实现是什么?是数据在 Graph 内部各个组件间以用户指定的流转方向和流式范式来流转。其中,“流转方向”不在当前讨论范围内,而各组件运行时的流式范式,则由 Graph 整体的触发方式决定,具体来说: + +- 如果用户通过 Invoke 来调用 Graph,则 Graph 内部所有组件都以 Invoke 范式来调用。如果某个组件,没有实现 Invoke 范式,则 Eino 框架自动根据组件实现了的流式范式,封装出 Invoke 调用范式,优先顺位如下: + + - 若组件实现了 Stream,则通过 Stream 封装 Invoke,即自动 concat 输出流。 + ![](/img/eino/invoke_outside_stream_inside.png) + - 否则,若组件实现了 Collect,则通过 Collect 封装 Invoke,即非流式入参转单帧流。 + ![](/img/eino/invoke_outside_collect_inside.png) + - 如果都没实现,则必须实现 Transform,通过 Transform 封装 Invoke,即入参转单帧流,出参 concat。 + ![](/img/eino/invoke_outside_transform_inside.png) +- 如果用户通过 Stream/Collect/Transform 来调用 Graph,则 Graph 内部所有组件都以 Transform 范式来调用。如果某个组件,没有实现 Transform 范式,则 Eino 框架自动根据组件实现了的流式范式,封装出 Transform 调用范式,优先顺位如下: + + - 若组件实现了 Stream,则通过 Stream 封装 Transform,即自动 concat 输入流。 + ![](/img/eino/transform_inside_stream_inside.png) + - 否则,若组件实现了 Collect,则通过 Collect 封装 Transform,即非流式出参转单帧流。 + ![](/img/eino/transform_outside_stream_inside.png) + - 如果都没实现,则必须实现 Invoke,通过 Invoke 封装 Transform,即入参流 concat,出参转单帧流 + ![](/img/eino/transform_outside_invoke_inside.png) + +结合上面穷举的各种案例,Eino 框架对 T 和 Stream[T] 的自动转换,可以总结为: + +- **T -> Stream[T]: 将完整的 T 装箱为单帧的 Stream[T]。非流式变假流式。** +- **Stream[T] -> T: 将 Stream[T] Concat 为完整的 T。当 Stream[T] 不是单帧流时,可能需要提供针对 T 的 Concat 方法。** + +看了上面的实现原理,可能会有疑问,为什么对 graph 的 Invoke,会要求所有内部组件都以 Invoke 调用?以及为什么对 graph 的 Stream/Collect/Transform,会要求所有内部组件都以 Transform 调用?毕竟,可以举出一些反例: + +- A, B 两个组件编排为一个 Chain,以 Invoke 调用。其中 A 的业务接口实现了 Stream,B 的业务接口实现了 Collect。这时 graph 内部组件的调用范式有两个选择: + + - A 以 stream 调用,B 以 collect 调用,整体的 Chain 依然是 Invoke 语义,同时保留了真流式的内部语义。即 A 的输出流不需要做 Concat,可以实时的输入到 B 中。 + - 目前 Eino 的实现,A、B 都以 Invoke 调用,需要把 A 的输出流 Concat,并把 B 的输入做成假流式。失去了真流式的内部语义。 +- A,B 两个组件编排为一个 Chain,以 Collect 调用。其中 A 实现了 Transform 和 Collect,B 实现了 Invoke。两个选择: + + - A 以 Collect 调用,B 以 Invoke 调用:整体还是 Collect 的语义,不需要框架做任何的自动转化和装箱操作。 + - 目前 Eino 的实现,A、B 都以 Transform 调用,由于 A 的业务接口里实现了 Transform,因此 A 的输出和 B 的输入都可能是真流式,而 B 的业务接口里只实现了 Invoke,根据上面的分析,B 的入参会需要由真流式 concat 成非流式。这时就需要用户额外提供 B 的入参的 cancat 函数,这本可以避免。 + +上面两个例子,都可以找到一个明确的、与 Eino 的约定不同的,但却更优的流式调用路径。但是,当泛化到任意的编排场景时,很难找到一个明确定义的、与 Eino 的约定不同的、却总是更优的普适的规则。比如,A->B->C,以 Collect 语义调用,是 A->B 的时候 Collect,还是 B->C 的时候 Collect?潜在的因素有 A、B、C 具体实现的业务接口,可能还有“尽量多的使用真流式”的判断,也许还有哪个参数实现了 Concat,哪个没有实现。如果是更复杂的 Graph,需要考虑的因素会快速增加。在这种情况下,即使框架能定义出一套明确的、更优的普适规则,也很难解释清楚,理解和使用成本会很高,很可能已经超过了这个新规则实际带来的好处。 + +综上,我们可以说,Eino 编排产物内部各组件在运行时的范式,是 **By Design** 的,明确如下: + +- **整体以 Invoke 调用,内部各组件均以 Invoke 调用,不存在任何流式的过程。** +- **整体以 Stream/Collect/Transform 调用,内部各组件均以 Transform 调用,当出现 Stream[T] -> T 的 concat 过程时,可能需要额外提供 T 的 concat function。** + +## 真流变假流 + +Q. 用 stream 调用 state graph,为什么 chat model 输出了一个“假流”? + +## 是流,不是批 + +Q. 我的 lambda 输出了一个 stream,能否在图里面遍历流中元素,循环执行后续节点? diff --git a/content/zh/docs/eino/core_modules/components/_index.md b/content/zh/docs/eino/core_modules/components/_index.md new file mode 100644 index 0000000000..f21fbdcf0a --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/_index.md @@ -0,0 +1,73 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: Components 组件' +weight: 1 +--- + +大模型应用开发和传统应用开发最显著的区别在于大模型所具备的两大核心能力: + +- **基于语义的文本处理能力**:能够理解和生成人类语言,处理非结构化的内容语义关系 +- **智能决策能力**:能够基于上下文进行推理和判断,做出相应的行为决策 + +这两项核心能力催生了三种主要的应用模式: + +1. **直接对话模式**:处理用户输入并生成相应回答 +2. **知识处理模式**:对文本文档进行语义化处理、存储和检索 +3. **工具调用模式**:基于上下文做出决策并调用相应工具 + +这些模式高度概括了当前大模型应用的主要场景,为我们提供了抽象和标准化的基础。基于此,Eino 将这些常用能力抽象为可复用的「组件」(Components) + +组件抽象和这几种模式关系对应如下: + +**对话处理类组件:** + +1. 模板化处理和大模型交互参数的组件抽象: `ChatTemplate` + + > 详见 [Eino: ChatTemplate 使用说明](/zh/docs/eino/core_modules/components/chat_template_guide) + > +2. 直接和大模型交互的组件抽象: `ChatModel` + + > 详见 [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide) + > + +**文本语义处理类组件:** + +1. 获取和处理文本文档的组件抽象: `Document.Loader` 、`Document.Transformer` + + > 详见 [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide)、[Eino: Document Transformer 使用说明](/zh/docs/eino/core_modules/components/document_transformer_guide) + > +2. 文本文档语义化处理的组件抽象: `Embedding` + + > 详见 [Eino: Embedding 使用说明](/zh/docs/eino/core_modules/components/embedding_guide) + > +3. Embedding 之后将数据索引进行存储的组件抽象: `Indexer` + + > 详见 [Eino: Indexer & Retriever 使用说明](/zh/docs/eino/core_modules/components/indexer_and_retriever_guide) + > +4. 将语义相关文本文档进行索引和召回的组件抽象: `Retriever` + + > 详见 [Eino: Indexer & Retriever 使用说明](/zh/docs/eino/core_modules/components/indexer_and_retriever_guide) + > + +**决策执行类组件**: + +1. 大模型能够做决策并调用工具的组件抽象:`ToolsNode` + > 详见 [Eino: Tool & ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tool_and_tools_node_guide) + > + +**自定义组件:** + +1. 用户自定义代码逻辑的组件抽象:`Lambda` + > 详见 [Eino: Lambda 使用说明](/zh/docs/eino/core_modules/components/lambda_guide) + > + +组件是大模型应用能力的提供者,是大模型应用构建过程中的砖和瓦,组件抽象的优劣决定了大模型应用开发的复杂度,Eino 的组件抽象秉持着以下设计原则: + +1. **模块化和标准化**,将一系列功能相同的能力抽象成统一的模块,组件间职能明确、边界清晰,支持灵活地组合。 +2. **可扩展性**,接口的设计保持尽可能小的模块能力约束,让组件的开发者能方便地实现自定义组件的开发。 +3. **可复用性**,把最常用的能力和实现进行封装,提供给开发者开箱即用的工具使用。 + +组件的抽象可以让大模型应用开发形成比较固定的范式,降低认知复杂度,增强共同协作的效率。让组件的封装让开发者可以专注于业务逻辑的实现,避免重复造轮子,以快速构建高质量的大模型应用。 diff --git a/content/zh/docs/eino/core_modules/components/chat_model_guide.md b/content/zh/docs/eino/core_modules/components/chat_model_guide.md new file mode 100644 index 0000000000..3f40b72133 --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/chat_model_guide.md @@ -0,0 +1,432 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: ChatModel 使用说明' +weight: 0 +--- + +## **基本介绍** + +Model 组件是一个用于与大语言模型交互的组件。它的主要作用是将用户的输入消息发送给语言模型,并获取模型的响应。这个组件在以下场景中发挥重要作用: + +- 自然语言对话 +- 文本生成和补全 +- 工具调用的参数生成 +- 多模态交互(文本、图片、音频等) + +## **组件定义** + +### **接口定义** + +```go +type ChatModel interface { + Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error) + Stream(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.StreamReader[*schema.Message], error) + BindTools(tools []*schema.ToolInfo) error +} +``` + +#### **Generate 方法** + +- 功能:生成完整的模型响应 +- 参数: + + - ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager + - input:输入消息列表 + - opts:可选参数,用于配置模型行为 +- 返回值: + + - `*schema.Message`:模型生成的响应消息 + - error:生成过程中的错误信息 + +#### **Stream 方法** + +- 功能:以流式方式生成模型响应 +- 参数:与 Generate 方法相同 +- 返回值: +- _*schema.StreamReader[*schema.Message]__:模型响应的流式读取器_ +- error:生成过程中的错误信息 + +#### **BindTools 方法** + +- 功能:为模型绑定可用的工具 +- 参数: + + - tools:工具信息列表 +- 返回值: + + - error:绑定过程中的错误信息 + +### **Message 结构体 ** + +> ### **[TODO, 放到专门的结构体说明文档中]** + +```go +type Message struct { + // Role 表示消息的角色(system/user/assistant/tool) + Role RoleType + // Content 是消息的文本内容 + Content string + // MultiContent 是多模态内容,支持文本、图片、音频等 + MultiContent []ChatMessagePart + // Name 是消息的发送者名称 + Name string + // ToolCalls 是 assistant 消息中的工具调用信息 + ToolCalls []ToolCall + // ToolCallID 是 tool 消息的工具调用 ID + ToolCallID string + // ResponseMeta 包含响应的元信息 + ResponseMeta *ResponseMeta + // Extra 用于存储额外信息 + Extra map[string]any +} +``` + +Message 结构体是模型交互的基本结构,支持: + +- 多种角色:system(系统)、user(用户)、assistant(ai)、tool(工具) +- 多模态内容:文本、图片、音频、视频、文件 +- 工具调用:支持模型调用外部工具和函数 +- 元信息:包含响应原因、token 使用统计等 + +### **公共 Option** + +Model 组件提供了一组公共 Option 用于配置模型行为: + +```go +type Options struct { + // Temperature 控制输出的随机性 + Temperature *float32 + // MaxTokens 控制生成的最大 token 数量 + MaxTokens *int + // Model 指定使用的模型名称 + Model *string + // TopP 控制输出的多样性 + TopP *float32 + // Stop 指定停止生成的条件 + Stop []string +} +``` + +可以通过以下方式设置 Option: + +```go +// 设置温度 +WithTemperature(temperature float32) Option + +// 设置最大 token 数 +WithMaxTokens(maxTokens int) Option + +// 设置模型名称 +WithModel(name string) Option + +// 设置 top_p 值 +WithTopP(topP float32) Option + +// 设置停止词 +WithStop(stop []string) Option +``` + +## **使用方式** + +### **单独使用** + +```go +// 初始化模型 (以openai为例) +model, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{ + // 配置参数 +}) +if err != nil { + return err +} + +// 准备输入消息 +messages := []*schema.Message{ + { + Role: schema.System, + Content: "你是一个有帮助的助手。", + }, + { + Role: schema.User, + Content: "你好!", + }, +} + +// 生成响应 +response, err := model.Generate(ctx, messages, model.WithTemperature(0.8)) +if err != nil { + return err +} + +// 流式生成 +stream, err := model.Stream(ctx, messages) +if err != nil { + return err +} +defer stream.Close() + +for { + chunk, err := stream.Recv() + if err == io.EOF { + break + } + if err != nil { + return err + } + // 处理响应片段 + fmt.Print(chunk.Content) +} +``` + +### **在编排中使用** + +```go +// 在 Chain 中使用 +chain := compose.NewChain[[]*schema.Message, *schema.Message]() +chain.AppendChatModel(model) + +// 编译并运行 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, messages) + +// 在 Graph 中使用 +graph := compose.NewGraph[[]*schema.Message, *schema.Message]() +graph.AddChatModelNode("model_node", model) +``` + +## **Option 和 Callback 使用** + +### **Option 使用示例** + +```go +// 使用 Option +response, err := model.Generate(ctx, messages, + model.WithTemperature(0.7), + model.WithMaxTokens(2000), + model.WithModel("gpt-4"), +) +``` + +### **Callback 使用示例** + +```go +// 创建 callback handler +handler := &model.CallbackHandler{ + OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *model.CallbackInput) context.Context { + fmt.Printf("开始生成,输入消息数量: %d\n", len(input.Messages)) + return ctx + }, + OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *model.CallbackOutput) context.Context { + fmt.Printf("生成完成,Token 使用情况: %+v\n", output.TokenUsage) + return ctx + }, + OnEndWithStreamOutput: func(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[*model.CallbackOutput]) context.Context { + fmt.Println("开始接收流式输出") + return ctx + }, +} + +// 使用 callback handler +helper := template.NewHandlerHelper(). + ChatModel(handler). + Handler() + +// 在运行时使用 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, messages, compose.WithCallbacks(helper)) +``` + +## **已有实现** + +1. OpenAI ChatModel: 使用 OpenAI 的 GPT 系列模型 [ChatModel - OpenAI](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_openai) +2. Ollama ChatModel: 使用 Ollama 本地模型 [ChatModel - Ollama](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ollama) +3. ARK ChatModel: 使用 ARK 平台的模型服务 [ChatModel - ARK](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ark) + +## **自行实现参考** + +实现自定义的 ChatModel 组件时,需要注意以下几点: + +1. 注意要实现公共的 option +2. 注意实现 callback 机制 +3. 在流式输出时记得完成输出后要 close writer + +### **Option 机制** + +自定义 ChatModel 需要实现自己的 Option 机制: + +```go +// 定义 Option 结构体 +type MyChatModelOptions struct { + Options *model.Options + RetryCount int + Timeout time.Duration +} + +// 定义 Option 函数 +func WithRetryCount(count int) model.Option { + return model.WrapModelImplSpecificOptFn(func(o *MyChatModelOptions) { + o.RetryCount = count + }) +} + +func WithTimeout(timeout time.Duration) model.Option { + return model.WrapModelImplSpecificOptFn(func(o *MyChatModelOptions) { + o.Timeout = timeout + }) +} +``` + +### **Callback 处理** + +ChatModel 实现需要在适当的时机触发回调,以下结构由 ChatModel 组件定义: + +```go +// 定义回调输入输出 +type CallbackInput struct { + Messages []*schema.Message + Model string + Temperature *float32 + MaxTokens *int + Extra map[string]any +} + +type CallbackOutput struct { + Message *schema.Message + TokenUsage *schema.TokenUsage + Extra map[string]any +} +``` + +### **完整实现示例** + +```go +type MyChatModel struct { + client *http.Client + apiKey string + baseURL string + model string + timeout time.Duration + retryCount int +} + +func NewMyChatModel(config *MyChatModelConfig) (*MyChatModel, error) { + if config.APIKey == "" { + return nil, errors.New("api key is required") + } + + return &MyChatModel{ + client: &http.Client{}, + apiKey: config.APIKey, + baseURL: config.BaseURL, + model: config.DefaultModel, + timeout: config.DefaultTimeout, + retryCount: config.DefaultRetryCount, + }, nil +} + +func (m *MyChatModel) Generate(ctx context.Context, messages []*schema.Message, opts ...model.Option) (*schema.Message, error) { + // 1. 处理选项 + options := &MyChatModelOptions{ + Options: &model.Options{ + Model: &m.model, + }, + RetryCount: m.retryCount, + Timeout: m.timeout, + } + options.Options = model.GetCommonOptions(options.Options, opts...) + options = model.GetImplSpecificOptions(options, opts...) + + // 2. 获取 callback manager + cm := callbacks.ManagerFromContext(ctx) + + // 3. 开始生成前的回调 + ctx = cm.OnStart(ctx, info, &model.CallbackInput{ + Messages: messages, + Model: *options.Options.Model, + Temperature: options.Options.Temperature, + MaxTokens: options.Options.MaxTokens, + }) + + // 4. 执行生成逻辑 + response, err := m.doGenerate(ctx, messages, options) + + // 5. 处理错误和完成回调 + if err != nil { + ctx = cm.OnError(ctx, info, err) + return nil, err + } + + ctx = cm.OnEnd(ctx, info, &model.CallbackOutput{ + Message: response.Message, + TokenUsage: response.TokenUsage, + }) + + return response.Message, nil +} + +func (m *MyChatModel) Stream(ctx context.Context, messages []*schema.Message, opts ...model.Option) (*schema.StreamReader[*schema.Message], error) { + // 1. 处理选项 + options := &MyChatModelOptions{ + Options: &model.Options{ + Model: &m.model, + }, + RetryCount: m.retryCount, + Timeout: m.timeout, + } + options = model.GetCommonOptions(options, opts...) + + // 2. 获取 callback manager + cm := callbacks.ManagerFromContext(ctx) + + // 3. 开始流式生成前的回调 + ctx = cm.OnStart(ctx, info, &model.CallbackInput{ + Messages: messages, + Model: *options.Options.Model, + Temperature: options.Options.Temperature, + MaxTokens: options.Options.MaxTokens, + }) + + // 4. 创建流式响应 + reader, writer := schema.Pipe[*schema.Message](1) + + // 5. 启动异步生成 + go func() { + defer writer.Close() + + // 执行流式生成 + err := m.doStream(ctx, messages, options, writer) + if err != nil { + cm.OnError(ctx, info, err) + return + } + + // 完成回调 + cm.OnEndWithStreamOutput(ctx, info, stream) + }() + + return reader, nil +} + +func (m *MyChatModel) BindTools(tools []*schema.ToolInfo) error { + // 实现工具绑定逻辑 + return nil +} + +func (m *MyChatModel) doGenerate(ctx context.Context, messages []*schema.Message, opts *MyChatModelOptions) (*model.Response, error) { + // 实现生成逻辑 + return response, err +} + +func (m *MyChatModel) doStream(ctx context.Context, messages []*schema.Message, opts *MyChatModelOptions, writer *schema.StreamWriter[*schema.Message]) error { + // 实现流式生成逻辑 + return err +} +``` diff --git a/content/zh/docs/eino/core_modules/components/chat_template_guide.md b/content/zh/docs/eino/core_modules/components/chat_template_guide.md new file mode 100644 index 0000000000..6cb0a50c7b --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/chat_template_guide.md @@ -0,0 +1,249 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: ChatTemplate 使用说明' +weight: 0 +--- + +## **基本介绍** + +Prompt 组件是一个用于处理和格式化提示模板的组件。它的主要作用是将用户提供的变量值填充到预定义的消息模板中,生成用于与语言模型交互的标准消息格式。这个组件可用于以下场景: + +- 构建结构化的系统提示 +- 处理多轮对话的模板 (包括 history) +- 实现可复用的提示模式 + +## **组件定义** + +### **接口定义** + +```go +type ChatTemplate interface { + Format(ctx context.Context, vs map[string]any, opts ...Option) ([]*schema.Message, error) +} +``` + +#### **Format 方法** + +- 功能:将变量值填充到消息模板中 +- 参数: + + - ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager + - vs:变量值映射,用于填充模板中的占位符 + - opts:可选参数,用于配置格式化行为 +- 返回值: + + - `[]*schema.Message`:格式化后的消息列表 + - error:格式化过程中的错误信息 + +### **内置模板化方式** + +Prompt 组件内置支持三种模板化方式: + +1. String 格式 (schema.FString) + + - 使用 `{variable}` 语法进行变量替换 + - 简单直观,适合基础文本替换场景 + - 示例:`"你是一个{role},请帮我{task}。"` +2. Template 格式 (schema.FTemplate) + + - 使用 Go 标准库的 text/template 语法 + - 支持条件判断、循环等复杂逻辑 + - 示例:`"{{if .expert}}作为专家{{end}}请{{.action}}"` +3. Jinja2 格式 (schema.FJinja2) + + - 使用 Jinja2 模板语法 + - 支持丰富的过滤器和控制结构 + - 示例:`"{% if level == 'expert' %}以专家的角度{% endif %}分析{{topic}}"` + +### **公共 Option** + +Prompt 组件使用 Option 来定义可选参数, ChatTemplate 没有公共的 option 抽象。每个具体的实现可以定义自己的特定 Option,通过 WrapImplSpecificOptFn 函数包装成统一的 Option 类型。 + +## **使用方式** + +ChatTemplate 一般用于 ChatModel 之前做上下文准备的。 + +### **单独使用** + +```go +// 创建模板 +template := prompt.FromMessages(schema.FString, + &schema.Message{ + Role: schema.System, + Content: "你是一个{role}。", + }, + &schema.Message{ + Role: schema.User, + Content: "请帮我{task}。", + }, +) + +// 准备变量 +variables := map[string]any{ + "role": "专业的助手", + "task": "写一首诗", +} + +// 格式化模板 +messages, err := template.Format(ctx, variables) +if err != nil { + return err +} +``` + +### **在编排中使用** + +```go +// 在 Chain 中使用 +chain := compose.NewChain[map[string]any, []*schema.Message]() +chain.AppendChatTemplate(template) + +// 编译并运行 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, variables) + +// 在 Graph 中使用 +graph := compose.NewGraph[map[string]any, []*schema.Message]() +graph.AddChatTemplateNode("template_node", template) +``` + +## **Option 和 Callback 使用** + +### **Callback 使用示例** + +```go +// 创建 callback handler +handler := &prompt.CallbackHandler{ + OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *prompt.CallbackInput) context.Context { + fmt.Printf("开始格式化模板,变量: %v\n", input.Variables) + return ctx + }, + OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *prompt.CallbackOutput) context.Context { + fmt.Printf("模板格式化完成,生成消息数量: %d\n", len(output.Result)) + return ctx + }, +} + +// 使用 callback handler +helper := template.NewHandlerHelper(). + Prompt(handler). + Handler() + +// 在运行时使用 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, variables, compose.WithCallbacks(helper)) +``` + +## **自行实现参考** + +### Option **机制** + +若有需要,组件实现者可实现自定义 prompt option: + +```go +// 定义 Option 结构体 +type MyPromptOptions struct { + StrictMode bool + DefaultValues map[string]string +} + +// 定义 Option 函数 +func WithStrictMode(strict bool) prompt.Option { + return prompt.WrapImplSpecificOptFn(func(o *MyPromptOptions) { + o.StrictMode = strict + }) +} + +func WithDefaultValues(values map[string]string) prompt.Option { + return prompt.WrapImplSpecificOptFn(func(o *MyPromptOptions) { + o.DefaultValues = values + }) +} +``` + +### **Callback 处理** + +Prompt 实现需要在适当的时机触发回调,以下结构是组件定义好的: + +```go +// 定义回调输入输出 +type CallbackInput struct { + Variables map[string]any + Templates []schema.MessagesTemplate + Extra map[string]any +} + +type CallbackOutput struct { + Result []*schema.Message + Templates []schema.MessagesTemplate + Extra map[string]any +} +``` + +### **完整实现示例** + +```go +type MyPrompt struct { + templates []schema.MessagesTemplate + formatType schema.FormatType + strictMode bool + defaultValues map[string]string +} + +func NewMyPrompt(config *MyPromptConfig) (*MyPrompt, error) { + return &MyPrompt{ + templates: config.Templates, + formatType: config.FormatType, + strictMode: config.DefaultStrictMode, + defaultValues: config.DefaultValues, + }, nil +} + +func (p *MyPrompt) Format(ctx context.Context, vs map[string]any, opts ...prompt.Option) ([]*schema.Message, error) { + // 1. 处理 Option + options := &MyPromptOptions{ + StrictMode: p.strictMode, + DefaultValues: p.defaultValues, + } + options = prompt.GetImplSpecificOptions(options, opts...) + + // 2. 获取 callback manager + cm := callbacks.ManagerFromContext(ctx) + + // 3. 开始格式化前的回调 + ctx = cm.OnStart(ctx, info, &prompt.CallbackInput{ + Variables: vs, + Templates: p.templates, + }) + + // 4. 执行格式化逻辑 + messages, err := p.doFormat(ctx, vs, options) + + // 5. 处理错误和完成回调 + if err != nil { + ctx = cm.OnError(ctx, info, err) + return nil, err + } + + ctx = cm.OnEnd(ctx, info, &prompt.CallbackOutput{ + Result: messages, + Templates: p.templates, + }) + + return messages, nil +} + +func (p *MyPrompt) doFormat(ctx context.Context, vs map[string]any, opts *MyPromptOptions) ([]*schema.Message, error) { + // 实现自己定义逻辑 + return messages, nil +} +``` diff --git a/content/zh/docs/eino/core_modules/components/document_loader_guide/_index.md b/content/zh/docs/eino/core_modules/components/document_loader_guide/_index.md new file mode 100644 index 0000000000..1c13881917 --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/document_loader_guide/_index.md @@ -0,0 +1,272 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: Document Loader 使用说明' +weight: 6 +--- + +## **基本介绍** + +Document Loader 是一个用于加载文档的组件。它的主要作用是从不同来源(如网络 URL、本地文件等)加载文档内容,并将其转换为标准的文档格式。这个组件在处理需要从各种来源获取文档内容的场景中发挥重要作用,比如: + +- 从网络 URL 加载网页内容 +- 读取本地 PDF、Word 等格式的文档 + +## **组件定义** + +### **接口定义** + +```go +type Loader interface { + Load(ctx context.Context, src Source, opts ...LoaderOption) ([]*schema.Document, error) +} +``` + +#### **Load 方法** + +- 功能:从指定的数据源加载文档 +- 参数: + + - ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager + - src:文档来源,包含文档的 URI 信息 + - opts:加载选项,用于配置加载行为 +- 返回值: + + - `[]*schema.Document`:加载的文档列表 + - error:加载过程中的错误信息 + +### **Source 结构体** + +```go +type Source struct { + URI string +} +``` + +Source 结构体定义了文档的来源信息: + +- URI:文档的统一资源标识符,可以是网络 URL 或本地文件路径 + +### **Document 结构体** + +```go +type Document struct { + // ID 是文档的唯一标识符 + ID string + // Content 是文档的内容 + Content string + // MetaData 用于存储文档的元数据信息 + MetaData map[string]any +} +``` + +Document 结构体是文档的标准格式,包含以下重要字段: + +- ID:文档的唯一标识符,用于在系统中唯一标识一个文档 +- Content:文档的实际内容 +- MetaData:文档的元数据,可以存储如下信息: + + - 文档的来源信息 + - 文档的向量表示(用于向量检索) + - 文档的分数(用于排序) + - 文档的子索引(用于分层检索) + - 其他自定义元数据 + +### **公共选项** + +Loader 组件使用 `LoaderOption` 来定义加载选项。Loader 目前没有公共的 Option,每个具体的实现可以定义自己的特定选项,通过 `WrapLoaderImplSpecificOptFn` 函数包装成统一的 `LoaderOption` 类型。 + +## **使用方式** + +### **单独使用** + +```go +// 初始化 loader (以file loader为例) +loader, err := file.NewLoader(ctx, &file.LoaderConfig{ + // 配置参数 +}) +if err != nil { + return err +} + +// 加载文档 +docs, err := loader.Load(ctx, document.Source{ + URI: "https://example.com/doc.pdf", +}) +if err != nil { + return err +} +``` + +### **在编排中使用** + +```go +// 在 Chain 中使用 +chain := compose.NewChain[string, []*schema.Document]() +chain.AppendLoader(loader) + +// 编译并运行 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, input) + +// 在 Graph 中使用 +graph := compose.NewGraph[string, []*schema.Document]() +graph.AddLoaderNode("loader_node", loader) +``` + +## **Option 和 Callback 使用** + +### **Callback 使用示例** + +```go +// 创建 callback handler +handler := &document.LoaderCallbackHandler{ + OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *document.LoaderCallbackInput) context.Context { + fmt.Printf("开始加载文档: %s\n", input.Source.URI) + return ctx + }, + OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *document.LoaderCallbackOutput) context.Context { + fmt.Printf("文档加载完成,共加载 %d 个文档\n", len(output.Docs)) + return ctx + }, + // OnError +} + +// 使用 callback handler +helper := template.NewHandlerHelper(). + Loader(handler). + Handler() + +// 在运行时使用 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, input, compose.WithCallbacks(helper)) +``` + +## **已有实现** + +1. File Loader: 用于加载本地文件系统中的文档 [Loader - local file](/zh/docs/eino/ecosystem_integration/document/loader_local_file) +2. Web Loader: 用于加载网络 URL 指向的文档 [Loader - web url](/zh/docs/eino/ecosystem_integration/document/loader_web_url) +3. S3 Loader: 用于加载存储在 S3 兼容存储系统中的文档 [Loader - amazon s3](/zh/docs/eino/ecosystem_integration/document/loader_amazon_s3) + +## **自行实现参考** + +自行实现 loader 组件时,需要注意 option 机制和 callback 的处理。 + +### option **机制** + +自定义 Loader 需要实现自己的 Option 参数机制: + +```go +// 定义选项结构体 +type MyLoaderOptions struct { + Timeout time.Duration + RetryCount int +} + +// 定义选项函数 +func WithTimeout(timeout time.Duration) document.LoaderOption { + return document.WrapLoaderImplSpecificOptFn(func(o *MyLoaderOptions) { + o.Timeout = timeout + }) +} + +func WithRetryCount(count int) document.LoaderOption { + return document.WrapLoaderImplSpecificOptFn(func(o *MyLoaderOptions) { + o.RetryCount = count + }) +} +``` + +### **Callback 处理** + +Loader 实现需要在适当的时机触发回调: + +```go +// 这是由loader组件定义的回调输入输出, 在实现时需要满足参数的含义 +type LoaderCallbackInput struct { + Source Source + Extra map[string]any +} + +type LoaderCallbackOutput struct { + Source Source + Docs []*schema.Document + Extra map[string]any +} +``` + +### **完整实现示例** + +```go +type MyLoader struct { + timeout time.Duration + retryCount int +} + +func NewMyLoader(config *MyLoaderConfig) (*MyLoader, error) { + return &MyLoader{ + timeout: config.DefaultTimeout, + retryCount: config.DefaultRetryCount, + }, nil +} + +func (l *MyLoader) Load(ctx context.Context, src document.Source, opts ...document.LoaderOption) ([]*schema.Document, error) { + // 1. 处理 option + options := &MyLoaderOptions{ + Timeout: l.timeout, + RetryCount: l.retryCount, + } + options = document.GetLoaderImplSpecificOptions(options, opts...) + + // 2. 获取 callback manager + cm := callbacks.ManagerFromContext(ctx) + + // 3. 开始加载前的回调 + ctx = cm.OnStart(ctx, info, &document.LoaderCallbackInput{ + Source: src, + }) + + // 4. 执行加载逻辑 + docs, err := l.doLoad(ctx, src, options) + + // 5. 处理错误和完成回调 + if err != nil { + ctx = cm.OnError(ctx, info, err) + return nil, err + } + + ctx = cm.OnEnd(ctx, info, &document.LoaderCallbackOutput{ + Source: src, + Docs: docs, + }) + + return docs, nil +} + +func (l *MyLoader) doLoad(ctx context.Context, src document.Source, opts *MyLoaderOptions) ([]*schema.Document, error) { + // 实现文档加载逻辑 + // 1. 加载文档内容 + // 2. 构造 Document 对象,注意可在 MetaData 中保存文档来源等重要信息 + return docs, nil +} +``` + +### **注意事项** + +- MetaData 是文档的重要组成部分,用于保存文档的各种元信息 +- 文档加载失败时返回有意义的错误信息,便于做错误的排查 + +## 其他参考文档 + +- [[🚧]Eino: Document Transformer 使用说明](/zh/docs/eino/core_modules/components/document_transformer_guide) +- [[🚧]Eino: Embedding 使用说明](/zh/docs/eino/core_modules/components/embedding_guide) +- [[🚧]Eino: Indexer 使用说明](/zh/docs/eino/core_modules/components/indexer_guide) +- [[🚧]Eino: Retriever 使用说明](/zh/docs/eino/core_modules/components/retriever_guide) diff --git a/content/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide.md b/content/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide.md new file mode 100644 index 0000000000..47948d4513 --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide.md @@ -0,0 +1,212 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: Document Parser 接口使用说明' +weight: 1 +--- + +## **基本介绍** + +Document Parser 是一个用于解析文档内容的工具包。它不是一个独立的组件,而是作为 Document Loader 的内部工具,用于将不同格式的原始内容解析成标准的文档格式。Parser 支持: + +- 解析不同格式的文档内容(如文本、PDF、Markdown 等) +- 根据文件扩展名自动选择合适的解析器 (eg:ExtParser) +- 为解析后的文档添加元数据信息 + +## **接口定义** + +### **Parser 接口** + +```go +type Parser interface { + Parse(ctx context.Context, reader io.Reader, opts ...Option) ([]*schema.Document, error) +} +``` + +#### **Parse 方法** + +- 功能:从 Reader 中解析文档内容 +- 参数: + + - ctx:上下文对象 + - reader:提供原始内容的 Reader + - opts:解析选项 +- 返回值: + + - `[]*schema.Document`:解析后的文档列表 + - error:解析过程中的错误 + +### **公共 Option 定义** + +```go +type Options struct { + // URI 表示文档的来源 + URI string + + // ExtraMeta 会被合并到每个解析出的文档的元数据中 + ExtraMeta map[string]any +} +``` + +提供了两个基础的选项函数: + +- WithURI:设置文档的 URI,在 ExtParser 中用于选择解析器 +- WithExtraMeta:设置额外的元数据 + +## **内置解析器** + +### **TextParser** + +最基础的文本解析器,将输入内容直接作为文档内容: + +```go +// 使用示例 +docs, err := TextParser{}.Parse(ctx, strings.NewReader("hello world")) +if err != nil { + return err +} +fmt.Println(docs[0].Content) // 输出: hello world +``` + +### **ExtParser** + +基于文件扩展名的解析器,可以根据文件扩展名自动选择合适的解析器: + +```go +// 创建扩展解析器 +parser, err := NewExtParser(ctx, &ExtParserConfig{ + // 注册特定扩展名的解析器 + Parsers: map[string]Parser{ + ".html": html.NewParser(&html.ParserConfig{ + // HTML 解析器的配置 + RemoveScript: true, // 移除脚本标签 + RemoveStyle: true, // 移除样式标签 + }), + ".pdf": pdf.NewParser(&pdf.ParserConfig{ + // PDF 解析器的配置 + ExtractImages: false, // 不提取图片 + }), + }, + // 设置默认解析器,用于处理未知格式 + FallbackParser: TextParser{}, +}) +if err != nil { + return err +} + +// 使用解析器 +file, _ := os.Open("./document.html") +docs, err := parser.Parse(ctx, file, + WithURI("./document.html"), // 必须提供 URI 以便选��正确的解析器 + WithExtraMeta(map[string]any{ + "source": "local", + }), +) +``` + +### 其他实现 + +- pdf parser, 用于提取和 parse pdf 格式的文件: [[🚧]Parser - pdf](/zh/docs/eino/ecosystem_integration/document/parser_pdf) +- html parser, 用于提取和 parse html 格式的内容: [[🚧]Parser - html](/zh/docs/eino/ecosystem_integration/document/parser_html) + +## **在 Document Loader 中使用** + +Parser 主要在 Document Loader 中使用,用于解析加载的文档内容。以下是一些典型的使用场景: + +### **文件加载器** + +```go +// 使用 FileLoader 加载本地文件 +ctx := context.Background() + +// 创建文件加载器,使用文本解析器 +loader, err := file.NewFileLoader(ctx, &file.FileLoaderConfig{ + UseNameAsID: true, // 使用文件名作为文档ID + Parser: parser.TextParser{}, // 使用文本解析器 +}) +if err != nil { + return err +} + +// 加载文件 +docs, err := loader.Load(ctx, document.Source{ + URI: "./document.txt", +}) +if err != nil { + return err +} + +// 处理加载的文档 +for _, doc := range docs { + fmt.Printf("Document ID: %s\n", doc.ID) // 输出: Document ID: document.txt + fmt.Printf("Content: %s\n", doc.Content) + fmt.Printf("Extension: %s\n", doc.MetaData[file.MetaKeyExtension]) // 输出: Extension: .txt + fmt.Printf("Source: %s\n", doc.MetaData[file.MetaKeySource]) // 输出: Source: ./document.txt +} +``` + +## **自定义解析器实现** + +### option **机制** + +自定义解析器可以定义自己的 option: + +```go +// 定义选项结构体 +type MyParserOptions struct { + Encoding string + MaxSize int64 +} + +// 定义选项函数 +func WithEncoding(encoding string) parser.Option { + return parser.WrapImplSpecificOptFn(func(o *MyParserOptions) { + o.Encoding = encoding + }) +} + +func WithMaxSize(size int64) parser.Option { + return parser.WrapImplSpecificOptFn(func(o *MyParserOptions) { + o.MaxSize = size + }) +} +``` + +### **完整实现示例** + +```go +type MyParser struct { + defaultEncoding string + defaultMaxSize int64 +} + +func NewMyParser(config *MyParserConfig) (*MyParser, error) { + return &MyParser{ + defaultEncoding: config.DefaultEncoding, + defaultMaxSize: config.DefaultMaxSize, + }, nil +} + +func (p *MyParser) Parse(ctx context.Context, reader io.Reader, opts ...parser.Option) ([]*schema.Document, error) { + // 1. 处理通用选项 + commonOpts := parser.GetCommonOptions(&parser.Options{}, opts...) + + // 2. 处理特定选项 + myOpts := &MyParserOptions{ + Encoding: p.defaultEncoding, + MaxSize: p.defaultMaxSize, + } + myOpts = parser.GetImplSpecificOptions(myOpts, opts...) + + // 3. 实现解析逻辑 + + return docs, nil +} +``` + +### **注意事项** + +1. 注意对公共 option 抽象的处理 +2. 注意 metadata 的设置和传递 diff --git a/content/zh/docs/eino/core_modules/components/document_transformer_guide.md b/content/zh/docs/eino/core_modules/components/document_transformer_guide.md new file mode 100644 index 0000000000..27f577b4cc --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/document_transformer_guide.md @@ -0,0 +1,263 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: Document Transformer 使用说明' +weight: 8 +--- + +## **基本介绍** + +Document Transformer 是一个用于文档转换和处理的组件。它的主要作用是对输入的文档进行各种转换操作,如分割、过滤、合并等,从而得到满足特定需求的文档。这个组件可用于以下场景中: + +- 将长文档分割成小段落以便于处理 +- 根据特定规则过滤文档内容 +- 对文档内容进行结构化转换 +- 提取文档中的特定部分 + +## **组件定义** + +### **接口定义** + +```go +type Transformer interface { + Transform(ctx context.Context, src []*schema.Document, opts ...TransformerOption) ([]*schema.Document, error) +} +``` + +#### **Transform 方法** + +- 功能:对输入的文档进行转换处理 +- 参数: + + - ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager + - src:待处理的文档列表 + - opts:可选参数,用于配置转换行为 +- 返回值: + + - `[]*schema.Document`:转换后的文档列表 + - error:转换过程中的错误信息 + +### **Document 结构体** + +【TODO】 可把所有的公共结构体放到一个特定的地方 + +```go +type Document struct { + // ID 是文档的唯一标识符 + ID string + // Content 是文档的内容 + Content string + // MetaData 用于存储文档的元数据信息 + MetaData map[string]any +} +``` + +Document 结构体是文档的标准格式,包含以下重要字段: + +- ID:文档的唯一标识符,用于在系统中唯一标识一个文档 +- Content:文档的实际内容 +- MetaData:文档的元数据,可以存储如下信息: + + - 文档的来源信息 + - 文档的向量表示(用于向量检索) + - 文档的分数(用于排序) + - 文档的子索引(用于分层检索) + - 其他自定义元数据 + +### **公共 Option** + +Transformer 组件使用 TransformerOption 来定义可选参数,目前没有公共的 option。每个具体的实现可以定义自己的特定 Option,通过 WrapTransformerImplSpecificOptFn 函数包装成统一的 TransformerOption 类型。 + +## **使用方式** + +### **单独使用** + +```go +// 初始化 transformer (以 markdown 为例) +transformer, err := markdown.NewHeaderSplitter(ctx, &markdown.HeaderSplitterConfig{ + // 配置参数 +}) +if err != nil { + return err +} + +// 转换文档 +transformedDocs, err := transformer.Transform(ctx, docs) +if err != nil { + return err +} +``` + +### **在编排中使用** + +```go +// 在 Chain 中使用 +chain := compose.NewChain[[]*schema.Document, []*schema.Document]() +chain.AppendDocumentTransformer(transformer) + +// 编译并运行 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, input) + +// 在 Graph 中使用 +graph := compose.NewGraph[[]*schema.Document, []*schema.Document]() +graph.AddDocumentTransformerNode("transformer_node", transformer) +``` + +## **Option 和 Callback 使用** + +### **Callback 使用示例** + +```go +// 创建 callback handler +handler := &document.TransformerCallbackHandler{ + OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *document.TransformerCallbackInput) context.Context { + fmt.Printf("开始转换文档,输入文档数量: %d\n", len(input.Input)) + return ctx + }, + OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *document.TransformerCallbackOutput) context.Context { + fmt.Printf("文档转换完成,输出文档数量: %d\n", len(output.Output)) + return ctx + }, +} + +// 使用 callback handler +helper := template.NewHandlerHelper(). + Transformer(handler). + Handler() + +// 在运行时使用 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, input, compose.WithCallbacks(helper)) +``` + +## **已有实现** + +1. Markdown Header Splitter: 基于 Markdown 标题进行文档分割 [Splitter - markdown](/zh/docs/eino/ecosystem_integration/document/splitter_markdown) +2. Text Splitter: 基于文本长度或分隔符进行文档分割 [Splitter - semantic](/zh/docs/eino/ecosystem_integration/document/splitter_semantic) +3. Document Filter: 基于规则过滤文档内容 [Splitter - recursive](/zh/docs/eino/ecosystem_integration/document/splitter_recursive) + +## **自行实现参考** + +实现自定义的 Transformer 组件时,需要注意以下几点: + +1. option 的处理 +2. callback 的处理 + +### **Option 机制** + +自定义 Transformer 需要实现自己的 Option 机制: + +```go +// 定义 Option 结构体 +type MyTransformerOptions struct { + ChunkSize int + Overlap int + MinChunkLength int +} + +// 定义 Option 函数 +func WithChunkSize(size int) document.TransformerOption { + return document.WrapTransformerImplSpecificOptFn(func(o *MyTransformerOptions) { + o.ChunkSize = size + }) +} + +func WithOverlap(overlap int) document.TransformerOption { + return document.WrapTransformerImplSpecificOptFn(func(o *MyTransformerOptions) { + o.Overlap = overlap + }) +} +``` + +### **Callback 处理** + +Transformer 实现需要在适当的时机触发回调: + +```go +// 这是由 transformer 定义的回调输入输出,自行组件在实现时需要满足结构的含义 +type TransformerCallbackInput struct { + Input []*schema.Document + Extra map[string]any +} + +type TransformerCallbackOutput struct { + Output []*schema.Document + Extra map[string]any +} +``` + +### **完整实现示例** + +```go +type MyTransformer struct { + chunkSize int + overlap int + minChunkLength int +} + +func NewMyTransformer(config *MyTransformerConfig) (*MyTransformer, error) { + return &MyTransformer{ + chunkSize: config.DefaultChunkSize, + overlap: config.DefaultOverlap, + minChunkLength: config.DefaultMinChunkLength, + }, nil +} + +func (t *MyTransformer) Transform(ctx context.Context, src []*schema.Document, opts ...document.TransformerOption) ([]*schema.Document, error) { + // 1. 处理 Option + options := &MyTransformerOptions{ + ChunkSize: t.chunkSize, + Overlap: t.overlap, + MinChunkLength: t.minChunkLength, + } + options = document.GetTransformerImplSpecificOptions(options, opts...) + + // 2. 获取 callback manager + cm := callbacks.ManagerFromContext(ctx) + + // 3. 开始转换前的回调 + ctx = cm.OnStart(ctx, info, &document.TransformerCallbackInput{ + Input: src, + }) + + // 4. 执行转换逻辑 + docs, err := t.doTransform(ctx, src, options) + + // 5. 处理错误和完成回调 + if err != nil { + ctx = cm.OnError(ctx, info, err) + return nil, err + } + + ctx = cm.OnEnd(ctx, info, &document.TransformerCallbackOutput{ + Output: docs, + }) + + return docs, nil +} + +func (t *MyTransformer) doTransform(ctx context.Context, src []*schema.Document, opts *MyTransformerOptions) ([]*schema.Document, error) { + // 实现文档转换逻辑 + return docs, nil +} +``` + +### **注意事项** + +- 转换后的文档需要注意对 metadata 的处理,注意保留原 metadata,以及新增自定义的 metadata + +## 其他参考文档 + +- [[🚧]Eino: Embedding 使用说明](/zh/docs/eino/core_modules/components/embedding_guide) +- [[🚧]Eino: Indexer 使用说明](/zh/docs/eino/core_modules/components/indexer_guide) +- [[🚧]Eino: Retriever 使用说明](/zh/docs/eino/core_modules/components/retriever_guide) +- [[🚧]Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide) diff --git a/content/zh/docs/eino/core_modules/components/embedding_guide.md b/content/zh/docs/eino/core_modules/components/embedding_guide.md new file mode 100644 index 0000000000..3578fbf09e --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/embedding_guide.md @@ -0,0 +1,276 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: Embedding 使用说明' +weight: 7 +--- + +## **基本介绍** + +Embedding 组件是一个用于将文本转换为向量表示的组件。它的主要作用是将文本内容映射到高维向量空间,使得语义相似的文本在向量空间中的距离较近。这个组件在以下场景中发挥重要作用: + +- 文本相似度计算 +- 语义搜索 +- 文本聚类分析 + +## **组件定义** + +### **接口定义** + +```go +type Embedder interface { + EmbedStrings(ctx context.Context, texts []string, opts ...Option) ([][]float64, error) +} +``` + +#### **EmbedStrings 方法** + +- 功能:将一组文本转换为向量表示 +- 参数: + + - ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager + - texts:待转换的文本列表 + - opts:转换选项,用于配置转换行为 +- 返回值: + + - `[][]float64`:文本对应的向量表示列表,每个向量的维度由具体的实现决定 + - error:转换过程中的错误信息 + +### **公共 Option** + +Embedding 组件使用 EmbeddingOption 来定义可选参数,下方是抽象出的公共 option。每个具体的实现可以定义自己的特定 Option,通过 WrapEmbeddingImplSpecificOptFn 函数包装成统一的 EmbeddingOption 类型。 + +```go +type Options struct { + // Model 是用于生成向量的模型名称 + Model *string +} +``` + +可以通过以下方式设置选项: + +```go +// 设置模型名称 +WithModel(model string) Option +``` + +## **使用方式** + +### **单独使用** + +```go +// 初始化 embedder (以 openai 为例) +embedder, err := openai.NewEmbedder(ctx, &openai.EmbedderConfig{ + // 配置参数 +}) +if err != nil { + return err +} + +// 生成向量 +texts := []string{"这是第一段文本", "这是第二段文本"} +vectors, err := embedder.EmbedStrings(ctx, texts) +if err != nil { + return err +} +``` + +### **在编排中使用** + +```go +// 在 Chain 中使用 +chain := compose.NewChain[[]string, [][]float64]() +chain.AppendEmbedding(embedder) + +// 编译并运行 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, texts) + +// 在 Graph 中使用 +graph := compose.NewGraph[[]string, [][]float64]() +graph.AddEmbeddingNode("embedding_node", embedder) +``` + +## **Option 和 Callback 使用** + +### **Option 使用示例** + +```go +// 使用选项 (以独立使用组件为例) +vectors, err := embedder.EmbedStrings(ctx, texts, + embedding.WithModel("text-embedding-3-small"), +) +``` + +### **Callback 使用示例** + +```go +// 创建 callback handler +handler := &embedding.CallbackHandler{ + OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *embedding.CallbackInput) context.Context { + fmt.Printf("开始生成向量,文本数量: %d\n", len(input.Texts)) + return ctx + }, + OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *embedding.CallbackOutput) context.Context { + fmt.Printf("向量生成完成,向量数量: %d\n", len(output.Embeddings)) + if output.TokenUsage != nil { + fmt.Printf("Token 使用情况: %+v\n", output.TokenUsage) + } + return ctx + }, +} + +// 使用 callback handler +helper := template.NewHandlerHelper(). + Embedding(handler). + Handler() + +// 在运行时使用 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, texts, compose.WithCallbacks(helper)) +``` + +## **已有实现** + +1. OpenAI Embedder: 使用 OpenAI 的文本嵌入模型生成向量 [Embedding - OpenAI](/zh/docs/eino/ecosystem_integration/embedding/embedding_openai) +2. ARK Embedder: 使用 ARK 平台的模型生成向量 [Embedding - ARK](/zh/docs/eino/ecosystem_integration/embedding/embedding_ark) + +## **自行实现参考** + +实现自定义的 Embedding 组件时,需要注意以下几点: + +1. 注意处理公共 option +2. 注意实现 callback 机制 + +### **Option 机制** + +自定义 Embedding 需要实现自己的 Option 机制: + +```go +// 定义 Option 结构体 +type MyEmbeddingOptions struct { + BatchSize int + MaxRetries int + Timeout time.Duration +} + +// 定义 Option 函数 +func WithBatchSize(size int) embedding.Option { + return embedding.WrapEmbeddingImplSpecificOptFn(func(o *MyEmbeddingOptions) { + o.BatchSize = size + }) +} +``` + +### **Callback 处理** + +Embedder 实现需要在适当的时机触发回调。框架已经定义了标准的回调输入输出结构体: + +```go +// CallbackInput 是 embedding 回调的输入 +type CallbackInput struct { + // Texts 是待转换的文本列表 + Texts []string + // Config 是生成向量的配置信息 + Config *Config + // Extra 是回调的额外信息 + Extra map[string]any +} + +// CallbackOutput 是 embedding 回调的输出 +type CallbackOutput struct { + // Embeddings 是生成的向量列表 + Embeddings [][]float64 + // Config 是生成向量的配置信息 + Config *Config + // TokenUsage 是 token 使用情况 + TokenUsage *TokenUsage + // Extra 是回调的额外信息 + Extra map[string]any +} + +// TokenUsage 是 token 使用情况 +type TokenUsage struct { + // PromptTokens 是提示词的 token 数量 + PromptTokens int + // CompletionTokens 是补全的 token 数量 + CompletionTokens int + // TotalTokens 是总的 token 数量 + TotalTokens int +} +``` + +### **完整实现示例** + +```go +type MyEmbedder struct { + model string + batchSize int +} + +func NewMyEmbedder(config *MyEmbedderConfig) (*MyEmbedder, error) { + return &MyEmbedder{ + model: config.DefaultModel, + batchSize: config.DefaultBatchSize, + }, nil +} + +func (e *MyEmbedder) EmbedStrings(ctx context.Context, texts []string, opts ...embedding.Option) ([][]float64, error) { + // 1. 处理选项 + options := &MyEmbeddingOptions{ + Options: &embedding.Options{}, + BatchSize: e.batchSize, + } + options.Options = embedding.GetCommonOptions(options.Options, opts...) + options = embedding.GetImplSpecificOptions(options.Options, opts...) + + // 2. 获取 callback manager + cm := callbacks.ManagerFromContext(ctx) + + // 3. 开始生成前的回调 + ctx = cm.OnStart(ctx, info, &embedding.CallbackInput{ + Texts: texts, + Config: &embedding.Config{ + Model: e.model, + }, + }) + + // 4. 执行向量生成逻辑 + vectors, tokenUsage, err := e.doEmbed(ctx, texts, options) + + // 5. 处理错误和完成回调 + if err != nil { + ctx = cm.OnError(ctx, info, err) + return nil, err + } + + ctx = cm.OnEnd(ctx, info, &embedding.CallbackOutput{ + Embeddings: vectors, + Config: &embedding.Config{ + Model: e.model, + }, + TokenUsage: tokenUsage, + }) + + return vectors, nil +} + +func (e *MyEmbedder) doEmbed(ctx context.Context, texts []string, opts *MyEmbeddingOptions) ([][]float64, *TokenUsage, error) { + // 实现逻辑 + return vectors, tokenUsage, nil +} +``` + +## 其他参考文档 + +- [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide) +- [Eino: Indexer 使用说明](/zh/docs/eino/core_modules/components/indexer_guide) +- [Eino: Retriever 使用说明](/zh/docs/eino/core_modules/components/retriever_guide) diff --git a/content/zh/docs/eino/core_modules/components/indexer_guide.md b/content/zh/docs/eino/core_modules/components/indexer_guide.md new file mode 100644 index 0000000000..969818610c --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/indexer_guide.md @@ -0,0 +1,265 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: Indexer 使用说明' +weight: 10 +--- + +## **基本介绍** + +Indexer 组件是一个用于存储和索引文档的组件。它的主要作用是将文档及其向量表示存储到后端存储系统中,并提供高效的检索能力。这个组件在以下场景中发挥重要作用: + +- 构建向量数据库,以用于语义关联搜索 + +## **组件定义** + +### **接口定义** + +```go +type Indexer interface { + Store(ctx context.Context, docs []*schema.Document, opts ...Option) (ids []string, err error) +} +``` + +#### **Store 方法** + +- 功能:存储文档并建立索引 +- 参数: + + - ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager + - docs:待存储的文档列表 + - opts:存储选项,用于配置存储行为 +- 返回值: + + - ids:存储成功的文档 ID 列表 + - error:存储过程中的错误信息 + +### **公共 Option** + +Indexer 组件使用 IndexerOption 来定义可选参数,Indexer 定义了如下的公共 option。另外,每个具体的实现可以定义自己的特定 Option,通过 WrapIndexerImplSpecificOptFn 函数包装成统一的 IndexerOption 类型。 + +```go +type Options struct { + // SubIndexes 是要建立索引的子索引列表 + SubIndexes []string + // Embedding 是用于生成文档向量的组件 + Embedding embedding.Embedder +} +``` + +可以通过以下方式设置选项: + +```go +// 设置子索引 +WithSubIndexes(subIndexes []string) Option +// 设置向量生成组件 +WithEmbedding(emb embedding.Embedder) Option +``` + +## **使用方式** + +### **单独使用** + +```go +// 初始化 indexer (以 vikingdb 为例) +indexer, err := vikingdb.NewIndexer(ctx, &vikingdb.IndexerConfig{ + // 配置参数 +}) +if err != nil { + return err +} + +// 存储文档 +ids, err := indexer.Store(ctx, docs, + indexer.WithSubIndexes([]string{"knowledge_base_1"}), + indexer.WithEmbedding(embedder), +) +if err != nil { + return err +} +``` + +### **在编排中使用** + +```go +// 在 Chain 中使用 +chain := compose.NewChain[[]*schema.Document, []string]() +chain.AppendIndexer(indexer) + +// 编译并运行 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, docs) + +// 在 Graph 中使用 +graph := compose.NewGraph[[]*schema.Document, []string]() +graph.AddIndexerNode("indexer_node", indexer) +``` + +## **Option 和 Callback 使用** + +### **Option 使用示例** + +```go +// 使用选项 (单独使用时) +ids, err := indexer.Store(ctx, docs, + // 设置子索引 + indexer.WithSubIndexes([]string{"kb_1", "kb_2"}), + // 设置向量生成组件 + indexer.WithEmbedding(embedder), +) +``` + +### **Callback 使用示例** + +```go +// 创建 callback handler +handler := &indexer.CallbackHandler{ + OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *indexer.CallbackInput) context.Context { + fmt.Printf("开始存储文档,文档数量: %d\n", len(input.Docs)) + return ctx + }, + OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *indexer.CallbackOutput) context.Context { + fmt.Printf("文档存储完成,成功存储数量: %d\n", len(output.IDs)) + return ctx + }, +} + +// 使用 callback handler +helper := template.NewHandlerHelper(). + Indexer(handler). + Handler() + +// 在运行时使用 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, docs, compose.WithCallbacks(helper)) +``` + +## **已有实现** + +1. Volc VikingDB Indexer: 基于火山引擎 VikingDB 实现的向量数据库索引器 [Indexer - VikingDB](/zh/docs/eino/ecosystem_integration/indexer_volc_vikingdb) +2. ByteES Indexer: 基于字节内部 ES 服务实现的全文索引器 + +## **自行实现参考** + +实现自定义的 Indexer 组件时,需要注意以下几点: + +1. 注意对公共 option 的处理以及组件实现级的 option 处理 +2. 注意对 callback 的处理 + +### **Option 机制** + +自定义 Indexer 可根据需要实现自己的 Option: + +```go +// 定义 Option 结构体 +type MyIndexerOptions struct { + BatchSize int + MaxRetries int +} + +// 定义 Option 函数 +func WithBatchSize(size int) indexer.Option { + return indexer.WrapIndexerImplSpecificOptFn(func(o *MyIndexerOptions) { + o.BatchSize = size + }) +} +``` + +### **Callback 处理** + +Indexer 实现需要在适当的时机触发回调。框架已经定义了标准的回调输入输出结构体: + +```go +// CallbackInput 是 indexer 回调的输入 +type CallbackInput struct { + // Docs 是待索引的文档列表 + Docs []*schema.Document + // Extra 是回调的额外信息 + Extra map[string]any +} + +// CallbackOutput 是 indexer 回调的输出 +type CallbackOutput struct { + // IDs 是索引器返回的文档 ID 列表 + IDs []string + // Extra 是回调的额外信息 + Extra map[string]any +} +``` + +### **完整实现示例** + +```go +type MyIndexer struct { + batchSize int + embedder embedding.Embedder +} + +func NewMyIndexer(config *MyIndexerConfig) (*MyIndexer, error) { + return &MyIndexer{ + batchSize: config.DefaultBatchSize, + embedder: config.DefaultEmbedder, + }, nil +} + +func (i *MyIndexer) Store(ctx context.Context, docs []*schema.Document, opts ...indexer.Option) ([]string, error) { + // 1. 处理选项 + options := &indexer.Options{}, + options = indexer.GetCommonOptions(options, opts...) + + // 2. 获取 callback manager + cm := callbacks.ManagerFromContext(ctx) + + // 3. 开始存储前的回调 + ctx = cm.OnStart(ctx, info, &indexer.CallbackInput{ + Docs: docs, + }) + + // 4. 执行存储逻辑 + ids, err := i.doStore(ctx, docs, options) + + // 5. 处理错误和完成回调 + if err != nil { + ctx = cm.OnError(ctx, info, err) + return nil, err + } + + ctx = cm.OnEnd(ctx, info, &indexer.CallbackOutput{ + IDs: ids, + }) + + return ids, nil +} + +func (i *MyIndexer) doStore(ctx context.Context, docs []*schema.Document, opts *indexer.Options) ([]string, error) { + // 实现文档存储逻辑 (注意处理公共option的参数) + // 1. 如果设置了 Embedding 组件,生成文档的向量表示 + if opts.Embedding != nil { + // 提取文档内容 + texts := make([]string, len(docs)) + for j, doc := range docs { + texts[j] = doc.Content + } + // 生成向量 + vectors, err := opts.Embedding.EmbedStrings(ctx, texts) + if err != nil { + return nil, err + } + // 将向量存储到文档的 MetaData 中 + for j, doc := range docs { + doc.WithVector(vectors[j]) + } + } + + // 2. 其他自定义逻辑 + return ids, nil +} +``` diff --git a/content/zh/docs/eino/core_modules/components/retriever_guide.md b/content/zh/docs/eino/core_modules/components/retriever_guide.md new file mode 100644 index 0000000000..fbc22265e5 --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/retriever_guide.md @@ -0,0 +1,293 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: Retriever 使用说明' +weight: 0 +--- + +## **基本介绍** + +Retriever 组件是一个用于从各种数据源检索文档的组件。它的主要作用是根据用户的查询(query)从文档库中检索出最相关的文档。这个组件在以下场景中特别有用: + +- 基于向量相似度的文档检索 +- 基于关键词的文档搜索 +- 知识库问答系统 (rag) + +## **组件定义** + +### **接口定义** + +```go +type Retriever interface { + Retrieve(ctx context.Context, query string, opts ...Option) ([]*schema.Document, error) +} +``` + +#### **Retrieve 方法** + +- 功能:根据查询检索相关文档 +- 参数: + + - ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager + - query:查询字符串 + - opts:检索选项,用于配置检索行为 +- 返回值: + + - `[]*schema.Document`:检索到的文档列表 + - error:检索过程中的错误信息 + +### **Document 结构体** + +```go +type Document struct { + // ID 是文档的唯一标识符 + ID string + // Content 是文档的内容 + Content string + // MetaData 用于存储文档的元数据信息 + MetaData map[string]any +} +``` + +### **公共 Option** + +Retriever 组件使用 RetrieverOption 来定义可选参数, 以下是 Retriever 组件需要实现的公共 option。另外,每个具体的实现可以定义自己的特定 Option,通过 WrapRetrieverImplSpecificOptFn 函数包装成统一的 RetrieverOption 类型。 + +```go +type Options struct { + // Index 是检索器使用的索引,不同检索器中的索引可能有不同含义 + Index *string + + // SubIndex 是检索器使用的子索引,不同检索器中的子索引可能有不同含义 + SubIndex *string + + // TopK 是检索的文档数量上限 + TopK *int + + // ScoreThreshold 是文档相似度的阈值,例如 0.5 表示文档的相似度分数必须大于 0.5 + ScoreThreshold *float64 + + // Embedding 是用于生成查询向量的组件 + Embedding embedding.Embedder + + // DSLInfo 是用于检索的 DSL 信息,仅在 viking 类型的检索器中使用 + DSLInfo map[string]interface{} +} +``` + +可以通过以下方式设置选项: + +```go +// 设置索引 +WithIndex(index string) Option + +// 设置子索引 +WithSubIndex(subIndex string) Option + +// 设置检索文档数量上限 +WithTopK(topK int) Option + +// 设置相似度阈值 +WithScoreThreshold(threshold float64) Option + +// 设置向量生成组件 +WithEmbedding(emb embedding.Embedder) Option + +// 设置 DSL 信息(仅用于 viking 类型检索器) +WithDSLInfo(dsl map[string]any) Option +``` + +## **使用方式** + +### **单独使用** + +```go +// 初始化 retriever (以 vikingdb 为例) +retriever, err := vikingdb.NewRetriever(ctx, &vikingdb.RetrieverConfig{ + // 配置参数 +}) +if err != nil { + return err +} + +// 基本检索 +docs, err := retriever.Retrieve(ctx, "查询内容") +if err != nil { + return err +} + +// 使用 Option 进行检索 +docs, err = retriever.Retrieve(ctx, "查询内容", + retriever.WithTopK(5), + retriever.WithScoreThreshold(0.7), +) +``` + +### **在编排中使用** + +```go +// 在 Chain 中使用 +chain := compose.NewChain[string, []*schema.Document]() +chain.AppendRetriever(retriever) + +// 编译并运行 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, "查询内容") + +// 在 Graph 中使用 +graph := compose.NewGraph[string, []*schema.Document]() +graph.AddRetrieverNode("retriever_node", retriever) +``` + +## **Option 和 Callback 使用** + +### **Callback 使用示例** + +```go +// 创建 callback handler +handler := &retriever.CallbackHandler{ + OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *retriever.CallbackInput) context.Context { + fmt.Printf("开始检索,查询内容: %s,TopK: %d\n", input.Query, input.TopK) + return ctx + }, + OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *retriever.CallbackOutput) context.Context { + fmt.Printf("检索完成,找到文档数量: %d\n", len(output.Docs)) + return ctx + }, +} + +// 使用 callback handler +helper := template.NewHandlerHelper(). + Retriever(handler). + Handler() + +// 在运行时使用 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, "查询内容", compose.WithCallbacks(helper)) +``` + +## **已有实现** + +- Volc VikingDB Retriever: 基于火山引擎 VikingDB 的检索实现 [Retriever - VikingDB](/zh/docs/eino/ecosystem_integration/retriever_volc_vikingdb) + +## **自行实现参考** + +实现自定义的 Retriever 组件时,需要注意以下几点: + +1. 注意 option 机制的处理,及处理公共的 option. +2. 注意处理 callback +3. 注意需要注入特定的 metadata,以便后续节点使用 + +### **option 机制** + +Retriever 组件提供了一组公共选项,实现时需要正确处理这些选项: + +```go +// 使用 GetCommonOptions 处理公共 option +func (r *MyRetriever) Retrieve(ctx context.Context, query string, opts ...retriever.Option) ([]*schema.Document, error) { + // 1. 初始化及读取 option + options := &retriever.Options{ // 可设置default值 + Index: &r.index, + TopK: &r.topK, + Embedding: r.embedder, + } + options = retriever.GetCommonOptions(options, opts...) + + // ... +} +``` + +### **Callback 处理** + +Retriever 实现需要在适当的时机触发回调,以下结构体是 retriever 组件定义好的结构: + +```go +// 定义回调输入输出 +type CallbackInput struct { + Query string + TopK int + Filter string + ScoreThreshold *float64 + Extra map[string]any +} + +type CallbackOutput struct { + Docs []*schema.Document + Extra map[string]any +} +``` + +### **完整实现示例** + +```go +type MyRetriever struct { + embedder embedding.Embedder + index string + topK int +} + +func NewMyRetriever(config *MyRetrieverConfig) (*MyRetriever, error) { + return &MyRetriever{ + embedder: config.Embedder, + index: config.Index, + topK: config.DefaultTopK, + }, nil +} + +func (r *MyRetriever) Retrieve(ctx context.Context, query string, opts ...retriever.Option) ([]*schema.Document, error) { + // 1. 处理选项 + options := &retriever.Options{ + Index: &r.index, + TopK: &r.topK, + Embedding: r.embedder, + } + options = retriever.GetCommonOptions(options, opts...) + + // 2. 获取 callback manager + cm := callbacks.ManagerFromContext(ctx) + + // 3. 开始检索前的回调 + ctx = cm.OnStart(ctx, info, &retriever.CallbackInput{ + Query: query, + TopK: *options.TopK, + }) + + // 4. 执行检索逻辑 + docs, err := r.doRetrieve(ctx, query, options) + + // 5. 处理错误和完成回调 + if err != nil { + ctx = cm.OnError(ctx, info, err) + return nil, err + } + + ctx = cm.OnEnd(ctx, info, &retriever.CallbackOutput{ + Docs: docs, + }) + + return docs, nil +} + +func (r *MyRetriever) doRetrieve(ctx context.Context, query string, opts *retriever.Options) ([]*schema.Document, error) { + // 1. 如果设置了 Embedding,生成查询的向量表示 (注意公共option的逻辑处理) + var queryVector []float64 + if opts.Embedding != nil { + vectors, err := opts.Embedding.EmbedStrings(ctx, []string{query}) + if err != nil { + return nil, err + } + queryVector = vectors[0] + } + + // 2. 其他逻辑 + return docs, nil +} +``` diff --git a/content/zh/docs/eino/core_modules/components/tools_node_guide.md b/content/zh/docs/eino/core_modules/components/tools_node_guide.md new file mode 100644 index 0000000000..fa453d5603 --- /dev/null +++ b/content/zh/docs/eino/core_modules/components/tools_node_guide.md @@ -0,0 +1,204 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: ToolsNode 使用说明' +weight: 0 +--- + +## **基本介绍** + +ToolsNode 组件是一个用于扩展模型能力的组件,它允许模型调用外部工具来完成特定的任务。这个组件可用于以下场景中: + +- 让模型能够获取实时信息(如搜索引擎、天气查询等) +- 使模型能够执行特定的操作(如数据库操作、API 调用等) +- 扩展模型的能力范围(如数学计算、代码执行等) +- 与外部系统集成(如知识库查询、插件系统等) + +## **组件定义** + +### **接口定义** + +Tool 组件提供了三个层次的接口: + +```go +// 基础工具接口,提供工具信息 +type BaseTool interface { + Info(ctx context.Context) (*schema.ToolInfo, error) +} + +// 可调用的工具接口,支持同步调用 +type InvokableTool interface { + BaseTool + InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error) +} + +// 支持流式输出的工具接口 +type StreamableTool interface { + BaseTool + StreamableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (*schema.StreamReader[string], error) +} +``` + +#### **Info 方法** + +- 功能:获取工具的描述信息 +- 参数: + + - ctx:上下文对象 +- 返回值: + + - `*schema.ToolInfo`:工具的描述信息 + - error:获取信息过程中的错误 + +#### **InvokableRun 方法** + +- 功能:同步执行工具 +- 参数: + + - ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager + - `argumentsInJSON`:JSON 格式的参数字符串 + - opts:工具执行的选项 +- 返回值: + + - string:执行结果 + - error:执行过程中的错误 + +#### **StreamableRun 方法** + +- 功能:以流式方式执行工具 +- 参数: + + - ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager + - `argumentsInJSON`:JSON 格式的参数字符串 + - opts:工具执行的选项 +- 返回值: + + - `*schema.StreamReader[string]`:流式执行结果 + - error:执行过程中的错误 + +### **ToolInfo 结构体** + +```go +type ToolInfo struct { + // 工具的唯一名称,用于清晰地表达其用途 + Name string + // 用于告诉模型如何/何时/为什么使用这个工具 + // 可以在描述中包含少量示例 + Desc string + // 工具接受的参数定义 + // 可以通过两种方式描述: + // 1. 使用 ParameterInfo:schema.NewParamsOneOfByParams(params) + // 2. 使用 OpenAPIV3:schema.NewParamsOneOfByOpenAPIV3(openAPIV3) + *ParamsOneOf +} +``` + +### **公共 Option** + +Tool 组件使用 ToolOption 来定义可选参数, ToolsNode 没有抽象公共的 option。每个具体的实现可以定义自己的特定 Option,通过 WrapToolImplSpecificOptFn 函数包装成统一的 ToolOption 类型。 + +## **使用方式** + +ToolsNode 无法单独使用,仅能用于编排之中,一般在其之前是 ChatModel 组件。 + +### **在编排中使用** + +```go +// 创建工具节点 +toolsNode := compose.NewToolsNode([]tool.Tool{ + searchTool, // 搜索工具 + weatherTool, // 天气查询工具 + calculatorTool, // 计算器工具 +}) + +// 在 Chain 中使用 +chain := compose.NewChain[*schema.Message, []*schema.Message]() +chain.AppendToolsNode(toolsNode) + +// 编译并运行 +runnable, err := chain.Compile() +if err != nil { + return err +} + +// 输入消息包含工具调用信息 +input := &schema.Message{ + Content: "查询深圳明天的天气", + ToolCalls: []*schema.ToolCall{ + { + Name: "weather", + Arguments: `{"city": "深圳", "date": "tomorrow"}`, + }, + }, +} + +result, err := runnable.Invoke(ctx, input) + +// graph 中 +graph := compose.NewGraph[*schema.Message, []*schema.Message]() +chain.AddToolsNode(toolsNode) +``` + +## **Option 机制** + +自定义 Tool 可根据自己需要实现特定的 Option: + +```go +// 定义 Option 结构体 +type MyToolOptions struct { + Timeout time.Duration + MaxRetries int + RetryInterval time.Duration +} + +// 定义 Option 函数 +func WithTimeout(timeout time.Duration) tool.Option { + return tool.WrapToolImplSpecificOptFn(func(o *MyToolOptions) { + o.Timeout = timeout + }) +} +``` + +## **Option 和 Callback 使用** + +### **Callback 使用示例** + +```go +// 创建 callback handler +handler := &tool.CallbackHandler{ + OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *tool.CallbackInput) context.Context { + fmt.Printf("开始执行工具,参数: %s\n", input.ArgumentsInJSON) + return ctx + }, + OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *tool.CallbackOutput) context.Context { + fmt.Printf("工具执行完成,结果: %s\n", output.Response) + return ctx + }, + OnEndWithStreamOutput: func(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[*tool.CallbackOutput]) context.Context { + fmt.Println("工具开始流式输出") + for chunk := range output.Chan() { + fmt.Printf("收到流式输出: %s\n", chunk.Response) + } + return ctx + }, +} + +// 使用 callback handler +helper := template.NewHandlerHelper(). + Tool(handler). + Handler() + +// 在运行时使用 +runnable, err := chain.Compile() +if err != nil { + return err +} +result, err := runnable.Invoke(ctx, input, compose.WithCallbacks(helper)) +``` + +## **已有实现** + +1. Google Search Tool: 基于 Google 搜索的工具实现 [Tool - Googlesearch](/zh/docs/eino/ecosystem_integration/tool/tool_googlesearch) +2. duckduckgo search tool: 基于 duckduckgo 搜索的工具实现 [Tool - DuckDuckGoSearch](/zh/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search) diff --git a/content/zh/docs/eino/core_modules/flow_integration_components/_index.md b/content/zh/docs/eino/core_modules/flow_integration_components/_index.md new file mode 100644 index 0000000000..a1793a3535 --- /dev/null +++ b/content/zh/docs/eino/core_modules/flow_integration_components/_index.md @@ -0,0 +1,19 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: Flow 集成组件' +weight: 3 +--- + +大模型应用是存在**通用场景和模式**的,若把这些场景进行抽象,就能提供一些可以帮助开发者快速构建大模型应用的工具。Eino 的 Flow 模块就是在做这件事。 + +目前 Eino 已经集成了 `react agent`、`host multi agent` 两个常用的模式。 + +- React Agent: [Eino: React Agent 使用手册](/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual) +- Multi Agent: [Eino Tutorial: Host Multi-Agent ](/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting) + +> 另外一些常用的模式例如 `retriever agent`、`chat agent`、`summary agent`、`DB agent` 等等,如果你有这类需求,希望 Eino 提供对他们的封装,请联系并和我们交流 + +# 子目录 diff --git a/content/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting.md b/content/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting.md new file mode 100644 index 0000000000..1d562f2cc1 --- /dev/null +++ b/content/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting.md @@ -0,0 +1,343 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino Tutorial: Host Multi-Agent ' +weight: 0 +--- + +Host Multi-Agent 是一个 Host 做意图识别后,跳转到某个专家 agent 做实际的生成。 + +以一个简单的“日记助手”做例子:可以写日记、读日记、根据日记回答问题。 + +Host: + +```go +func newHost(ctx context.Context) (*host.Host, error) { + chatModel, err := bytedgpt.NewChatModel(ctx, &bytedgpt.ChatModelConfig{ + BaseURL: "https://search.bytedance.net/gpt/openapi/online/multimodal/crawl", + Model: "gpt-4o-2024-05-13", + ByAzure: true, + APIKey: os.Getenv("OPENAI_API_KEY"), + }) + if err != nil { + return nil, err + } + + return &host.Host{ + ChatModel: chatModel, + SystemPrompt: "You can read and write journal on behalf of the user. When user asks a question, always answer with journal content.", + }, nil +} +``` + +写日记的“专家”:host 识别出用户意图是写日记后,会跳转到这里,把用户想要写的内容写到文件里。 + +```go +func newWriteJournalSpecialist(ctx context.Context) (*host.Specialist, error) { + chatModel, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{ + BaseURL: "http://localhost:11434", + Model: "llama3-groq-tool-use", + + Options: &api.Options{ + Temperature: 0.000001, + }, + }) + if err != nil { + return nil, err + } + + // use a chat model to rewrite user query to journal entry + // for example, the user query might be: + // + // write: I got up at 7:00 in the morning. + // + // should be rewritten to: + // + // I got up at 7:00 in the morning. + chain := compose.NewChain[[]*schema.Message, *schema.Message]() + chain.AppendLambda(compose.InvokableLambda(func(ctx context.Context, input []*schema.Message) ([]*schema.Message, error) { + systemMsg := &schema.Message{ + Role: schema._System_, + Content: "You are responsible for preparing the user query for insertion into journal. The user's query is expected to contain the actual text the user want to write to journal, as well as convey the intention that this query should be written to journal. You job is to remove that intention from the user query, while preserving as much as possible the user's original query, and output ONLY the text to be written into journal", + } + return append([]*schema.Message{systemMsg}, input...), nil + })). + AppendChatModel(chatModel). + AppendLambda(compose.InvokableLambda(func(ctx context.Context, input *schema.Message) (*schema.Message, error) { + err := appendJournal(input.Content) + if err != nil { + return nil, err + } + return &schema.Message{ + Role: schema._Assistant_, + Content: "Journal written successfully: " + input.Content, + }, nil + })) + + r, err := chain.Compile(ctx) + if err != nil { + return nil, err + } + + return &host.Specialist{ + AgentMeta: host.AgentMeta{ + Name: "write_journal", + IntendedUse: "treat the user query as a sentence of a journal entry, append it to the right journal file", + }, + Invokable: func(ctx context.Context, input []*schema.Message, opts ...agent.AgentOption) (*schema.Message, error) { + return r.Invoke(ctx, input, agent.GetComposeOptions(opts...)...) + }, + }, nil +} +``` + +读日记的“专家”:host 识别出用户意图是读日记后,会跳转到这里,读日记文件内容并一行行的输出。就是一个本地的 function。 + +```go +func newReadJournalSpecialist(ctx context.Context) (*host.Specialist, error) { + // create a new read journal specialist + return &host.Specialist{ + AgentMeta: host.AgentMeta{ + Name: "view_journal_content", + IntendedUse: "let another agent view the content of the journal", + }, + Streamable: func(ctx context.Context, input []*schema.Message, opts ...agent.AgentOption) (*schema.StreamReader[*schema.Message], error) { + now := time.Now() + dateStr := now.Format("2006-01-02") + + journal, err := readJournal(dateStr) + if err != nil { + return nil, err + } + + reader, writer := schema.Pipe[*schema.Message](0) + go func() { + scanner := bufio.NewScanner(journal) + scanner.Split(bufio.ScanLines) + + for scanner.Scan() { + line := scanner.Text() + message := &schema.Message{ + Role: schema._Assistant_, + Content: line + "\n", + } + writer.Send(message, nil) + } + + if err := scanner.Err(); err != nil { + writer.Send(nil, err) + } + + writer.Close() + }() + + return reader, nil + }, + }, nil +} +``` + +根据日记回答问题的"专家": + +```go +func newAnswerWithJournalSpecialist(ctx context.Context) (*host.Specialist, error) { + chatModel, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{ + BaseURL: "http://localhost:11434", + Model: "llama3-groq-tool-use", + + Options: &api.Options{ + Temperature: 0.000001, + }, + }) + if err != nil { + return nil, err + } + + // create a graph: load journal and user query -> chat template -> chat model -> answer + + graph := compose.NewGraph[[]*schema.Message, *schema.Message]() + + if err = graph.AddLambdaNode("journal_loader", compose.InvokableLambda(func(ctx context.Context, input []*schema.Message) (string, error) { + now := time.Now() + dateStr := now.Format("2006-01-02") + + return loadJournal(dateStr) + }), compose.WithOutputKey("journal")); err != nil { + return nil, err + } + + if err = graph.AddLambdaNode("query_extractor", compose.InvokableLambda(func(ctx context.Context, input []*schema.Message) (string, error) { + return input[len(input)-1].Content, nil + }), compose.WithOutputKey("query")); err != nil { + return nil, err + } + + systemTpl := `Answer user's query based on journal content: {journal}'` + chatTpl := prompt.FromMessages(schema._FString_, + schema.SystemMessage(systemTpl), + schema.UserMessage("{query}"), + ) + if err = graph.AddChatTemplateNode("template", chatTpl); err != nil { + return nil, err + } + + if err = graph.AddChatModelNode("model", chatModel); err != nil { + return nil, err + } + + if err = graph.AddEdge("journal_loader", "template"); err != nil { + return nil, err + } + + if err = graph.AddEdge("query_extractor", "template"); err != nil { + return nil, err + } + + if err = graph.AddEdge("template", "model"); err != nil { + return nil, err + } + + if err = graph.AddEdge(compose._START_, "journal_loader"); err != nil { + return nil, err + } + + if err = graph.AddEdge(compose._START_, "query_extractor"); err != nil { + return nil, err + } + + if err = graph.AddEdge("model", compose._END_); err != nil { + return nil, err + } + + r, err := graph.Compile(ctx) + if err != nil { + return nil, err + } + + return &host.Specialist{ + AgentMeta: host.AgentMeta{ + Name: "answer_with_journal", + IntendedUse: "load journal content and answer user's question with it", + }, + Invokable: func(ctx context.Context, input []*schema.Message, opts ...agent.AgentOption) (*schema.Message, error) { + return r.Invoke(ctx, input, agent.GetComposeOptions(opts...)...) + }, + }, nil +} +``` + +编排成 host multi agent 并在命令行启动: + +```go +func main() { + ctx := context.Background() + h, err := newHost(ctx) + if err != nil { + panic(err) + } + + writer, err := newWriteJournalSpecialist(ctx) + if err != nil { + panic(err) + } + + reader, err := newReadJournalSpecialist(ctx) + if err != nil { + panic(err) + } + + answerer, err := newAnswerWithJournalSpecialist(ctx) + if err!= nil { + panic(err) + } + + hostMA, err := host.NewMultiAgent(ctx, &host.MultiAgentConfig{ + Host: *h, + Specialists: []*host.Specialist{ + writer, + reader, + answerer, + }, + }) + if err != nil { + panic(err) + } + + cb := &logCallback{} + + for { // 多轮对话,除非用户输入了 "exit",否则一直循环 + println("\n\nYou: ") // 提示轮到用户输入了 + + var message string + scanner := bufio.NewScanner(os.Stdin) // 获取用户在命令行的输入 + for scanner.Scan() { + message += scanner.Text() + break + } + + if err := scanner.Err(); err != nil { + panic(err) + } + + if message == "exit" { + return + } + + msg := &schema.Message{ + Role: schema._User_, + Content: message, + } + + out, err := hostMA.Stream(ctx, []*schema.Message{msg}, host.WithAgentCallbacks(cb)) + if err != nil { + panic(err) + } + + defer out.Close() + + println("\nAnswer:") + + for { + msg, err := out.Recv() + if err != nil { + if err == io.EOF { + break + } + } + + print(msg.Content) + } + } +} +``` + +运行 console 输出: + +```go +You: +write journal: I got up at 7:00 in the morning + +HandOff to write_journal with argument {"reason":"I got up at 7:00 in the morning"} + +Answer: +Journal written successfully: I got up at 7:00 in the morning + +You: +read journal + +HandOff to view_journal_content with argument {"reason":"User wants to read the journal content."} + +Answer: +I got up at 7:00 in the morning + + +You: +when did I get up in the morning? + +HandOff to answer_with_journal with argument {"reason":"To find out the user's morning wake-up times"} + +Answer: +You got up at 7:00 in the morning. +``` diff --git a/content/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual.md b/content/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual.md new file mode 100644 index 0000000000..fd1deae45a --- /dev/null +++ b/content/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual.md @@ -0,0 +1,473 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: React Agent 使用手册' +weight: 0 +--- + +# 简介 + +Eino React Agent 是实现了 [React 逻辑](https://react-lm.github.io/)的智能体框架,用户可以用来快速灵活地构建并调用 React Agent. + +> 💡 +> 代码实现详见:[实现代码目录](https://github.com/cloudwego/eino/tree/main/flow/agent/react) + +## 节点拓扑&数据流图 + +react agent 底层使用 `compose.StateGraph` 作为编排方案,仅有 2 个节点: ChatModel、Tools,中间运行过程中的所有历史消息都会放入 state 中,在将所有历史消息传递给 ChatModel 之前,会 copy 消息交由 MessageModifier 进行处理,处理的结果再传递给 ChatModel。直到 ChatModel 返回的消息中不再有 tool call,则返回最终消息。 + +![](/img/eino/react_agent_graph.png) + +## 初始化 + +提供了 ReactAgent 初始化函数,必填参数为 Model 和 ToolsConfig,选填参数为 MessageModifier 和 MaxStep. + +```bash +go get github.com/cloudwego/eino-ext/components/model/openai@latest +go get github.com/cloudwego/eino@latest +``` + +```go +import ( + "github.com/cloudwego/eino-ext/components/model/openai" + + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/flow/agent/react" + "github.com/cloudwego/eino/schema" +) + +func main() { + // 先初始化所需的 chatModel + toolableChatModel, err := openai.NewChatModel(...) + + // 初始化所需的 tools + tools := compose.ToolsNodeConfig{ + InvokableTools: []tool.InvokableTool{mytool}, + StreamableTools: []tool.StreamableTool{myStreamTool}, + } + + // 创建 agent + agent, err := react.NewAgent(ctx, react.AgentConfig{ + Model: toolableChatModel, + ToolsConfig: tools, + ... + } +} +``` + +### Model + +model 接收一个 ChatModel,在 agent 内部,会调用 BindTools 接口,定义为: + +```go +type ChatModel interface { + Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error) + Stream(ctx context.Context, input []*schema.Message, opts ...Option) ( + *schema.StreamReader[*schema.Message], error) + + BindTools(tools []*schema.ToolInfo) error +} +``` + +目前,eino 提供了 openai 和 ark 的实现,只要底层模型支持 tool call 即可。 + +```bash +go get github.com/cloudwego/eino-ext/components/model/openai@latest +go get github.com/cloudwego/eino-ext/components/model/ark@latest +``` + +```go +import ( + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino-ext/components/model/ark" +) + +func openaiExample() { + chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{ + BaseURL: "https://search.bytedance.net/gpt/openapi/online/multimodal/crawl", + Key: os.Getenv("OPENAI_ACCESS_KEY"), + ByAzure: true, + Model: "{{model name which support tool call}}", + }) + + agent, err := react.NewAgent(ctx, react.AgentConfig{ + Model: chatModel, + ToolsConfig: ..., + }) +} + +func arkExample() { + arkModel, err := ark.NewChatModel(context.Background(), ark.ChatModelConfig{ + APIKey: os.Getenv("ARK_API_KEY"), + Model: os.Getenv("ARK_MODEL"), + }) + + agent, err := react.NewAgent(ctx, react.AgentConfig{ + Model: arkModel, + ToolsConfig: ..., + }) +} +``` + +### ToolsConfig + +toolsConfig 类型为 `compose.ToolsNodeConfig`, 在 eino 中,若要构建一个 Tool 节点,则需要提供 Tool 的信息,以及调用 Tool 的接口,tool 的接口定义如下: + +```go +type InvokableRun func(ctx context.Context, arguments string, opts ...Option) (content string, err error) +type StreamableRun func(ctx context.Context, arguments string, opts ...Option) (content *schema.StreamReader[string], err error) + +type BaseTool interface { + Info() *schema.ToolInfo +} + +// InvokableTool the tool for ChatModel intent recognition and ToolsNode execution. +type InvokableTool interface { + BaseTool + Run() InvokableRun +} + +// StreamableTool the stream tool for ChatModel intent recognition and ToolsNode execution. +type StreamableTool interface { + BaseTool + Run() StreamableRun +} +``` + +用户可以根据 tool 的接口定义自行实现所需的 tool,同时框架也提供了更简便的构建 tool 的方法: + +```go +import ( + "context" + + "github.com/cloudwego/eino/components/tool/utils" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +func main() { + // 提供 tool 的信息 + toolInfo := &schema.ToolInfo{ + Name: "xxx", + Desc: "description for tool, it's important for chatmodel choice which tool to use", + Params: map[string]*schema.ParameterInfo{ + "param01": { + Type: "string", + Desc: "xxxx", // import for chatmodel generate params + }, + "param01": { + Type: "string", + Desc: "xxxx", + }, + }, + } + + // 提供 tool 的调用方法 + // 需满足 type InvokeFunc[T, D any] func(ctx context.Context, input T) (output D, err error) + toolInvokeFunc := func(ctx context.Context, in string) (out string, err error) + + // 构建 tool + invokeTool := utils.NewTool(toolInfo, toolInvokeFunc) + + // stream tool 同理 + // utils.NewStreamTool + + toolConfig := &compose.ToolsNodeConfig{ + InvokableTools: []tool.InvokableTool{invokeTool}, + } + +} +``` + +### MessageModifier + +MessageModifier 会在每次把所有历史消息传递给 ChatModel 之前执行,其定义为: + +```go +// modify the input messages before the model is called. +type MessageModifier func(ctx context.Context, input []*schema.Message) []*schema.Message +``` + +框架提供了一个简便的 PersonaModifier,用于在消息列表的最头部增加一个代表 agent 个性的 system message,使用如下: + +```go +import ( + "github.com/cloudwego/eino/flow/agent/react" + "github.com/cloudwego/eino/schema" +) + +func main() { + persona := `你是一个 golang 开发专家.` + + agent, err := react.NewAgent(ctx, react.AgentConfig{ + Model: toolableChatModel, + ToolsConfig: tools, + + // MessageModifier + MessageModifier: react.NewPersonaModifier(persona), + } + + agent.Generate(ctx, []*schema.Message{{Role: schame.Human, Content: "写一个 hello world 的代码"}} + // 实际到 ChatModel 的 input 为 + // []*schema.Message{ + // {Role: schema.System, Content: "你是一个 golang 开发专家."}, + // {Role: schema.Human, Content: "写一个 hello world 的代码"} + //} +} +``` + +### MaxStep + +指定 Agent 最大运行步长,每次从一个节点转移到下一个节点为一步,默认值为 12。 + +由于 Agent 中一次循环为 ChatModel + Tools,即为 2 步,因此默认值 12 最多可运行 6 个循环。但由于最后一步必须为 ChatModel 返回 (因为 ChatModel 结束后判断无须运行 tool 才能返回最终结果),因此最多运行 5 次 tool。 + +同理,若希望最多可运行 10 个循环 (10 次 ChatModel + 9 次 Tools),则需要设置 MaxStep 为 20。若希望最多运行 20 个循环,则 MaxStep 需为 40。 + +```go +func main() { + agent, err := react.NewAgent(ctx, react.AgentConfig{ + Model: toolableChatModel, + ToolsConfig: tools, + MaxStep: 20, + } +} +``` + +## 调用 + +### Generate + +```go +import ( + "context" + + "github.com/cloudwego/eino/flow/agent/react" + "github.com/cloudwego/eino/schema" +) + +func main() { + agent, err := react.NewAgent(...) + + var outMessage *schema.Message + outMessage, err = agent.Generate(ctx, []*schema.Message{ + { + Role: schema.Human, + Content: "写一个 golang 的 hello world 程序", + }, + }) +} +``` + +### Stream + +```go +import ( + "context" + "fmt" + + "github.com/cloudwego/eino/flow/agent/react" + "github.com/cloudwego/eino/schema" +) + +func main() { + agent, err := react.NewAgent(...) + + var msgReader *schema.StreamReader[*schema.Message] + msgReader, err = agent.Stream(ctx, []*schema.Message{ + { + Role: schema.Human, + Content: "写一个 golang 的 hello world 程序", + }, + }) + + for { + // msg type is *schema.Message + msg, err := msgReader.Recv() + if err != nil { + if errors.Is(err, io.EOF) { + // finish + break + } + // error + log.Printf("failed to recv: %v\n", err) + return + } + + fmt.Print(msg.Content) + } +} +``` + +### WithCallbacks + +Callback 是在 Agent 运行时特定时机执行的回调,传递了一些运行时信息,定义为: + +```go +type AgentCallback interface { + OnChatModelStart(ctx context.Context, input *model.CallbackInput) + OnChatModelEnd(ctx context.Context, output *model.CallbackOutput) + OnChatModelEndStream(ctx context.Context, output *schema.StreamReader[*model.CallbackOutput]) + + OnToolStart(ctx context.Context, input string) + OnToolEnd(ctx context.Context, output string) + OnToolEndStream(ctx context.Context, output *schema.StreamReader[string]) + + OnError(ctx context.Context, err error) +} +``` + +框架提供了空的 BaseCallback 来辅助用户实现接口: + +```go +import "github.com/cloudwego/eino/flow/agent/react" + +// type BaseCallback struct{} +// func (cb *BaseCallback) OnChatModelStart(ctx context.Context, input *model.CallbackInput) {} +// func (cb *BaseCallback) OnChatModelEnd(ctx context.Context, output *model.CallbackOutput) {} +// func (cb *BaseCallback) OnChatModelEndStream(ctx context.Context, output *schema.StreamReader[*model.CallbackOutput]) {} + +// func (cb *BaseCallback) OnToolStart(ctx context.Context, input string) {} +// func (cb *BaseCallback) OnToolEnd(ctx context.Context, output string) {} +// func (cb *BaseCallback) OnToolEndStream(ctx context.Context, output *schema.StreamReader[string]) {} + +// func (cb *BaseCallback) OnError(ctx context.Context, err error) {} + +type MyCallback struct{ + *react.BaseCallback +} + +// 重载需要的方法…… +func (m *MyCallback) OnChatModelEnd(ctx context.Context, output *model.CallbackOutput) { + // some logic +} + +func main() { + agent, err := react.NewAgent(...) + if err != nil {...} + + agent.Generate(ctx, []*schema.Message{...}, react.WithCallbacks(&MyCallback{}) +} +``` + +## Agent In Graph/Chain + +目前 agent 不是一级的 component 编排到 graph 中,可作为 Lambda 编排 Agent: + +```go +import ( + "context" + + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/model/openai" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/flow/agent/react" + "github.com/cloudwego/eino/schema" +) + +func main() { + // 创建一个 chain + chain := compose.NewChain[[]*schema.Message, string]() + + // 创建 agent + agent, err := react.NewAgent(...) + + // 把 agent 变成一个 Lambda + agentLambda, err := compose.AnyLambda(agent.Generate, agent.Stream, nil, nil) + + // 把 agentLambda 加入到 chain 的第一个节点 + chain.AppendLambda(agentLambda) + + // other + chain.AppendLambda(...).AppendXXX(...) + runnable, err := chain.Compile() + + // 调用时可传入 LambdaOption,用于传递 agent 的 calloption,例如 callback + res, err := r.Invoke(ctx, []*schema.Message{{Role: schema.Human, Content: "hello"}}, + compose.WithLambdaOption(agent.WithCallbacks(&MyCallback{}))) + +} +``` + +## Demo + +### 基本信息 + +简介:这是一个拥有两个 tool (query_restaurants 和 query_dishes ) 的 `美食推荐官` + +地址:[eino-examples/flow/agent/react](https://github.com/cloudwego/eino-examples/tree/main/flow/agent/react) + +使用方式: + +1. clone eino-examples repo,并 cd 到根目录 +2. 提供一个 `OPENAI_API_KEY`: `export OPENAI_API_KEY=xxxxxxx` +3. 运行 demo: `go run flow/agent/react/react.go` + +### 运行过程解释 + +- 模拟用户输入了 `我在海淀区,给我推荐一些菜,需要有口味辣一点的菜,至少推荐有 2 家餐厅` +- agent 运行第一个节点 `ChatModel`,大模型判断出需要做一次 ToolCall 调用来查询餐厅,并且给出的参数为: + +```json +"function": { + "name": "query_restaurants", + "arguments": "{\"location\":\"海淀区\",\"topn\":2}" +} +``` + +- 进入 `Tools` 节点,调用 查询餐厅 的 tool,并且得到结果,结果返回了 2 家海淀区的餐厅信息: + +```json +[{"id":"1001","name":"跳不动的E世界5F餐厅","place":"中关村E世界 5F, 左转进入","desc":"","score":3},{"id":"1002","name":"跳动的E世界地下餐厅","place":"中关村E世界-1F","desc":"","score":5}] +``` + +- 得到 tool 的结果后,此时对话的 history 中包含了 tool 的结果,再次运行 `ChatModel`,大模型判断出需要再次调用另一个 ToolCall,用来查询餐厅有哪些菜品,注意,由于有两家餐厅,因此大模型返回了 2 个 ToolCall,如下: + +```json +"Message": { + "role": "ai", + "content": "", + "tool_calls": [ // <= 这里有 2 个 tool call + { + "index": 1, + "id": "call_wV7zA3vGGJBhuN7r9guhhAfF", + "function": { + "name": "query_dishes", + "arguments": "{\"restaurant_id\": \"1002\", \"topn\": 5}" + } + }, + { + "index": 0, + "id": "call_UOsp0jRtzEbfxixNjP5501MF", + "function": { + "name": "query_dishes", + "arguments": "{\"restaurant_id\": \"1001\", \"topn\": 5}" + } + } + ] + } +``` + +- 再次进入到 `Tools` 节点,由于有 2 个 tool call,Tools 节点内部并发执行这两个调用,并且均加入到对话的 history 中,从 callback 的调试日志中可以看到结果如下: + +```json +=========[OnToolStart]========= +{"restaurant_id": "1001", "topn": 5} +=========[OnToolEnd]========= +[{"name":"红烧肉","desc":"一块红烧肉","price":20,"score":8},{"name":"清泉牛肉","desc":"很多的水煮牛肉","price":50,"score":8},{"name":"清炒小南瓜","desc":"炒的糊糊的南瓜","price":5,"score":5},{"name":"韩式辣白菜","desc":"这可是开过光的辣白菜,好吃得很","price":20,"score":9},{"name":"酸辣土豆丝","desc":"酸酸辣辣的土豆丝","price":10,"score":9}] +=========[OnToolStart]========= +{"restaurant_id": "1002", "topn": 5} +=========[OnToolEnd]========= +[{"name":"红烧排骨","desc":"一块一块的排骨","price":43,"score":7},{"name":"大刀回锅肉","desc":"经典的回锅肉, 肉很大","price":40,"score":8},{"name":"火辣辣的吻","desc":"凉拌猪嘴,口味辣而不腻","price":60,"score":9},{"name":"辣椒拌皮蛋","desc":"擂椒皮蛋,下饭的神器","price":15,"score":8}] +``` + +- 得到所有 tool call 返回的结果后,再次进入 `ChatModel` 节点,这次大模型发现已经拥有了回答用户提问的所有信息,因此整合信息后输出结论,由于调用时使用的 `Stream` 方法,因此流式返回的大模型结果。 + +## 关联阅读 + +- [Eino Tutorial: Host Multi-Agent ](/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting) diff --git a/content/zh/docs/eino/ecosystem_integration/_index.md b/content/zh/docs/eino/ecosystem_integration/_index.md new file mode 100644 index 0000000000..084f26711e --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/_index.md @@ -0,0 +1,53 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: 生态集成' +weight: 0 +--- + +## 组件集成 + +### ChatModel + +- openai: [ChatModel OpenAI](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_openai) +- ark: [ChatModel - ARK](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ark) +- ollama: [ChatModel - Ollama](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ollama) + +### Document + +#### Loader + +- file: [[🚧]Loader - local file](/zh/docs/eino/ecosystem_integration/document/loader_local_file) +- s3: [[🚧]Loader - amazon s3](/zh/docs/eino/ecosystem_integration/document/loader_amazon_s3) +- web url: [[🚧]Loader - web url](/zh/docs/eino/ecosystem_integration/document/loader_web_url) + +#### Parser + +- html: [[🚧]Parser - html](/zh/docs/eino/ecosystem_integration/document/parser_html) +- pdf: [[🚧]Parser - pdf](/zh/docs/eino/ecosystem_integration/document/parser_pdf) + +#### Transformer + +- markdown splitter: [[🚧]Splitter - markdown](/zh/docs/eino/ecosystem_integration/document/splitter_markdown) +- recursive splitter: [[🚧]Splitter - recursive](/zh/docs/eino/ecosystem_integration/document/splitter_recursive) +- semantic splitter: [[🚧]Splitter - semantic](/zh/docs/eino/ecosystem_integration/document/splitter_semantic) + +### Embedding + +- ark: [[🚧]Embedding - ARK](/zh/docs/eino/ecosystem_integration/embedding/embedding_ark) +- openai: [[🚧]Embedding - OpenAI](/zh/docs/eino/ecosystem_integration/embedding/embedding_openai) + +### Indexer + +- volc vikingdb: [[🚧]Indexer - volc VikingDB](/zh/docs/eino/ecosystem_integration/indexer_volc_vikingdb) + +### Retriever + +- volc vikingdb: [[🚧]Retriever - volc VikingDB](/zh/docs/eino/ecosystem_integration/retriever_volc_vikingdb) + +### Tools + +- googlesearch: [[🚧]Tool - Googlesearch](/zh/docs/eino/ecosystem_integration/tool/tool_googlesearch) +- duckduckgo search: [[🚧]Tool - DuckDuckGoSearch](/zh/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search) diff --git a/content/zh/docs/eino/ecosystem_integration/chat_model/_index.md b/content/zh/docs/eino/ecosystem_integration/chat_model/_index.md new file mode 100644 index 0000000000..a279314f1c --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/chat_model/_index.md @@ -0,0 +1,10 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: ChatModel +weight: 0 +--- + + diff --git a/content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ark.md b/content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ark.md new file mode 100644 index 0000000000..f5700a686c --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ark.md @@ -0,0 +1,237 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: ChatModel - ARK +weight: 0 +--- + +## **基本介绍** + +Ark 是 ChatModel 接口的一个实现,用于与火山引擎 Ark Runtime 服务进行交互。Ark Runtime 是火山引擎提供的大语言模型运行时服务,提供了丰富的模型选择和完整的 API 功能。本组件通过 Ark Runtime Go SDK 与服务进行交互,可调用火山引擎上部署的 豆包大模型、暗影之月大模型 等。该组件实现了 [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide)。 + +## **使用方式** + +### **组件初始化** + +Ark 模型通过 `NewChatModel` 函数进行初始化,主要配置参数如下: + +```go +model, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{ + // 服务配置 + BaseURL: "https://ark.cn-beijing.volces.com/api/v3", // 服务地址 + Region: "cn-beijing", // 区域 + HTTPClient: httpClient, // 自定义 HTTP 客户端 + Timeout: &timeout, // 超时时间 + RetryTimes: &retries, // 重试次数 + + // 认证配置(二选一) + APIKey: "your-api-key", // API Key 认证 + AccessKey: "your-ak", // AK/SK 认证 + SecretKey: "your-sk", + + // 模型配置 + Model: "endpoint-id", // 模型端点 ID + + // 生成参数 + MaxTokens: &maxTokens, // 最大生成长度 + Temperature: &temp, // 温度 + TopP: &topP, // Top-P 采样 + Stop: []string{}, // 停止词 + FrequencyPenalty: &fp, // 频率惩罚 + PresencePenalty: &pp, // 存在惩罚 + RepetitionPenalty: &rp, // 重复惩罚 + N: &n, // 生成数量 + + // 高级参数 + ResponseFormat: &format, // 响应格式 + LogitBias: map[string]int{}, // Token 偏置 + LogProbs: &logProbs, // 是否返回概率 + TopLogProbs: &topLp, // Top K 概率数量 + User: &user, // 用户标识 +}) +``` + +### **生成对话** + +对话生成支持普通模式和流式模式: + +```go +func main() { + // 普通模式 + response, err := model.Generate(ctx, messages) + + // 流式模式 + stream, err := model.Stream(ctx, messages) +} +``` + +消息格式示例: + +> 注意,是否支持多模态的图片需要看具体的模型 + +```go +func main() { + messages := []*schema.Message{ + // 系统消息 + schema.SystemMessage("你是一个助手"), + + // 文本消息 + schema.UserMessage("你好"), + + // 多模态消息(包含图片) + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + { + Type: schema.ChatMessagePartTypeText, + Text: "这张图片是什么?", + }, + { + Type: schema.ChatMessagePartTypeImageURL, + ImageURL: &schema.ChatMessageImageURL{ + URL: "https://example.com/image.jpg", + Detail: schema.ImageURLDetailAuto, + }, + }, + }, + }, + } +} +``` + +### **工具调用** + +支持绑定工具: + +```go +// 定义工具 +tools := []*schema.ToolInfo{ + { + Name: "search", + Desc: "搜索信息", + ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ + "query": { + Type: schema.String, + Desc: "搜索关键词", + Required: true, + }, + }), + }, +} + +// 绑定工具 +err := model.BindTools(tools) +``` + +> 工具相关信息,可以参考 [[🚧]Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide) + +### **完整使用示例** + +#### **直接对话** + +```go +package main + +import ( + "context" + "time" + + "github.com/cloudwego/eino-ext/components/model/ark" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化模型 + model, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{ + APIKey: "your-api-key", + Region: "cn-beijing", + Model: "endpoint-id", + Timeout: ptrOf(30 * time.Second), + }) + if err != nil { + panic(err) + } + + // 准备消息 + messages := []*schema.Message{ + schema.SystemMessage("你是一个助手"), + schema.UserMessage("介绍一下火山引擎"), + } + + // 生成回复 + response, err := model.Generate(ctx, messages) + if err != nil { + panic(err) + } + + // 处理回复 + println(response.Content) + + // 获取 Token 使用情况 + if usage := response.ResponseMeta.Usage; usage != nil { + println("提示 Tokens:", usage.PromptTokens) + println("生成 Tokens:", usage.CompletionTokens) + println("总 Tokens:", usage.TotalTokens) + } +} +``` + +#### **流式对话** + +```go +package main + +import ( + "context" + "time" + + "github.com/cloudwego/eino-ext/components/model/ark" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化模型 + model, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{ + APIKey: "your-api-key", + Model: "ep-xxx", + }) + if err != nil { + panic(err) + } + + // 准备消息 + messages := []*schema.Message{ + schema.SystemMessage("你是一个助手"), + schema.UserMessage("介绍一下 Eino"), + } + + // 获取流式回复 + reader, err := model.Stream(ctx, messages) + if err != nil { + panic(err) + } + defer reader.Close() // 注意要关闭 + + // 处理流式内容 + for { + chunk, err := reader.Recv() + if err != nil { + break + } + print(chunk.Content) + } +} +``` + +## **相关文档** + +- [[🚧]Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide) +- [[🚧]ChatModel - OpenAI](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_openai) +- [[🚧]ChatModel - Ollama](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ollama) +- [火山引擎官网](https://www.volcengine.com/product/doubao) diff --git a/content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ollama.md b/content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ollama.md new file mode 100644 index 0000000000..7ed67e3028 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ollama.md @@ -0,0 +1,213 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: ChatModel - Ollama +weight: 0 +--- + +## **基本介绍** + +Ollama 模型是 ChatModel 接口的一个实现,用于与 Ollama 本地大语言模型服务进行交互,Ollama 是一个开源的本地大语言模型运行框架,支持多种开源模型(如 Llama、Mistral 等),提供简单的 API 接口和完整的性能监控。。该组件实现了 [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide) + +## **使用方式** + +### **组件初始化** + +Ollama 模型通过 `NewChatModel` 函数进行初始化,主要配置参数如下: + +```go +model, err := NewChatModel(ctx, &ChatModelConfig{ + // 基础配置 + BaseURL: "http://localhost:11434", // Ollama 服务地址 + Timeout: 30 * time.Second, // 请求超时时间 + + // 模型配置 + Model: "llama2", // 模型名称 + Format: "json", // 输出格式(可选) + KeepAlive: &keepAlive, // 保持连接时间 + + // 模型参数 + Options: &api.Options{ + Temperature: 0.7, // 温度 + TopP: 0.9, // Top-P 采样 + TopK: 40, // Top-K 采样 + Seed: 42, // 随机种子 + NumPredict: 100, // 最大生成长度 + Stop: []string{}, // 停止词 + RepeatPenalty: 1.1, // 重复惩罚 + NumCtx: 4096, // 上下文窗口大小 + NumGPU: 1, // GPU 数量 + NumThread: 4, // CPU 线程数 + }, +}) +``` + +### **生成对话** + +对话生成支持普通模式和流式模式: + +```go +func main() { + // 普通模式 + response, err := model.Generate(ctx, messages) + + // 流式模式 + stream, err := model.Stream(ctx, messages) +} +``` + +消息格式示例: + +```go +func main() { + messages := []*schema.Message{ + // 系统消息 + schema.SystemMessage("你是一个助手"), + + // 用户消息 + schema.UserMessage("你好") + } +} +``` + +### **工具调用** + +支持绑定工具: + +> 注意,仅有支持 function call 的模型才能使用这个能力 + +```go +func main() { + // 定义工具 + tools := []*schema.ToolInfo{ + { + Name: "search", + Desc: "搜索信息", + ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ + "query": { + Type: schema.String, + Desc: "搜索关键词", + Required: true, + }, + }), + }, + } + + // 绑定工具 + err := model.BindTools(tools) +} +``` + +### **完整使用示例** + +#### **基本对话** + +```go +package main + +import ( + "context" + "time" + + "github.com/cloudwego/eino-ext/components/model/ollama" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化模型 + model, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{ + BaseURL: "http://localhost:11434", + Timeout: 30 * time.Second, + Model: "llama2", + Options: &api.Options{ + Temperature: 0.7, + NumPredict: 100, + }, + }) + if err != nil { + panic(err) + } + + // 准备消息 + messages := []*schema.Message{ + schema.SystemMessage("你是一个助手"), + schema.UserMessage("介绍一下 Ollama"), + } + + // 生成回复 + response, err := model.Generate(ctx, messages) + if err != nil { + panic(err) + } + + // 处理回复 + println(response.Content) + + // 获取性能指标 + if metrics, ok := response.ResponseMeta.Extra["ollama_metrics"].(api.Metrics); ok { + println("评估时间:", metrics.EvalDuration) + println("总时间:", metrics.TotalDuration) + } +} +``` + +#### **流式对话** + +```go +package main + +import ( + "context" + "time" + + "github.com/cloudwego/eino-ext/components/model/ollama" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化模型 + model, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{ + BaseURL: "http://localhost:11434", + Timeout: 30 * time.Second, + Model: "llama2", + }) + if err != nil { + panic(err) + } + + // 准备消息 + messages := []*schema.Message{ + schema.SystemMessage("你是一个助手"), + schema.UserMessage("讲个笑话"), + } + + // 获取流式回复 + stream, err := model.Stream(ctx, messages) + if err != nil { + panic(err) + } + defer stream.Close() // 注意关闭 reader + + // 处理流式内容 + for { + chunk, err := stream.Recv() + if err != nil { + break + } + print(chunk.Content) + } +} +``` + +## **相关文档** + +- [[🚧]Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide) +- [[🚧]ChatModel - OpenAI](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_openai) +- [[🚧]Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide) +- [Ollama 模型库](https://ollama.ai/library) diff --git a/content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_openai.md b/content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_openai.md new file mode 100644 index 0000000000..beac147621 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/chat_model/chat_model_openai.md @@ -0,0 +1,235 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: ChatModel - OpenAI +weight: 0 +--- + +## **基本介绍** + +OpenAI 模型是 ChatModel 接口的一个实现,用于与 OpenAI 的 GPT 系列模型进行交互。该组件实现了 [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide),主要用于以下场景: + +- 需要使用 OpenAI 的 GPT 系列模型 +- 需要使用 Azure OpenAI Service +- 使用其他 OpenAI 接口兼容的模型 + +## **使用方式** + +### **组件初始化** + +OpenAI 模型通过 `NewChatModel` 函数进行初始化,主要配置参数如下: + +```go +func main() { + model, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{ + // Azure OpenAI Service 配置(可选) + ByAzure: false, // 是否使用 Azure OpenAI + BaseURL: "your-url", // Azure API 基础 URL + APIVersion: "2023-05-15", // Azure API 版本 + + // 基础配置 + APIKey: "your-key", // API 密钥 + Timeout: 30 * time.Second, // 超时时间 + + // 模型参数 + Model: "gpt-4", // 模型名称 + MaxTokens: &maxTokens,// 最大生成长度 + Temperature: &temp, // 温度 + TopP: &topP, // Top-P 采样 + N: &n, // 生成数量 + Stop: []string{},// 停止词 + PresencePenalty: &pp, // 存在惩罚 + FrequencyPenalty: &fp, // 频率惩罚 + + // 高级参数 + ResponseFormat: &format, // 响应格式 + Seed: &seed, // 随机种子 + LogitBias: map[string]int{}, // Token 偏置 + LogProbs: &logProbs, // 是否返回概率 + TopLogProbs: &topLp, // Top K 概率数量 + User: &user, // 用户标识 + }) +} +``` + +> - 参数具体含义,可以参考: [https://platform.openai.com/docs/api-reference/chat/create](https://platform.openai.com/docs/api-reference/chat/create) +> - azure 相关服务,可以参考: [https://learn.microsoft.com/en-us/azure/ai-services/openai/](https://learn.microsoft.com/en-us/azure/ai-services/openai/) + +### **生成对话** + +对话生成支持普通模式和流式模式: + +```go +func main() { + // invoke模式 + response, err := model.Generate(ctx, messages) + + // 流式模式 + stream, err := model.Stream(ctx, messages) +} +``` + +消息格式示例: + +```go +messages := []*schema.Message{ + // 系统消息 + schema.SystemMessage("你是一个助手"), + + // 文本消息 + schema.UserMessage("你好"), + + // 多模态消息(包含图片) + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + { + Type: schema.ChatMessagePartTypeImageURL, + ImageURL: &schema.ChatMessageImageURL{ + URL: "https://example.com/image.jpg", + Detail: "high", + }, + }, + { + Type: schema.ChatMessagePartTypeText, + Text: "这张图片是什么?", + }, + }, + }, +} +``` + +### **工具调用** + +支持绑定工具和强制工具调用: + +```go +func main() { + // 定义工具 + tools := []*schema.ToolInfo{ + { + Name: "search", + Desc: "搜索信息", + ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ + "query": { + Type: schema.String, + Desc: "搜索关键词", + Required: true, + }, + }), + }, + } + + // 绑定可选工具 + err := model.BindTools(tools) + + // 绑定强制工具 + err := model.BindForcedTools(tools) +} +``` + +> 工具相关信息,可以参考 [Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide) + +### **完整使用示例** + +#### **直接对话** + +```go +package main + +import ( + "context" + "time" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化模型 + model, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{ + APIKey: "your-api-key", // required + Timeout: 30 * time.Second, + Model: "gpt-4", // required + }) + if err != nil { + panic(err) + } + + // 准备消息 + messages := []*schema.Message{ + schema.SystemMessage("你是一个助手"), + schema.UserMessage("介绍一下 eino"), + } + + // 生成回复 + response, err := model.Generate(ctx, messages) + if err != nil { + panic(err) + } + + // 处理回复 + println(response.Content) +} +``` + +#### **流式对话** + +```go +package main + +import ( + "context" + "time" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化模型 + model, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{ + APIKey: "your-api-key", + Timeout: 30 * time.Second, + Model: "gpt-4", + }) + if err != nil { + panic(err) + } + + // 准备消息 + messages := []*schema.Message{ + schema.SystemMessage("你是一个助手"), + schema.UserMessage("写一个故事"), + } + + // 获取流式回复 + reader, err := model.Stream(ctx, messages) + if err != nil { + panic(err) + } + defer reader.Close() // 注意要关闭 + + // 处理流式内容 + for { + chunk, err := reader.Recv() + if err != nil { + break + } + print(chunk.Content) + } +} +``` + +## **相关文档** + +- [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide) +- [Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide) +- [ChatModel - ARK](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ark) +- [ChatModel - Ollama](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ollama) diff --git a/content/zh/docs/eino/ecosystem_integration/document/_index.md b/content/zh/docs/eino/ecosystem_integration/document/_index.md new file mode 100644 index 0000000000..752eb529fd --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/document/_index.md @@ -0,0 +1,10 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: Document +weight: 0 +--- + + diff --git a/content/zh/docs/eino/ecosystem_integration/document/loader_amazon_s3.md b/content/zh/docs/eino/ecosystem_integration/document/loader_amazon_s3.md new file mode 100644 index 0000000000..5ac99e004b --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/document/loader_amazon_s3.md @@ -0,0 +1,110 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Loader - amazon s3 +weight: 0 +--- + +## **基本介绍** + +S3 文档加载器是 Document Loader 接口的一个实现,用于从 AWS S3 存储桶中加载文档内容。该组件实现了 [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide) + +### **AWS S3 服务介绍** + +Amazon Simple Storage Service (Amazon S3) 是一种对象存储服务,提供行业领先的可扩展性、数据可用性、安全性和性能。本组件通过 AWS SDK for Go v2 与 S3 服务进行交互,支持通过访问密钥或默认凭证的方式进行认证。 + +## **使用方式** + +### **组件初始化** + +S3 文档加载器通过 `NewS3Loader` 函数进行初始化,主要配置参数如下: + +```go +loader, err := NewS3Loader(ctx, &LoaderConfig{ + Region: aws.String("us-east-1"), // AWS 区域 + AWSAccessKey: aws.String("your-access-key"), // AWS 访问密钥ID + AWSSecretKey: aws.String("your-secret-key"), // AWS 访问密钥 + UseObjectKeyAsID: true, // 是否使用对象键作为文档ID + Parser: &parser.TextParser{}, // 文档解析器,默认为 TextParser +}) +``` + +配置参数说明: + +- `Region`:AWS S3 存储桶所在的区域 +- `AWSAccessKey` 和 `AWSSecretKey`:AWS 访问凭证,如果不提供则使用默认凭证链 +- `UseObjectKeyAsID`:是否将 S3 对象的键值用作文档 ID +- `Parser`:用于解析文档内容的解析器,默认使用 TextParser 直接将内容转换为字符串 + +### **加载文档** + +文档加载通过 `Load` 方法实现: + +```go +docs, err := loader.Load(ctx, document.Source{ + URI: "s3://bucket-name/path/to/document.txt", +}) +``` + +URI 格式说明: + +- 必须以 `s3://` 开头 +- 后接存储桶名称和对象键 +- 示例:`s3://my-bucket/folder/document.pdf` + +注意事项: + +- 目前不支持通过前缀批量加载文档 +- URI 必须指向具体的对象,不能以 `/` 结尾 +- 确保有足够的权限访问指定的存储桶和对象 + +### **完整使用示例** + +#### **单独使用** + +```go +package main + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + s3loader "github.com/cloudwego/eino-ext/components/document/loader/s3" + "github.com/cloudwego/eino/components/document" +) + +func main() { + ctx := context.Background() + + // 初始化加��器 + loader, err := s3loader.NewS3Loader(ctx, &s3loader.LoaderConfig{ + Region: aws.String("us-east-1"), + AWSAccessKey: aws.String("your-access-key"), + AWSSecretKey: aws.String("your-secret-key"), + UseObjectKeyAsID: true, + }) + if err != nil { + panic(err) + } + + // 加载文档 + docs, err := loader.Load(ctx, document.Source{ + URI: "s3://my-bucket/documents/sample.txt", + }) + if err != nil { + panic(err) + } + + // 使用文档内容 + for _, doc := range docs { + println(doc.Content) + } +} +``` + +## **相关文档** + +- [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide) +- [Eino: Document Parser 接口使用说明](/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide) diff --git a/content/zh/docs/eino/ecosystem_integration/document/loader_local_file.md b/content/zh/docs/eino/ecosystem_integration/document/loader_local_file.md new file mode 100644 index 0000000000..a01cd45863 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/document/loader_local_file.md @@ -0,0 +1,110 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Loader - local file +weight: 0 +--- + +## **基本介绍** + +local file 文件加载器是 Document Loader 接口的一个实现,用于从本地文件系统中加载文档内容。该组件实现了 [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide)。 + +### **特性介绍** + +本地文件加载器具有以下特点: + +- 支持通过文件路径直接加载文档 +- 自动识别文件类型并选择合适的解析器 (需设置 ExtParser) +- 保留文件的元数据信息 +- 支持将文件名作为文档 ID + +## **使用方式** + +### **组件初始化** + +本地文件加载器通过 `NewFileLoader` 函数进行初始化,主要配置参数如下: + +```go +loader, err := NewFileLoader(ctx, &FileLoaderConfig{ + UseNameAsID: true, // 是否使用文件名作为文档ID + Parser: &parser.TextParser{}, // 可选:指定自定义解析器 +}) +``` + +配置参数说明: + +- `UseNameAsID`:是否将文件名用作文档 ID +- `Parser`:文档解析器,如果不指定则使用默认的扩展名解析器(ExtParser,当前仅实现了 TextParser) + +### **加载文档** + +文档加载通过 `Load` 方法实现: + +```go +docs, err := loader.Load(ctx, document.Source{ + URI: "./path/to/document.txt", +}) +``` + +文档加载后会自动添加以下元数据: + +- `_file_name`:文件名 +- `_extension`:文件扩展名 +- `_source`:文件的完整路径 + +注意事项: + +- 路径必须指向一个文件,不能是目录 +- 文件必须可读 +- 如果 `UseNameAsID` 为 true,单文件时使用文件名作为 ID,多文档时使用 `文件名_序号` 作为 ID + +### **完整使用示例** + +#### **单独使用** + +```go +package main + +import ( + "context" + + fileloader "github.com/cloudwego/eino-ext/components/document/loader/file" + "github.com/cloudwego/eino/components/document" +) + +func main() { + ctx := context.Background() + + // 初始化加载器 + loader, err := fileloader.NewFileLoader(ctx, &fileloader.FileLoaderConfig{ + UseNameAsID: true, + }) + if err != nil { + panic(err) + } + + // 加载文档 + docs, err := loader.Load(ctx, document.Source{ + URI: "./documents/sample.txt", + }) + if err != nil { + panic(err) + } + + // 使用文档内容 + for _, doc := range docs { + println(doc.Content) + // 访问元数据 + fileName := doc.MetaData[fileloader.MetaKeyFileName] + extension := doc.MetaData[fileloader.MetaKeyExtension] + source := doc.MetaData[fileloader.MetaKeySource] + } +} +``` + +## **相关文档** + +- [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide) +- [Eino: Document Parser 接口使用说明](/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide) diff --git a/content/zh/docs/eino/ecosystem_integration/document/loader_web_url.md b/content/zh/docs/eino/ecosystem_integration/document/loader_web_url.md new file mode 100644 index 0000000000..648160f8ca --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/document/loader_web_url.md @@ -0,0 +1,156 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Loader - web url +weight: 0 +--- + +## **基本介绍** + +URL 文档加载器是 Document Loader 接口的一个实现,用于从网络 URL 中加载文档内容。该组件实现了 [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide)。 + +### **特性介绍** + +URL 文档加载器具有以下特点: + +- 默认支持 HTML 网页内容的解析 +- 可自定义 HTTP 客户端配置 (eg: 自定义代理等) +- 支持自定义内容解析器 (eg: body、或其他特定的容器) + +## **使用方式** + +### **组件初始化** + +URL 文档加载器通过 `NewLoader` 函数进行初始化,主要配置参数如下: + +```go +loader, err := NewLoader(ctx, &LoaderConfig{ + Parser: parser, // 可选:自定义解析器,默认使用 HTML 解析器 + Client: httpClient, // 可选:自定义 HTTP 客户端 + RequestBuilder: requestBuilder, // 可选:自定义请求构建器 +}) +``` + +配置参数说明: + +- `Parser`:文档解析器,默认使用 HTML 解析器,会提取网页正文内容 +- `Client`:HTTP 客户端,可以自定义超时、代理等配置 +- `RequestBuilder`:请求构建器,用于自定义请求方法、请求头等 + +### **加载文档** + +文档加载通过 `Load` 方法实现: + +```go +docs, err := loader.Load(ctx, document.Source{ + URI: "https://example.com/document", +}) +``` + +注意事项: + +- URI 必须是有效的 HTTP/HTTPS URL +- 默认使用 GET 方法请求 +- 如果需要其他 HTTP 方法或自定义请求头,需要配置 RequestBuilder,例如 鉴权 场景 + +### **完整使用示例** + +#### **基本使用** + +```go +package main + +import ( + "context" + + urlloader "github.com/cloudwego/eino-ext/components/document/loader/url" + "github.com/cloudwego/eino/components/document" +) + +func main() { + ctx := context.Background() + + // 使用默认配置初始化加载器 + loader, err := urlloader.NewLoader(ctx, nil) + if err != nil { + panic(err) + } + + // 加载文档 + docs, err := loader.Load(ctx, document.Source{ + URI: "https://example.com/article", + }) + if err != nil { + panic(err) + } + + // 使用文档内容 + for _, doc := range docs { + println(doc.Content) + } +} +``` + +#### **自定义配置示例** + +```go +package main + +import ( + "context" + "net/http" + "time" + + urlloader "github.com/cloudwego/eino-ext/components/document/loader/url" + "github.com/cloudwego/eino/components/document" +) + +func main() { + ctx := context.Background() + + // 自定义 HTTP 客户端 + client := &http.Client{ + Timeout: 10 * time.Second, + } + + // 自定义请求构建器 + requestBuilder := func(ctx context.Context, src document.Source, opts ...document.LoaderOption) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, "GET", src.URI, nil) + if err != nil { + return nil, err + } + // 添加自定义请求头 + req.Header.Add("User-Agent", "MyBot/1.0") + return req, nil + } + + // 初始化加载器 + loader, err := urlloader.NewLoader(ctx, &urlloader.LoaderConfig{ + Client: client, + RequestBuilder: requestBuilder, + }) + if err != nil { + panic(err) + } + + // 加载文档 + docs, err := loader.Load(ctx, document.Source{ + URI: "https://example.com/article", + }) + if err != nil { + panic(err) + } + + // 使用文档内容 + for _, doc := range docs { + println(doc.Content) + } +} +``` + +## **相关文档** + +- [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide) +- [Eino: Document Parser 接口使用说明](/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide) diff --git a/content/zh/docs/eino/ecosystem_integration/document/parser_html.md b/content/zh/docs/eino/ecosystem_integration/document/parser_html.md new file mode 100644 index 0000000000..d2a875e707 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/document/parser_html.md @@ -0,0 +1,149 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Parser - html +weight: 0 +--- + +## **基本介绍** + +HTML 文档解析器是 Document Parser 接口的一个实现,用于将 HTML 网页内容解析为纯文本。该组件实现了 [Eino: Document Parser 接口使用说明](/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide),主要用于以下场景: + +- 需要从网页中提取纯文本内容 +- 需要获取网页的元数据(标题、描述等) + +### **特性介绍** + +HTML 解析器具有以下特点: + +- 支持选择性提取页面内容,灵活的内容选择器配置 (html selector) +- 自动提取网页元数据 (metadata) +- 安全的 HTML 解析 + +## **使用方式** + +### **组件初始化** + +HTML 解析器通过 `NewParser` 函数进行初始化,主要配置参数如下: + +```go +parser, err := NewParser(ctx, &Config{ + Selector: &selector, // 可选:内容选择器,默认为 body +}) +``` + +配置参数说明: + +- `Selector`:可选参数,指定要提取的内容区域,使用 goquery 选择器语法 + - 例如:`body` 表示提取 `` 标签内容 + - `#content` 表示提取 id 为 "content" 的元素内容 + +### **元数据说明** + +解析器会自动提取以下 metadata: + +- `html.MetaKeyTitle` ("_title"):网页标题 +- `html.MetaKeyDesc` ("_description"):网页描述 +- `html.MetaKeyLang` ("_language"):网页语言 +- `html.MetaKeyCharset` ("_charset"):字符编码 +- `html.MetaKeySource` ("_source"):文档来源 URI + +### **完整使用示例** + +#### **基本使用** + +```go +package main + +import ( + "context" + "strings" + + htmlparser "github.com/cloudwego/eino-ext/components/document/parser/html" + "github.com/cloudwego/eino/components/document/parser" +) + +func main() { + ctx := context.Background() + + // 初始化解析器 + p, err := htmlparser.NewParser(ctx, nil) // 使用默认配置 + if err != nil { + panic(err) + } + + // HTML 内容 + html := ` + + + 示例页面 + + + + +
+

欢迎

+

这是正文内容。

+
+ + + ` + + // 解析文档 + docs, err := p.Parse(ctx, strings.NewReader(html), + parser.WithURI("https://example.com"), + parser.WithExtraMeta(map[string]any{ + "custom": "value", + }), + ) + if err != nil { + panic(err) + } + + // 使用解析结果 + doc := docs[0] + println("内容:", doc.Content) + println("标题:", doc.MetaData[htmlparser.MetaKeyTitle]) + println("描述:", doc.MetaData[htmlparser.MetaKeyDesc]) + println("语言:", doc.MetaData[htmlparser.MetaKeyLang]) +} +``` + +#### **使用选择器** + +```go +package main + +import ( + "context" + + htmlparser "github.com/cloudwego/eino-ext/components/document/parser/html" +) + +func main() { + ctx := context.Background() + + // 指定只提取 id 为 content 的元素内容 + selector := "#content" + p, err := htmlparser.NewParser(ctx, &htmlparser.Config{ + Selector: &selector, + }) + if err != nil { + panic(err) + } + + // ... 解析文档的代码 ... +} +``` + +#### 在 loader 中使用 + +可参考 [Eino: Document Parser 接口使用说明](/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide) 中的示例 + +## **相关文档** + +- [Eino: Document Parser 接口使用说明](/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide) +- [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide) +- [Parser - pdf](/zh/docs/eino/ecosystem_integration/document/parser_pdf) diff --git a/content/zh/docs/eino/ecosystem_integration/document/parser_pdf.md b/content/zh/docs/eino/ecosystem_integration/document/parser_pdf.md new file mode 100644 index 0000000000..c2d85ba051 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/document/parser_pdf.md @@ -0,0 +1,120 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Parser - pdf +weight: 0 +--- + +## **基本介绍** + +PDF 文档解析器是 Document Parser 接口的一个实现,用于将 PDF 文件内容解析为纯文本。该组件实现了 [Eino: Document Parser 接口使用说明](/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide),主要用于以下场景: + +- 需要将 PDF 文档转换为可处理的纯文本格式 +- 需要按页面分割 PDF 文档内容 + +### **特性说明** + +PDF 解析器具有以下特点: + +- 支持基本的 PDF 文本提取 +- 可以选择按页面分割文档 +- 自动处理 PDF 字体和编码 +- 支持多页面 PDF 文档 + +注意事项: + +- 目前可能不能完全支持所有 PDF 格式 +- 不会保留空格和换行等格式信息 +- 复杂的 PDF 布局可能会影响提取效果 + +## **使用方式** + +### **组件初始化** + +PDF 解析器通过 `NewPDFParser` 函数进行初始化,主要配置参数如下: + +```go +parser, err := NewPDFParser(ctx, &Config{ + ToPages: true, // 是否按页面分割文档 +}) +``` + +配置参数说明: + +- `ToPages`:是否将 PDF 按页面分割成多个文档,默认为 false + +### **解析文档** + +文档解析通过 `Parse` 方法实现: + +```go +docs, err := parser.Parse(ctx, reader, opts...) +``` + +解析选项: + +- 支持通过 `parser.WithURI` 设置文档 URI +- 支持通过 `parser.WithExtraMeta` 添加额外元数据 + +### **完整使用示例** + +#### **基本使用** + +```go +package main + +import ( + "context" + "os" + + pdfparser "github.com/cloudwego/eino-ext/components/document/parser/pdf" + "github.com/cloudwego/eino/components/document/parser" +) + +func main() { + ctx := context.Background() + + // 初始化解析器 + p, err := pdfparser.NewPDFParser(ctx, &pdfparser.Config{ + ToPages: false, // 不按页面分割 + }) + if err != nil { + panic(err) + } + + // 打开 PDF 文件 + file, err := os.Open("document.pdf") + if err != nil { + panic(err) + } + defer file.Close() + + // 解析文档 + docs, err := p.Parse(ctx, file, + parser.WithURI("document.pdf"), + parser.WithExtraMeta(map[string]any{ + "source": "local", + }), + ) + if err != nil { + panic(err) + } + + // 使用解析结果 + for _, doc := range docs { + println(doc.Content) + } +} +``` + +#### 在 loader 中使用 + +可参考 [Eino: Document Parser 接口使用说明](/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide) 中的示例 + +## **相关文档** + +- [Eino: Document Parser 接口使用说明](/zh/docs/eino/core_modules/components/document_loader_guide/document_parser_interface_guide) +- [Eino: Document Loader 使用说明](/zh/docs/eino/core_modules/components/document_loader_guide) +- [Parser - pdf](/zh/docs/eino/ecosystem_integration/document/parser_pdf) diff --git a/content/zh/docs/eino/ecosystem_integration/document/splitter_markdown.md b/content/zh/docs/eino/ecosystem_integration/document/splitter_markdown.md new file mode 100644 index 0000000000..fd394388e5 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/document/splitter_markdown.md @@ -0,0 +1,130 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Splitter - markdown +weight: 0 +--- + +## **基本介绍** + +Markdown 分割器是 Document Transformer 接口的一个实现,用于根据 Markdown 文档的标题层级结构进行分割。该组件实现了 [Eino: Document Transformer 使用说明](/zh/docs/eino/core_modules/components/document_transformer_guide)。 + +### **工作原理** + +Markdown 标题分割器通过以下步骤工作: + +1. 识别文档中的 Markdown 标题(`#`、`##`、`###` 等) +2. 根据标题层级构建文档结构树 +3. 将文档按标题分割成独立片段 + +## **使用方式** + +### **组件初始化** + +Markdown 标题分割器通过 `NewHeaderSplitter` 函数进行初始化,主要配置参数如下: + +```go +splitter, err := markdown.NewHeaderSplitter(ctx, &markdown.HeaderConfig{ + Headers: map[string]string{ + "#": "h1", // 一级标题 + "##": "h2", // 二级标题 + "###": "h3", // 三级标题 + }, + TrimHeaders: false, // 是否在输出中保留标题行 +}) +``` + +配置参数说明: + +- `Headers`:必需参数,定义标题标记和对应的元数据键名映射 +- `TrimHeaders`:是否在输出的内容中移除标题行 + +### **完整使用示例** + +```go +package main + +import ( + "context" + + "github.com/cloudwego/eino-ext/components/document/transformer/splitter/markdown" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化分割器 + splitter, err := markdown.NewHeaderSplitter(ctx, &markdown.HeaderConfig{ + Headers: map[string]string{ + "#": "h1", + "##": "h2", + "###": "h3", + }, + TrimHeaders: false, + }) + if err != nil { + panic(err) + } + + // 准备要分割的文档 + docs := []*schema.Document{ + { + ID: "doc1", + Content: `# 文档标题 + +这是介绍部分的内容。 + +## 第一章 + +这是第一章的内容。 + +### 1.1 节 + +这是 1.1 节的内容。 + +## 第二章 + +这是第二章的内容。 + +\`\`\` +# 这是代码块中的注释,不会被识别为标题 +\`\`\` +`, + }, + } + + // 执行分割 + results, err := splitter.Transform(ctx, docs) + if err != nil { + panic(err) + } + + // 处理分割结果 + for i, doc := range results { + println("片段", i+1, ":", doc.Content) + println("标题层级:") + for k, v := range doc.MetaData { + if k == "h1" || k == "h2" || k == "h3" { + println(" ", k, ":", v) + } + } + } +} +``` + +## **特性说明** + +- 支持 ````` 和 `~~~` 风格的代码块 +- 自动维护标题的层级关系 + + - 新的同级标题会重置下级标题 + - 标题层级信息通过元数据传递 + +## **相关文档** + +- [Eino: Document Transformer 使用说明](/zh/docs/eino/core_modules/components/document_transformer_guide) +- [Splitter - recursive](/zh/docs/eino/ecosystem_integration/document/splitter_recursive) +- [Splitter - semantic](/zh/docs/eino/ecosystem_integration/document/splitter_semantic) diff --git a/content/zh/docs/eino/ecosystem_integration/document/splitter_recursive.md b/content/zh/docs/eino/ecosystem_integration/document/splitter_recursive.md new file mode 100644 index 0000000000..285c03d816 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/document/splitter_recursive.md @@ -0,0 +1,148 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Splitter - recursive +weight: 0 +--- + +## **基本介绍** + +递归分割器是 Document Transformer 接口的一个实现,用于将长文档按照指定大小递归地切分成更小的片段。该组件实现了 [Eino: Document Transformer 使用说明](/zh/docs/eino/core_modules/components/document_transformer_guide)。 + +### **工作原理** + +递归分割器通过以下步骤工作: + +1. 按照分隔符列表顺序尝试分割文档 +2. 如果当前分隔符无法将文档分割成小于目标大小的片段,则使用下一个分隔符 +3. 对分割后的片段进行合并,确保片段大小接近目标大小 +4. 在合并过程中保持指定大小的重叠区域 + +## **使用方式** + +### **组件初始化** + +递归分割器通过 `NewSplitter` 函数进行初始化,主要配置参数如下: + +```go +splitter, err := recursive.NewSplitter(ctx, &recursive.Config{ + ChunkSize: 1000, // 必需:目标片段大小 + OverlapSize: 200, // 可选:片段重叠大小 + Separators: []string{"\n", ".", "?", "!"}, // 可选:分隔符列表 + LenFunc: nil, // 可选:自定义长度计算函数 + KeepType: recursive.KeepTypeNone, // 可选:分隔符保留策略 +}) +``` + +配置参数说明: + +- `ChunkSize`:必需参数,指定目标片段的大小 +- `OverlapSize`:片段之间的重叠大小,用于保持上下文连贯性 +- `Separators`:分隔符列表,按优先级顺序使用 +- `LenFunc`:自定义文本长度计算函数,默认使用 `len()` +- `KeepType`:分隔符保留策略,可选值: + + - `KeepTypeNone`:不保留分隔符 + - `KeepTypeStart`:在片段开始处保留分隔符 + - `KeepTypeEnd`:在片段结尾处保留分隔符 + +### **完整使用示例** + +```go +package main + +import ( + "context" + + "github.com/cloudwego/eino-ext/components/document/transformer/splitter/recursive" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化分割器 + splitter, err := recursive.NewSplitter(ctx, &recursive.Config{ + ChunkSize: 1000, + OverlapSize: 200, + Separators: []string{"\n\n", "\n", "。", "!", "?"}, + KeepType: recursive.KeepTypeEnd, + }) + if err != nil { + panic(err) + } + + // 准备要分割的文档 + docs := []*schema.Document{ + { + ID: "doc1", + Content: `这是第一个段落,包含了一些内容。 + + 这是第二个段落。这个段落有多个句子!这些句子通过标点符号分隔。 + + 这是第三个段落。这里有更多的内容。`, + }, + } + + // 执行分割 + results, err := splitter.Transform(ctx, docs) + if err != nil { + panic(err) + } + + // 处理分割结果 + for i, doc := range results { + println("片段", i+1, ":", doc.Content) + } +} +``` + +### **高级用法** + +自定义长度计算: + +```go +splitter, err := recursive.NewSplitter(ctx, &recursive.Config{ + ChunkSize: 1000, + LenFunc: func(s string) int { + // eg: 使用 unicode 字符数而不是字节数 + return len([]rune(s)) + }, +}) +``` + +调整重叠策略: + +```go +splitter, err := recursive.NewSplitter(ctx, &recursive.Config{ + ChunkSize: 1000, + // 增大重叠区域以保持更多上下文 + OverlapSize: 300, + // 在片段结尾保留分隔符 + KeepType: recursive.KeepTypeEnd, +}) +``` + +自定义分隔符: + +```go +splitter, err := recursive.NewSplitter(ctx, &recursive.Config{ + ChunkSize: 1000, + // 按优先级排序的分隔符列表 + Separators: []string{ + "\n\n", // 空行(段落分隔) + "\n", // 换行 + "。", // 句号 + ";", // 分号 + ",", // 逗号 + }, +}) +``` + +## **相关文档** + +- [Eino: Document Transformer 使用说明](/zh/docs/eino/core_modules/components/document_transformer_guide) +- [Splitter - markdown](/zh/docs/eino/ecosystem_integration/document/splitter_markdown) +- [Splitter - semantic](/zh/docs/eino/ecosystem_integration/document/splitter_semantic) diff --git a/content/zh/docs/eino/ecosystem_integration/document/splitter_semantic.md b/content/zh/docs/eino/ecosystem_integration/document/splitter_semantic.md new file mode 100644 index 0000000000..85b2d1706f --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/document/splitter_semantic.md @@ -0,0 +1,147 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Splitter - semantic +weight: 0 +--- + +## **基本介绍** + +语义分割器是 Document Transformer 接口的一个实现,用于基于语义相似度将长文档切分成更小的片段。该组件实现了 [Eino: Document Transformer 使用说明](/zh/docs/eino/core_modules/components/document_transformer_guide)。 + +### **工作原理** + +语义分割器通过以下步骤工作: + +1. 首先使用基本分隔符(如换行符、句号等)将文档分割成始片段 +2. 使用向量嵌入模型为每个片段生成语义向量 +3. 计算相邻片段之间的余弦相似度 +4. 根据相似度阈值(由百分位数控制)决定是否在两个片段之间进行分割 +5. 对小于最小大小的片段进行合并 + +## **使用方式** + +### **组件初始化** + +语义分割器通过 `NewSplitter` 函数进行初始化,主要配置参数如下: + +```go +splitter, err := semantic.NewSplitter(ctx, &semantic.Config{ + Embedding: embedder, // 必需:用于生成文本向量的嵌入器 + BufferSize: 2, // 可选:上下文缓冲区大小 + MinChunkSize: 100, // 可选:最小片段大小 + Separators: []string{"\n", ".", "?", "!"}, // 可选:分隔符列表 + Percentile: 0.9, // 可选:分割阈值百分位数 + LenFunc: nil, // 可选:自定义长度计算函数 +}) +``` + +配置参数说明: + +- `Embedding`:必需参数,用于生成文本向量的嵌入器实例 +- `BufferSize`:上下文缓冲区大小,用于在计算语义相似度时包含更多上下文信息 +- `MinChunkSize`:最小片段大小,小于此大小的片段会被合并 +- `Separators`:用于初始分割的分隔符列表,按顺序使用 +- `Percentile`:分割阈值的百分位数,范围 0-1,越大分割越少 +- `LenFunc`:自定义文本长度计算函数,默认使用 `len()` + +### **完整使用示例** + +```go +package main + +import ( + "context" + + "github.com/cloudwego/eino-ext/components/document/transformer/splitter/semantic" + "github.com/cloudwego/eino/components/embedding" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化嵌入器(示例使用) + embedder := &embedding.SomeEmbeddingImpl{} // eg: openai embedding + + // 初始化分割器 + splitter, err := semantic.NewSplitter(ctx, &semantic.Config{ + Embedding: embedder, + BufferSize: 2, + MinChunkSize: 100, + Separators: []string{"\n", ".", "?", "!"}, + Percentile: 0.9, + }) + if err != nil { + panic(err) + } + + // 准备要分割的文档 + docs := []*schema.Document{ + { + ID: "doc1", + Content: `这是第一段内容,包含了一些重要信息。 + 这是第二段内容,与第一段语义相关。 + 这是第三段内容,主题已经改变。 + 这是第四段内容,继续讨论新主题。`, + }, + } + + // 执行分割 + results, err := splitter.Transform(ctx, docs) + if err != nil { + panic(err) + } + + // 处理分割结果 + for i, doc := range results { + println("片段", i+1, ":", doc.Content) + } +} +``` + +### **高级用法** + +自定义长度计算: + +```go +splitter, err := semantic.NewSplitter(ctx, &semantic.Config{ + Embedding: embedder, + LenFunc: func(s string) int { + // 使用 unicode 字符数而不是字节数 + return len([]rune(s)) + }, +}) +``` + +调整分割粒度: + +```go +splitter, err := semantic.NewSplitter(ctx, &semantic.Config{ + Embedding: embedder, + // 增大百分位数,减少分割点 + Percentile: 0.95, + // 增大最小片段大小,避免过小的片段 + MinChunkSize: 200, +}) +``` + +优化语义判断: + +```go +splitter, err := semantic.NewSplitter(ctx, &semantic.Config{ + Embedding: embedder, + // 增大缓冲区大小,可包含更多上下文 + BufferSize: 10, + // 自定义分隔符优先级 + Separators: []string{"\n\n", "\n", "。", "!", "?", ","}, +}) +``` + +## **相关文档** + +- [Eino: Document Transformer 使用说明](/zh/docs/eino/core_modules/components/document_transformer_guide) +- [Splitter - recursive](/zh/docs/eino/ecosystem_integration/document/splitter_recursive) +- [Splitter - markdown](/zh/docs/eino/ecosystem_integration/document/splitter_markdown) diff --git a/content/zh/docs/eino/ecosystem_integration/embedding/_index.md b/content/zh/docs/eino/ecosystem_integration/embedding/_index.md new file mode 100644 index 0000000000..cf069862db --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/embedding/_index.md @@ -0,0 +1,10 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: Embedding +weight: 0 +--- + + diff --git a/content/zh/docs/eino/ecosystem_integration/embedding/embedding_ark.md b/content/zh/docs/eino/ecosystem_integration/embedding/embedding_ark.md new file mode 100644 index 0000000000..3b6e456e32 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/embedding/embedding_ark.md @@ -0,0 +1,102 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Embedding - ARK +weight: 0 +--- + +## **基本介绍** + +Ark Embedding 是 Eino Embedding 接口的一个实现,用于将文本转换为向量表示,火山引擎 Ark 是一个提供机器学习模型推理服务的平台,其中包含了文本向量化服务。该组件实现了 [[🚧]Eino: Embedding 使用说明](/zh/docs/eino/core_modules/components/embedding_guide)。 + +## **使用方式** + +### **组件初始化** + +Ark 向量嵌入器通过 `NewEmbedder` 函数进行初始化,主要配置参数如下: + +```go +embedder, err := NewEmbedder(ctx, &EmbeddingConfig{ + // 认证配置(二选一) + APIKey: "your-api-key", // 使用 API Key 认证 + // 或使用 AK/SK 认证 + AccessKey: "your-access-key", + SecretKey: "your-secret-key", + + // 服务配置 + Model: "ep-xxxxxxx-xxxxx", // Ark 平台的端点 ID + BaseURL: "https://ark.cn-beijing.volces.com/api/v3", // 可选,默认为北京区域 + Region: "cn-beijing", // 可选,默认为北京区域 + + // 高级配置 + Timeout: &timeout, // 请求超时时间 + RetryTimes: &retryTimes, // 重试次数 + Dimensions: &dimensions, // 输出向量维度 + User: &user, // 用户标识 +}) +``` + +### **生成向量嵌入** + +文本向量化通过 `EmbedStrings` 方法实现: + +```go +embeddings, err := embedder.EmbedStrings(ctx, []string{ + "第一段文本", + "第二段文本", +}) +``` + +### **完整使用示例** + +#### **基本使用** + +```go +package main + +import ( + "context" + "time" + + "github.com/cloudwego/eino-ext/components/embedding/ark" +) + +func main() { + ctx := context.Background() + + // 初始化嵌入器 + timeout := 30 * time.Second + embedder, err := ark.NewEmbedder(ctx, &ark.EmbeddingConfig{ + APIKey: "your-api-key", + Model: "ep-20xxxxxxx-xxxxx", + Timeout: &timeout, + }) + if err != nil { + panic(err) + } + + // 生成文本向量 + texts := []string{ + "这是第一段示例文本", + "这是第二段示例文本", + } + + embeddings, err := embedder.EmbedStrings(ctx, texts) + if err != nil { + panic(err) + } + + // 使用生成的向量 + for i, embedding := range embeddings { + println("文本", i+1, "的向量维度:", len(embedding)) + } +} +``` + +## **相关文档** + +- [Eino: Embedding 使用说明](/zh/docs/eino/core_modules/components/embedding_guide) +- [Embedding - OpenAI](/zh/docs/eino/ecosystem_integration/embedding/embedding_openai) +- [火山引擎 Ark 服务](https://www.volcengine.com/product/ark) diff --git a/content/zh/docs/eino/ecosystem_integration/embedding/embedding_openai.md b/content/zh/docs/eino/ecosystem_integration/embedding/embedding_openai.md new file mode 100644 index 0000000000..7b5d37db57 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/embedding/embedding_openai.md @@ -0,0 +1,104 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Embedding - OpenAI +weight: 0 +--- + +## **基本介绍** + +OpenAI 向量嵌入器是 Eino Embedding 接口的一个实现,用于将文本转换为向量表示。该组件实现了 [[🚧]Eino: Embedding 使用说明](/zh/docs/eino/core_modules/components/embedding_guide),主要用于以下场景: + +- 需要将文本转换为高维向量表示 +- 使用 OpenAI 的 embedding 模型 +- 使用 Azure OpenAI Service 的 embedding 模型 + +## **使用方式** + +### **组件初始化** + +OpenAI 向量嵌入器通过 `NewEmbedder` 函数进行初始化,主要配置参数如下: + +```go +embedder, err := NewEmbedder(ctx, &EmbeddingConfig{ + // OpenAI API 配置 + APIKey: "your-api-key", + Model: "text-embedding-ada-002", + Timeout: 30 * time.Second, + + // 可选:Azure OpenAI Service 配置 + ByAzure: true, + BaseURL: "https://your-resource.openai.azure.com", + APIVersion: "2023-05-15", + + // 可选:高级配置 + EncodingFormat: &format, // 编码格式 + Dimensions: &dimension, // 向量维度 + User: &user, // 用户标识 +}) +``` + +### **生成向量嵌入** + +文本向量化通过 `EmbedStrings` 方法实现: + +```go +embeddings, err := embedder.EmbedStrings(ctx, []string{ + "第一段文本", + "第二段文本", +}) +``` + +### **完整使用示例** + +#### **基本使用** + +```go +package main + +import ( + "context" + "time" + + "github.com/cloudwego/eino-ext/components/embedding/openai" +) + +func main() { + ctx := context.Background() + + // 初始化嵌入器 + embedder, err := openai.NewEmbedder(ctx, &openai.EmbeddingConfig{ + APIKey: "your-api-key", + Model: "text-embedding-ada-002", + Timeout: 30 * time.Second, + }) + if err != nil { + panic(err) + } + + // 生成文本向量 + texts := []string{ + "这是第一段示例文本", + "这是第二段示例文本", + } + + embeddings, err := embedder.EmbedStrings(ctx, texts) + if err != nil { + panic(err) + } + + // 使用生成的向量 + for i, embedding := range embeddings { + println("文本", i+1, "的向量维度:", len(embedding)) + } +} +``` + +## **相关文档** + +- [Eino: Embedding 使用说明](/zh/docs/eino/core_modules/components/embedding_guide) +- [Embedding - ARK](/zh/docs/eino/ecosystem_integration/embedding/embedding_ark) +- [OpenAI Embedding API 文档](https://platform.openai.com/docs/guides/embeddings) +- [Azure OpenAI Service 文档](https://learn.microsoft.com/azure/cognitive-services/openai/) diff --git a/content/zh/docs/eino/ecosystem_integration/indexer_volc_vikingdb.md b/content/zh/docs/eino/ecosystem_integration/indexer_volc_vikingdb.md new file mode 100644 index 0000000000..0c89a0351d --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/indexer_volc_vikingdb.md @@ -0,0 +1,177 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Indexer - volc VikingDB +weight: 0 +--- + +## **基本介绍** + +火山引擎 VikingDB 向量索引器是 Indexer 接口的一个实现,用于将文档内容存储到火山引擎的 VikingDB 向量数据库中。该组件实现了 [[🚧]Eino: Indexer 使用说明](/zh/docs/eino/core_modules/components/indexer_guide) + +### **火山引擎 VikingDB 服务介绍** + +火山引擎 VikingDB 是一个高性能的向量数据库服务,提供向量存储、检索和向量化等功能。本组件通过火山引擎 SDK 与服务进行交互,支持两种向量化方式: + +- 使用 VikingDB 内置的向量化方法(Embedding V2) +- 使用自定义的向量嵌入模型 + +## **使用方式** + +### **组件初始化** + +火山引擎 VikingDB 索引器通过 `NewIndexer` 函数进行初始化,主要配置参数如下: + +```go +indexer, err := NewIndexer(ctx, &IndexerConfig{ + Host: "api.volcengineapi.com", // 服务地址 + Region: "cn-beijing", // 区域 + AK: "your-ak", // Access Key + SK: "your-sk", // Secret Key + Scheme: "https", // 协议 + ConnectionTimeout: 30, // 连接超时时间(秒) + + Collection: "your-collection", // 集合名称 + + EmbeddingConfig: EmbeddingConfig{ + UseBuiltin: true, // 是否使用内置向量化 + ModelName: "text2vec-base", // 模型名称 + UseSparse: true, // 是否使用稀疏向量 + Embedding: embedder, // 自定义向量嵌入器 + }, + + AddBatchSize: 5, // 批量添加大小 +}) +``` + +### **完整使用示例** + +#### **使用内置向量化** + +```go +package main + +import ( + "context" + + volcvikingdb "github.com/cloudwego/eino-ext/components/indexer/volc_vikingdb" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化索引器 + idx, err := volcvikingdb.NewIndexer(ctx, &volcvikingdb.IndexerConfig{ + Host: "api.volcengineapi.com", + Region: "cn-beijing", + AK: "your-ak", + SK: "your-sk", + Scheme: "https", + + Collection: "test-collection", + + EmbeddingConfig: volcvikingdb.EmbeddingConfig{ + UseBuiltin: true, + ModelName: "text2vec-base", + UseSparse: true, + }, + }) + if err != nil { + panic(err) + } + + // 准备文档 + docs := []*schema.Document{ + { + Content: "这是第一个文档的内容", + }, + { + Content: "这是第二个文档的内容", + }, + } + + // 存储文档 + ids, err := idx.Store(ctx, docs) + if err != nil { + panic(err) + } + + // 处理返回的ID + for i, id := range ids { + println("文档", i+1, "的存储ID:", id) + } +} +``` + +#### **使用自定义向量嵌入** + +```go +package main + +import ( + "context" + + volcvikingdb "github.com/cloudwego/eino-ext/components/indexer/volc_vikingdb" + "github.com/cloudwego/eino/components/embedding" + "github.com/cloudwego/eino/schema" +) + +func main() { + ctx := context.Background() + + // 初始化向量嵌入器(openai 示例) + embedder, err := &openai.NewEmbedder(ctx, &openai.EmbeddingConfig{}) + if err != nil { + panic(err) + } + + // 初始化索引器 + idx, err := volcvikingdb.NewIndexer(ctx, &volcvikingdb.IndexerConfig{ + Host: "api.volcengineapi.com", + Region: "cn-beijing", + AK: "your-ak", + SK: "your-sk", + Scheme: "https", + + Collection: "test-collection", + + EmbeddingConfig: volcvikingdb.EmbeddingConfig{ + UseBuiltin: false, + Embedding: embedder, + }, + }) + if err != nil { + panic(err) + } + + // 准备文档 + docs := []*schema.Document{ + { + Content: "Document content one", + }, + { + Content: "Document content two", + }, + } + + // 存储文档 + ids, err := idx.Store(ctx, docs) + if err != nil { + panic(err) + } + + // 处理返回的ID + for i, id := range ids { + println("文档", i+1, "的存储ID:", id) + } +} +``` + +## **相关文档** + +- [Eino: Indexer 使用说明](/zh/docs/eino/core_modules/components/indexer_guide) +- [Eino: Retriever 使用说明](/zh/docs/eino/core_modules/components/retriever_guide) +- [火山引擎 VikingDB 使用指南](https://www.volcengine.com/docs/84313/1254617) diff --git a/content/zh/docs/eino/ecosystem_integration/retriever_volc_vikingdb.md b/content/zh/docs/eino/ecosystem_integration/retriever_volc_vikingdb.md new file mode 100644 index 0000000000..d46b7010b2 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/retriever_volc_vikingdb.md @@ -0,0 +1,170 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Retriever - volc VikingDB +weight: 0 +--- + +## **基本介绍** + +火山引擎 VikingDB 检索器是 Retriever 接口的一个实现,火山引擎 VikingDB 是火山引擎提供的向量数据库服务,提供了高性能的向量检索能力,本组件通过火山引擎 VikingDB Go SDK 与服务进行交互。该组件实现了 [[🚧]Eino: Retriever 使用说明](/zh/docs/eino/core_modules/components/retriever_guide) + +## **使用方式** + +### **组件初始化** + +火山引擎 VikingDB 检索器通过 `NewRetriever` 函数进行初始化,主要配置参数如下: + +```go +retriever, err := NewRetriever(ctx, &RetrieverConfig{ + // 服务配置 + Host: "api.volcengineapi.com", // 服务地址 + Region: "cn-beijing", // 区域 + AK: "your-ak", // 访问密钥 ID + SK: "your-sk", // 访问密钥密码 + Scheme: "https", // 协议 + ConnectionTimeout: 30, // 连接超时时间(秒) + + // 数据配置 + Collection: "collection-name", // 集合名称 + Index: "index-name", // 索引名称 + + // 向量化配置 + EmbeddingConfig: EmbeddingConfig{ + UseBuiltin: true, // 是否使用内置向量化 + ModelName: "model-name",// 模型名称 + UseSparse: true, // 是否使用稀疏向量 + DenseWeight: 0.5, // 稠密向量权重 + Embedding: embedder, // 自定义向量化器 + }, + + // 检索配置 + Partition: "partition", // 分区名称 + TopK: ptrOf(100), // 返回结果数量 + ScoreThreshold: ptrOf(0.7), // 相似度阈值 + + // 过滤配置 + FilterDSL: map[string]any{ // DSL 过滤条件 + "term": map[string]any{ + "field": "value", + }, + }, +}) +``` + +### **检索文档** + +文档检索通过 `Retrieve` 方法实现: + +```go +docs, err := retriever.Retrieve(ctx, "查询文本", retriever.WithTopK(5)) +``` + +### **完整使用示例** + +#### **基本检索** + +```go +package main + +import ( + "context" + + "github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb" +) + +func main() { + ctx := context.Background() + + // 初始化检索器 + r, err := volc_vikingdb.NewRetriever(ctx, &volc_vikingdb.RetrieverConfig{ + Host: "api.volcengineapi.com", + Region: "cn-beijing", + AK: "your-ak", + SK: "your-sk", + Collection: "your-collection", + Index: "your-index", + EmbeddingConfig: volc_vikingdb.EmbeddingConfig{ + UseBuiltin: true, + ModelName: "model-name", + UseSparse: true, + DenseWeight: 0.5, + }, + TopK: ptrOf(5), + }) + if err != nil { + panic(err) + } + + // 执行检索 + docs, err := r.Retrieve(ctx, "如何使用 VikingDB?") + if err != nil { + panic(err) + } + + // 处理结果 + for _, doc := range docs { + println("文档ID:", doc.ID) + println("内容:", doc.Content) + println("相似度:", doc.MetaData["_score"]) + } +} +``` + +#### **自定义向量化** + +```go +package main + +import ( + "context" + + "github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb" + "github.com/cloudwego/eino/components/embedding" +) + +func main() { + ctx := context.Background() + + // 初始化向量化器 (以 openai 为例) + embedder, err := &openai.NewEmbedder(ctx, &openai.EmbeddingConfig{}) + if err != nil { + panic(err) + } + + // 初始化检索器 + r, err := volc_vikingdb.NewRetriever(ctx, &volc_vikingdb.RetrieverConfig{ + Host: "api.volcengineapi.com", + Region: "cn-beijing", + AK: "your-ak", + SK: "your-sk", + Collection: "your-collection", + Index: "your-index", + EmbeddingConfig: volc_vikingdb.EmbeddingConfig{ + UseBuiltin: false, + Embedding: embedder, + }, + }) + if err != nil { + panic(err) + } + + // 执行检索 + docs, err := r.Retrieve(ctx, "查询文本") + if err != nil { + panic(err) + } + + // 处理结果 + for _, doc := range docs { + println(doc.Content) + } +} +``` + +## **相关文档** + +- [Eino: Retriever 使用说明](/zh/docs/eino/core_modules/components/retriever_guide) +- [火山引擎 VikingDB 文档](https://www.volcengine.com/docs/84313) diff --git a/content/zh/docs/eino/ecosystem_integration/tool/_index.md b/content/zh/docs/eino/ecosystem_integration/tool/_index.md new file mode 100644 index 0000000000..55c287a754 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/tool/_index.md @@ -0,0 +1,10 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Tool +weight: 0 +--- + + diff --git a/content/zh/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search.md b/content/zh/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search.md new file mode 100644 index 0000000000..baa7cde1a6 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search.md @@ -0,0 +1,109 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: Tool - DuckDuckGoSearch +weight: 0 +--- + +## **基本介绍** + +DuckDuckGo 搜索工具是 Tool InvokableTool 接口的一个实现,用于通过 DuckDuckGo 搜索引擎进行网络搜索,DuckDuckGo 是一个注重隐私的搜索引擎,不会追踪用户的搜索行为,重点是无需 api key 鉴权即可直接使用。该组件实现了 [Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide) + +## **使用方式** + +### **组件初始化** + +DuckDuckGo 搜索工具通过 `NewTool` 函数进行初始化,主要配置参数如下: + +```go +tool, err := NewTool(ctx, &Config{ + ToolName: "duckduckgo_search", // 工具名称 + ToolDesc: "search web for information by duckduckgo", // 工具描述 + Region: ddgsearch.RegionWT, // 搜索地区 + MaxResults: 10, // 每页结果数量 + SafeSearch: ddgsearch.SafeSearchOff, // 安全搜索级别 + TimeRange: ddgsearch.TimeRangeAll, // 时间范围 + DDGConfig: &ddgsearch.Config{}, // DuckDuckGo 配置 +}) +``` + +### **搜索参数** + +搜索请求支持以下参数: + +```go +type SearchRequest struct { + Query string `json:"query"` // 搜索关键词 + Page int `json:"page"` // 页码 +} +``` + +### **完整使用示例** + +```go +package main + +import ( + "context" + + "github.com/cloudwego/eino-ext/components/tool/duckduckgo" + "github.com/cloudwego/eino-ext/components/tool/duckduckgo/ddgsearch" +) + +func main() { + ctx := context.Background() + + // 初始化搜索工具 + searchTool, err := duckduckgo.NewTool(ctx, &duckduckgo.Config{ + Region: ddgsearch.RegionWT, + MaxResults: 10, + SafeSearch: ddgsearch.SafeSearchOff, + TimeRange: ddgsearch.TimeRangeAll, + }) + if err != nil { + panic(err) + } + + // 准备搜索请求 + request := &SearchRequest{ + Query: "Golang concurrent programming", + Page: 1, + } + + // 执行搜索 + result, err := searchTool.Search(ctx, request) + if err != nil { + panic(err) + } + + // 处理搜索结果 + println(result) // JSON 格式的搜索结果 +} +``` + +### **搜索结果示例** + +```json +{ + "results": [ + { + "title": "Go 并发编程实践", + "description": "这是一篇关于 Go 语言并发编程的文章...", + "link": "https://example.com/article1" + }, + { + "title": "Understanding Concurrency in Go", + "description": "A comprehensive guide to concurrent programming...", + "link": "https://example.com/article2" + } + ] +} +``` + +## **相关文档** + +- [Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide) +- [Tool - Googlesearch](/zh/docs/eino/ecosystem_integration/tool/tool_googlesearch) +- [DuckDuckGo 帮助文档](https://duckduckgo.com/duckduckgo-help-pages/settings/params/) diff --git a/content/zh/docs/eino/ecosystem_integration/tool/tool_googlesearch.md b/content/zh/docs/eino/ecosystem_integration/tool/tool_googlesearch.md new file mode 100644 index 0000000000..3954fc6fa4 --- /dev/null +++ b/content/zh/docs/eino/ecosystem_integration/tool/tool_googlesearch.md @@ -0,0 +1,115 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Tool - Googlesearch +weight: 0 +--- + +## **基本介绍** + +Google 搜索工具是 Eino InvokableTool 接口的一个实现,用于通过 Google Custom Search API 进行网络搜索。该组件实现了 [Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide)。 + +## **使用方式** + +### **组件初始化** + +Google 搜索工具通过 `NewGoogleSearchTool` 函数进行初始化,主要配置参数如下: + +```go +tool, err := NewGoogleSearchTool(ctx, &Config{ + APIKey: "your-api-key", // Google API 密钥 + SearchEngineID: "your-engine-id", // 搜索引擎 ID + BaseURL: "custom-base-url", // 可选:自定义 API 基础 URL, default: https://customsearch.googleapis.com + Num: 5, // 可选:每页结果数量 + Lang: "zh-CN", // 可选:搜索界面语言 + ToolName: "google_search", // 可选:工具名称 + ToolDesc: "google search tool", // 可选:工具描述 +}) +``` + +### **搜索参数** + +搜索请求支持以下参数: + +```go +type SearchRequest struct { + Query string `json:"query"` // 搜索关键词 + Num int `json:"num"` // 返回结果数量 + Offset int `json:"offset"` // 结果起始位置 + Lang string `json:"lang"` // 搜索语言 +} +``` + +### **完整使用示例** + +```go +package main + +import ( + "context" + + "github.com/cloudwego/eino-ext/components/tool/googlesearch" + "github.com/cloudwego/eino/components/tool" +) + +func main() { + ctx := context.Background() + + // 初始化搜索工具 + searchTool, err := googlesearch.NewGoogleSearchTool(ctx, &googlesearch.Config{ + APIKey: "your-api-key", + SearchEngineID: "your-engine-id", + Lang: "zh-CN", + Num: 5, + }) + if err != nil { + panic(err) + } + + // 准备搜索请求 + request := map[string]any{ + "query": "Golang concurrent programming", + "num": 3, + "lang": "en", + } + + // 执行搜索 + result, err := searchTool.Invoke(ctx, request) + if err != nil { + panic(err) + } + + // 处理搜索结果 + println(result) // JSON 格式的搜索结果 +} +``` + +### **搜索结果示例** + +```json +{ + "query": "Golang concurrent programming", + "items": [ + { + "link": "https://example.com/article1", + "title": "Go 并发编程实践", + "snippet": "这是一篇关于 Go 语言并发编程的文章...", + "desc": "详细介绍了 Go 语言中的 goroutine 和 channel..." + }, + { + "link": "https://example.com/article2", + "title": "Understanding Concurrency in Go", + "snippet": "A comprehensive guide to concurrent programming...", + "desc": "Learn about Go's concurrency model and best practices..." + } + ] +} +``` + +## **相关文档** + +- [Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide) +- [Google Custom Search API 文档](https://developers.google.com/custom-search/v1/overview) +- [Tool - DuckDuckGoSearch](/zh/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search) diff --git a/content/zh/docs/eino/overview/_index.md b/content/zh/docs/eino/overview/_index.md new file mode 100644 index 0000000000..8eb13b045a --- /dev/null +++ b/content/zh/docs/eino/overview/_index.md @@ -0,0 +1,416 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 'Eino: 概述' +weight: 1 +--- + +# 简介 + +**Eino['aino]** (近似音: i know,希望应用程序达到 "i know" 的愿景) 旨在提供 Golang 语言的 AI 应用开发框架。 Eino 参考了开源社区中诸多优秀的 AI 应用开发框架,例如 LangChain、LangGraph、LlamaIndex 等,提供了更符合 Golang 编程习惯的 AI 应用开发框架。 + +Eino 提供了丰富的辅助 AI 应用开发的原子组件、集成组件、组件编排、切面扩展等能力,可以帮助开发者更加简单便捷地开发出架构清晰、易维护、高可用的 AI 应用。 + +Eino 可在 AI 应用开发周期中的不同阶段,规范、简化和提效: + +![](/img/eino/eino_structure_modules.png) + +> + +# 主要特性 + +## 丰富的组件(Component) + +- 将多场景普遍使用的能力,抽象成可独立、可编排使用的组件,开箱即用 + + - 例如 ChatModel、PromptTemplate、Retriever、Loader 等原子组件 + - 每一种 Component 均有接口抽象及其对应的多种实现 + - 编排时,对应节点的输入输出与接口抽象保持一致 +- 组件又可细分为:功能不可细拆的原子组件、由一到多中组件以某种范式组合而成的集成组件 + + - 集成组件:React Agent、MultiQueryRetriver、Host MultiAgent 等 + +## 易用的图编排(Graph/Chain) + +- 将各组件实例,作为图的节点,以图的点边关系连接,以边的方向逐步执行节点并传输数据流,将 AI 应用的逻辑以图的方式进行编排和执行。 +- 图编排可极大简化 **并行、流式(异步)** 逻辑的开发,并优化其代码结构 + +使用“数据处理工厂”来比喻应用程序的构建。图编排将应用定义为:独立“黑箱(节点)”的网络,这些“黑箱”通过数据包在预定义的连接上进行通信。“黑箱”可以在不同场景被连接,形成不同的应用程序,而无需在内部进行更改。这里一个“黑箱”代表一种 Component,因此 Graph 编排具有 Component 导向的特性。 + +**图编排**提供一套简洁的「绘画」设计语义,通过添加 **点(计算逻辑)** 和 **线(数据流)**,让开发者能够通过形象化的“作图”,将 Component 中提供的各个基础组件编排为草稿(Draft),进而可以选择按需复用或是编译为一个 LLM 应用。 简而言之:N_odes do the work,Edges tell what to do next。_ + +**图编排**中所涉及的元素和概念可以用:点、线、面、切面 四个词进行概括。 + +图编排在 Eino 中表现为:Graph、Chain + +## 完善的流处理(Streaming) + +在类似 LLM 构建的长耗时应用的场景下,**流处理**在让终端用户觉得系统在实时响应方面至关重要 + +根据**输入、输出是否为流式**,可划分成 4 种交互模式。而不同的组件则仅能提供这 4 种交互模式中的一种或多种(绝大部分是 Invoke 能力,少数是 Stream 能力, 极少数是 Transform 能力),如何将具有不同交互模式的组件,通过图编排连接并进行数据流的流转,对用户来说是不可避免的困难。 + +图编排可根据上下游节点的输入、输出是否是流,自动进行 流 和 非流 的转换,使得开发者可不再关注上下游节点的流处理关系能否匹配,极大地方便开发者对 AI 应用提供流处理的能力 + +## 高扩展性的切面(Callbacks) + +Eino 像 LangChain 一样,提供了完善地回调系统,允许开发者在 AI 应用的各个阶段嵌入自己的钩子。方便开发者利用这些钩子扩展日志记录、监控、流式处理 等功能。 + +开发者可通过实现 callbacks.Handler、compose.GraphCompileCallback 等钩子对象,来订阅 AI 应用在构建、运行时各个阶段的事件。 + +站在图编排的视角来说,Eino 为图、节点的执行前后提供切面的注入、运行的机制。开发者可基于此机制,在不侵入主流程的前提下,灵活地设计和注入自己的切面能力。例如 Trace、埋点、日志等 + +# Eino 框架结构 + +![](/img/eino/eino_structure.png) + +Eino 框架整体由 三部分 构成: + +- Eino:存放 Eino 的组件抽象,Graph、Chain 等编排能力,切面机制等 +- EinoExt:Eino 的组件实现、通用切面实现、组件使用示例等,可放置各种各样的 Eino 扩展能力 +- Eino Devops:Eino 相关的开发、调试、评测等可视化、管理能力。 + +Eino Core 中的六大概念: + +- Components 抽象 + + - 每一种 Component 均有对应的接口抽象及其对应的多种实现。可直接使用、也可被编排 + - 编排时,对应节点的输入输出与接口抽象保持一致 + - 类似于 ChatModel、PromptTemplate、Retriever、Indexer 等开箱即用的原子组件 + - Eino 中 Component 概念较为宽松,任意满足如下职责中的一个,即可被称为 Component + - 可加入 Graph Node,作为编排对象被编排 + - 作为其他编排对象的依赖注入组件 +- Flow 集成组件 + + - 基于框架中的 Component、Graph ,针对常见的应用场景,提供开箱即用的预先编排好的集成组件能力。 + - 可能提供再次被编排的能力 + - 例如:Agent、MapReduce 长文本总结、MultiAgent 等 +- Runnable -- 用户弱感知 + + - 编排框架中的编排对象和编排产物。 + - 所有的 Component 在被编排时,均需转换成 Runnable 对象,一般用户不可见此过程。 + - 一张图编译成可执行对象时,本质是一个 Runnable 对象 +- Compose 编排 + + - 将各种 Component 实例,作为 Node 节点,以图的点线关系连接起来,数据流按照有向边的方向进行传输,并在不同节点中执行。 + - 支持 Graph、Chain、Workflow 等多种编排形式,本质均是通过有向图表达数据流的传递和节点的执行顺序 +- 切面能力 + + - Graph 中每个节点执行前后提供的切面能力。 + - 例如:Trace、埋点、日志等 +- Stream + + - 添加到 Node 中的组件实例,其输入、输出既有可能是 流、也有可能是 非流。 Compose 编排可以将这些不同形式的输入输出进行衔接,传递数据流并执行节点。 这个能力可称为流式编排能力 + - 例如,ChatModel 的输出、ASR 的输入输出 都是流式的 + +## Component + +具体每种 Component 的职责,可具体看对应的接口定义 + +> 下文是示例性说明,不完整,以[代码仓库](https://github.com/cloudwego/eino-ext/tree/main/components)为准 + +``` +eino/components // 组件根目录 +├── document +│   ├── interface.go +│   └── option.go +├── embedding +│   ├── callback_extra.go +│   ├── interface.go // 一个组件的抽象 +│   ├── ark // 与抽象同级的一个文件夹代表一种具体实现 +│   ├── openai +│   └── option.go +├── indexer +│   ├── callback_extra.go +│   ├── interface.go +│   ├── option.go +│   └── volc_vikingdb +├── model +│   ├── callback_extra.go +│   ├── interface.go +│   ├── ark +│   ├── openai +│   └── option.go +├── prompt +│   ├── callback_extra.go +│   ├── chat_template.go +│   ├── chat_template_test.go +│   └── interface.go +├── retriever +│   ├── callback_extra.go +│   ├── interface.go +│   ├── option.go +│   └── volc_vikingdb +├── tool +│   ├── duckduckgo +│   ├── interface.go +│   └── option.go +├── types.go +``` + +## Runnable + +```go +type Runnable[I, O any] interface { + Invoke(ctx context.Context, input I, opts ...Option) (output O, err error) + Stream(ctx context.Context, input I, opts ...Option) (output *schema.StreamReader[O], err error) + Collect(ctx context.Context, input *schema.StreamReader[I], opts ...Option) (output O, err error) + Transform(ctx context.Context, input *schema.StreamReader[I], opts ...Option) (output *schema.StreamReader[O], err error) +} +``` + +- Runnable 抽象根据输入、输出是否为流式,划分成 4 个 Lamba 算子 +- Compose 编排中,添加到 Node 中的组件实例,会被统一转换成上述的 Runnable 抽象 +- 当一个 Component 转换为 Runnable 时,根据其提供的任意 Lambda 算子,结合着 流化(Streaming)、合并(Concat) 能力,补全剩余的未提供的 Lambda 算子 + + - 流与非流间的转换: + > 用 StreamReader[T] 和 T 分别指代 流 和 非流 + > + + - 合并(Concat) + - 将 StreamReader[T] 中的 T-Frame 接收完整,并合并成一个完整的 T + - 流化(Streaming) + - 将 T 转换成仅有一个 T-Frame 的 StreamReader[T],进行流式传输 +- 基于上述两种转换关系,Eino 便可根据用户提供的具有任意 N(N<=4) 种交互模式的接口,封装转换成一个完整的 Runnable[I, O] + + + + + + + + + + + + +
源\目标
Invoke[I, O any]()
Stream[I, O any]()
Collect[I, O any]()
Transform[I, O any]()
Invoke[I, O any]()
-
- Invoke输入直接透传
- Invoke响应转成单帧流
- Invoke输入转成单帧流
- Invoke响应直接透传
- Invoke输入转成单帧流
- Invoke响应转成单帧流
Stream[I, O any]()
- Stream输入直接透传
- Stream输出Concat后透传
-
- Stream输入转成单帧流
- Stream输出Concat后透传
- Stream输入转成单帧流
- Stream输出直接透传
Collect[I, O any]()
- Collect输入Concat后透传
- Collect输出直接透传
- Collect输入Concat后透传
- Collect输出转成单帧流
-
- Collect输入直接透传
- Collect输出转成单帧流

Transform[I, O any]()
- Transform输入Concat后透传
- Transform输出Concat后透传
- Transform输入Concat后透传
- Transform输出直接透传
- Transform输入直接透传
- Transform输出Concat后透传
-
+ +- 编程产物中具有的真正的流式能力是什么,取决于如下的编排范式 + +![](/img/eino/invoke_stream_transform_collect.png) + +## Stream 流 + +Notice:Stream 流在 **生产**、**消费**、**复制**、**合并**、**转换**等场景下,处理逻辑均较为复杂。 实现时稍有考虑不周的地方,便可能导致 生产/消费者互相等待而夯死、Goroutine 泄露或溢出、内存泄露或溢出、CPU 负载高 等问题。 为了减少稳定性问题的产生,Eino 强要求使用 Eino 提供的 Stream 流,因此将 Stream 实现成了 Struct、而非定义成接口。 + +复杂的流操作的场景: + +- 流式接口和非流式接口直接的转换 + + - 流转成非流时,需要将流中的所有数据帧,Concat 成一个完整的数据结构 + - 非流转成流时,需要将一个数据结构转换成仅有一个数据帧的流 +- 同一个数据流可能需要被多次读取和消费,比如多个切面。 因一个流仅能被完成读取一次,所以需要根据消费次数,将流进行 Copy + + - 流 Copy 时,需要考虑多个流的 消费协同、Close 协同。 任意一个流没有正常 Close,均可能导致资源无法正常释放 +- 多个流合并成一个流 + +为了 Stream 的 API 接口更加清晰和易用,遂与 Golang 内置的 io.Pipe() 方法的定义对齐。 + +- API 接口定义为:`schema.Pipe[T any](cap int) (*StreamReader[T], *StreamWriter[T])` + + - 其中 cap 表示 Stream 的缓存有多大,即在无任何消费的情况下,Sender 可以无阻塞地发送 Chunk 的数量 + - `StreamWriter` 类似于 io.Pipe 中的 PipeWriter + - `StreamReader` 类似于 io.Pipe 中的 PipeReader,只是多了一个 `Copy(n int) []*StreamReader[T]` 方法 +- **WARN**:在任何地方见到 `*StreamReader[T]` 或 `*StreamWriter[T]` 都不要忘记 Close(),否则可能导致流无法正常释放。一般流的生产和消费都是单独 Goroutine,从而导致 Goroutine 的泄露。 + +Stream 流 的 API 设计,源码链接:[eino/schema/stream.go](https://github.com/cloudwego/eino/blob/main/schema/stream.go) + +## Compose 编排 + +### Graph + +#### 点(Node) + +- 把一个 Component 实例加入到 Graph 中,便形成一个 Node 节点 +- Component 即可被独立使用,又可被 Graph 编排 +- Add{Component}Node() 接口列举。 此处仅列举几个接口,更详细接口列表可查看最新的 Eino SDK + + - 对于通用的组件类型,均会抽象出一个标准行为语义,并给出不同的实现 + - 业务可通过使用 AddLambdaNode,添加任意定制函数作为节点 + +```go +// AddChatModelNode add node that implements model.ChatModel. +func (g *graph) AddChatModelNode(key string, node model.ChatModel, opts ...GraphAddNodeOpt) error { + return g.addNode(key, toChatModelNode(key, node, opts...)) +} + +// AddChatTemplateNode add node that implements prompt.ChatTemplate. +func (g *graph) AddChatTemplateNode(key string, node prompt.ChatTemplate, opts ...GraphAddNodeOpt) error { + return g.addNode(key, toChatTemplateNode(key, node, opts...)) +} + +func (g *graph) AddToolsNode(key string, node *ToolsNode, opts ...GraphAddNodeOpt) error { + return g.addNode(key, toToolsNode(key, node, opts...)) +} + +// AddLambdaNode add node that implements at least one of Invoke[I, O], Stream[I, O], Collect[I, O], Transform[I, O]. +// due to the lack of supporting method generics, we need to use function generics to generate Lambda run as Runnable[I, O]. +// for Invoke[I, O], use compose.InvokableLambda() +// for Stream[I, O], use compose.StreamableLambda() +// for Collect[I, O], use compose.CollectableLambda() +// for Transform[I, O], use compose.TransformableLambda() +// for arbitrary combinations of 4 kinds of lambda, use compose.AnyLambda() +func (g *graph) AddLambdaNode(key string, node *Lambda, opts ...GraphAddNodeOpt) error { + return g.addNode(key, toLambdaNode(key, node, opts...)) +} + +// AddGraphNode add one kind of Graph[I, O]、Chain[I, O]、StateChain[I, O, S] as a node. +// for Graph[I, O], comes from NewGraph[I, O]() +// for Chain[I, O], comes from NewChain[I, O]() +// for StateGraph[I, O, S], comes from NewStateGraph[I, O, S]() +func (g *graph) AddGraphNode(key string, node AnyGraph, opts ...GraphAddNodeOpt) error { + return g.addNode(key, toAnyGraphNode(key, node, opts...)) +} + +func (g *graph) AddRetrieverNode(key string, node retriever.Retriever, opts ...GraphAddNodeOpt) error { + return g.addNode(key, toRetrieverNode(key, node, opts...)) +} +``` + +#### 线(Edge) + +Eino 提供了多种添加线的方式 + +##### Add**Edge** + +```go +// AddEdge adds an edge to the graph, edge means a data flow from startNode to endNode. +// the previous node's output type must be set to the next node's input type. +// NOTE: startNode and endNode must have been added to the graph before adding edge. +// e.g. +// +// graph.AddNode("start_node_key", compose.NewPassthroughNode()) +// graph.AddNode("end_node_key", compose.NewPassthroughNode()) +// +// err := graph.AddEdge("start_node_key", "end_node_key") +func (g *graph) AddEdge(startNode, endNode string) (err error) {} +``` + +- 在两个节点间添加一条有向的数据传输链路,以控制数据的流动方向和节点的执行顺序 + +![](/img/eino/edge_of_parallel.png) + +##### **AddBranch** + +```go +// AddBranch adds a branch to the graph. +// e.g. +// +// condition := func(ctx context.Context, in string) (string, error) { +// return "next_node_key", nil +// } +// endNodes := map[string]bool{"path01": true, "path02": true} +// branch := compose.NewGraphBranch(condition, endNodes) +// +// graph.AddBranch("start_node_key", branch) +func (g *graph) AddBranch(startNode string, branch *GraphBranch) (err error) {} +``` + +- 根据传入的自定义选择函数,运行时根据经运算条件从多个 Node 中选出命中 Node 执行 + +![](/img/eino/run_way_branch_in_graph.png) + +##### **Parallel** + +- 将多个 Node 平行并联, 形成多个节点并发执行的节点 +- 无 AddParallel 方法,通过 AddEdge 构建并联的多条拓扑路径,以次形成 **Parallel ** + +![](/img/eino/input_keys_output_keys_in_parallel.png) + +#### 面(Graph) + +- 通过 NewGraph 创建 graph 实例,并通过 graph.AddXXXNode、graph.AddEdge、graph.AddBranch 绘制点和线,最终形成一张可编译执行的图 + +```go +// 无状态的 Graph 编排 +g := NewGraph[map[string]any, *schema.Message]() + +type testState struct { + ms []string +} + +genFn := func(ctx context.Context) *testState { + return &testState{} +} + +// 有状态的 Graph 编排 +sg := NewGraph[string, string](WithGenLocalState(genFn)) + +// 基于 Graph 编排简化 的 Chain +chain := NewChain[map[string]any, string]() +``` + +### Chain + +> Chain - 简化的 Graph,将不同类型的 Node 按照先后顺序,进行连接,形成从头到尾的数据流传递和顺序执行。 + +#### **AppendXXX** + +> XXX 可是 ChatMode、Prompt、Indexer、Retriever、Graph 等多种组件类型 +> Chain 是简化的 Graph,因此可通过 AppendGraph 实现 Chain 和 Graph 的相互嵌套 + +- 将多个 Node 按照传入顺序首尾串联,串联的 Node 依次进行数据传递和执行 + +![](/img/eino/graph_nodes.png) + +#### **AppendParallel** + +> 添加一个节点,这个节点具有多个并发执行的多个子节点 + +```go +// Parallel run multiple nodes in parallel +// +// use `NewParallel()` to create a new parallel type +// Example: + +parallel := NewParallel() +parallel.AddChatModel("output_key01", chat01) +parallel.AddChatModel("output_key01", chat02) + +chain := NewChain[any,any]() +chain.AppendParallel(parallel) +``` + +- 创建一个 Parallel,容纳并发执行的多个子节点 + +![](/img/eino/chain_append_parallel.png) + +#### **AppendBranch** + +> 添加一个节点,这个节点通过 condition 计算方法,从多个子节点中,选择一个执行 + +```go +// NewChainBranch creates a new ChainBranch instance based on a given condition. +// It takes a generic type T and a GraphBranchCondition function for that type. +// The returned ChainBranch will have an empty key2BranchNode map and a condition function +// that wraps the provided cond to handle type assertions and error checking. +// eg. + +condition := func(ctx context.Context, in string, opts ...any) (endNode string, err error) { + // logic to determine the next node + return "some_next_node_key", nil +} + +cb := NewChainBranch[string](condition) +cb.AddPassthrough("next_node_key_01", xxx) // node in branch, represent one path of branch +cb.AddPassthrough("next_node_key_02", xxx) // node in branch + +chain := NewChain[string, string]() +chain.AppendBranch(cb) +``` + +![](/img/eino/chain_append_branch.png) + +## 切面(Callbacks) + +Component(包括 Lambda)、Graph 编排共同解决“把业务逻辑定义出来”的问题。而 logging, tracing, metrics, 上屏展示等横切面性质的功能,需要有机制把功能注入到 Component(包括 Lambda)、Graph 中。 + +另一方面,用户可能想拿到某个具体 Component 实现的执行过程中的中间信息,比如 VikingDBRetriever 额外给出查询的 DB Name,ArkChatModel 额外给出请求的 temperature 等参数。需要有机制把中间状态透出。 + +Callbacks 支持“**横切面功能注入**”和“**中间状态透出**”,具体是:用户提供、注册“function”(Callback Handler),Component 和 Graph 在固定的“时机”(或者说切面、位点)回调这些 function,给出对应的信息。 + +Eino 中的 Component 和 Graph 等**实体**,在固定的**时机** (Callback Timing),回调用户提供的 **function** (Callback Handler),并把**自己是谁** (RunInfo),以及**当时发生了什么** (Callback Input & Output) 传出去。 + +详见:[Eino: Callback 用户手册](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual) diff --git a/content/zh/docs/eino/quick_start/_index.md b/content/zh/docs/eino/quick_start/_index.md new file mode 100644 index 0000000000..807a14bd55 --- /dev/null +++ b/content/zh/docs/eino/quick_start/_index.md @@ -0,0 +1,58 @@ +--- +Description: "" +date: "2025-01-06" +lastmod: "" +tags: [] +title: 'Eino: 快速开始' +weight: 2 +--- + +## 简要说明 + +Eino 提供了多种面向 AI 应用开发场景的组件抽象,同时也提供了多种实现,因此用 Eino 快速上手开发一个应用是**非常简单**的。本目录中将提供几个最常见的用 AI 搭建的应用实例,以帮助你快速地上手使用 Eino。 + +这几个小应用仅用于快速上手,对于其中的单项能力的更详细介绍及示例,可以参考 [组件介绍](/zh/docs/eino/core_modules/components)、[编排介绍](/zh/docs/eino/core_modules/chain_and_graph_orchestration/chain_graph_introduction) 等专题文档。 + +> 💡 +> Fornax Trace(Fornax 观测) 使用说明: +> Eino 的观测能力默认是基于 [Forna](https://fornax.bytedance.net/space)[x Trace](https://fornax.bytedance.net/space) 实现 (下面例子未包含观测的开启代码) + +- 当使用了 Chain/Graph 编排时,可注册全局的 Eino Callback 切面,将节点的输入输出相关信息以 Trace 形式上报。使用方式详见: [Eino: 公共切面 - Callbacks](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects)。 +- 当仅使用 Eino 组件,未使用 Eino 编排时,需要在调用组件时,手动通过 callbacks.InitCallbacks 注入 Callback Manager 信息,以开启组件切面的 Trace 上报能力(仅限于实现了组件切面的组件)。使用方式详见:[Eino: 公共切面 - Callbacks](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects) 。 + +## 快速开始示例 + +### 示例:LLM 最简应用 + +AI 的应用中,最基础的场景就是 prompt + chat model 的场景,这也是互联网上各类 AI 应用平台提供的最重要的功能。你可以定义 `System Prompt` 来约束大模型的回答逻辑,比如 “你在扮演一个 XXX 角色” 等等。这个示例中,你可以用 Eino 的 `PromptTemplate` 组件 和 `ChatModel` 组件来构建一个角色扮演应用。 + +- [实现一个最简 LLM 应用-ChatModel](/zh/docs/eino/quick_start/simple_llm_application) + +### 示例:创建一个 Agent + +大模型是 AI 的大脑,其核心是理解自然语言,并做出回应,(文本)大模型本身只能接收一段文本,然后输出一段文本。而当你希望大模型能使用一些工具自行获取所需的信息、执行一些动作,就需要使用 `Tool` 来实现了,拥有了 Tool 的大模型就像是拥有了手脚,可以和当下已有的 IT 基础设施进行交互,比如 "调用 http 接口查询天气,再根据天气提醒你今天要传什么衣服",就需要大模型调用 "search tool" 查询信息。 + +我们通常把能够根据大模型的输出调用相关 tool 的这套体系所构建出的整体,叫做 “智能体”,即 Agent。 + +在 Eino 中,你可以单独使用 ChatModel + ToolsNode 来实现 Agent,也可以使用封装好的 `react agent` 和 `multi agent`。 + +在这个示例中,我们将使用 react agent 来构建一个可以和现实世界交互的智能体。 + +- [Agent-让大模型拥有双手](/zh/docs/eino/quick_start/agent_llm_with_tools) + +### 示例:用编排构建复杂自定义应用 + +人工智能(AI) 的一项历史使命,就是把人从一些重复性的劳动中解放出来,而几乎任何一项劳动都是由多个流程和工序组合而成的,用 AI 完成这些相互串联的工作,这就是 “工作流”。由各种 AI 组件组合、编排而成的工作流,才是真正生产场景中的应用形态。 + +Eino 中,提供了以组件为第一编排对象,同时提供具有极强扩展能力的 Lambda 节点作为编排对象,能够实现快速上手和定制扩展的双优势。Eino 的编排还有一些其他特点: 编排过程中最重要的话题 “数据流” 在 Eino 中被强化,callbacks 提供了观测和调试的基础能力,call option 为运行时的扩展性提供了无限可能... + +这个示例中,我们将实现一个应用了编排能力的示例,结合 callbacks 和 call option 来实现观测和请求粒度的扩展能力。 + +- [复杂业务逻辑的利器-编排](/zh/docs/eino/quick_start/complex_business_logic_orchestration) + +## 下一步探索 + +- 理解 Eino 的核心模块和概念: [Eino: 核心模块](/zh/docs/eino/core_modules),这是你自如玩转使用 Eino 做应用开发的关键信息。 +- 我们准备了大量的 “如何做到?”: [Eino: 如何做到?](/zh/docs/eino/usage_guide/how_to_guide),你可以快速浏览一下用 Eino 做大模型应用开发时,有哪些「能力」可用?也可以在你遇到希望解决的场景时,参考我们对这个场景的理解和解决方案。 +- 我们构建了数个“开箱即用的应用示例 !”: [Eino: 开箱即用示例大全](/zh/docs/eino/usage_guide/examples_collection)。我们的理念是: 拿来即用,充分参考,先玩起来! +- Eino 保持开放生态的姿态,提供了大量生态集成组件:[Eino: 生态集成](/zh/docs/eino/ecosystem_integration),你可以使用这些组件快速构建自己的业务应用。 diff --git a/content/zh/docs/eino/quick_start/agent_llm_with_tools.md b/content/zh/docs/eino/quick_start/agent_llm_with_tools.md new file mode 100644 index 0000000000..0be8d1067f --- /dev/null +++ b/content/zh/docs/eino/quick_start/agent_llm_with_tools.md @@ -0,0 +1,256 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: Agent-让大模型拥有双手 +weight: 2 +--- + +## **Agent 是什么** + +Agent(智能代理)是一个能够感知环境并采取行动以实现特定目标的系统。在 AI 应用中,Agent 通过结合大语言模型的理解能力和预定义工具的执行能力,可以自主地完成复杂的任务。是未来 AI 应用到生活生产中主要的形态。 + +> 💡 +> 本文中示例的代码片段详见:[eino-examples/quickstart/todoagent](https://github.com/cloudwego/eino-examples/blob/master/quickstart/todoagent/main.go) + +## **Agent 的核心组成** + +在 Eino 中,要实现 Agent 主要需要两个核心部分:ChatModel 和 Tool。 + +### **ChatModel** + +ChatModel 是 Agent 的大脑,它通过强大的语言理解能力来处理用户的自然语言输入。当用户提出请求时,ChatModel 会深入理解用户的意图,分析任务需求,并决定是否需要调用特定的工具来完成任务。在需要使用工具时,它能够准确地选择合适的工具并生成正确的参数。不仅如此,ChatModel 还能将工具执行的结果转化为用户易于理解的自然语言回应,实现流畅的人机对话。 + +> 更详细的 ChatModel 的信息,可以参考: [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide) + +### **Tool** + +Tool 是 Agent 的执行器,提供了具体的功能实现。每个 Tool 都有明确的功能定义和参数规范,使 ChatModel 能够准确地调用它们。Tool 可以实现各种功能,从简单的数据操作到复杂的外部服务调用都可以封装成 Tool。 + +> 更详细关于 Tool 和 ToolsNode 的信息,可参考: [Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide) + +## **Tool 的实现方式** + +在 Eino 中,我们提供了多种方式来实现 Tool。下面通过一个待办事项(Todo)管理系统的例子来说明。 + +### **方式一:使用 NewTool 构建** + +这种方式适合简单的工具实现,通过定义工具信息和处理函数来创建 Tool: + +```go +func getAddTodoTool() tool.InvokableTool { + info := &schema.ToolInfo{ + Name: "add_todo", + Desc: "Add a todo item", + ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ + "content": { + Desc: "The content of the todo item", + Type: schema.String, + Required: true, + }, + "started_at": { + Desc: "The started time of the todo item, in unix timestamp", + Type: schema.Integer, + }, + "deadline": { + Desc: "The deadline of the todo item, in unix timestamp", + Type: schema.Integer, + }, + }), + } + + return utils.NewTool(info, AddTodoFunc) +} +``` + +这种方式虽然直观,但存在一个明显的缺点:需要在 ToolInfo 中手动定义参数信息(ParamsOneOf),和实际的参数结构(TodoAddParams)是分开定义的。这样不仅造成了代码的冗余,而且在参数发生变化时需要同时修改两处地方,容易导致不一致,维护起来也比较麻烦。 + +### **方式二:使用 InferTool 构建** + +这种方式更加简洁,通过结构体的 tag 来定义参数信息,就能实现参数结构体和描述信息同源,无需维护两份信息: + +```go +type TodoUpdateParams struct { + ID string `json:"id" jsonschema:"description=id of the todo"` + Content *string `json:"content,omitempty" jsonschema:"description=content of the todo"` + StartedAt *int64 `json:"started_at,omitempty" jsonschema:"description=start time in unix timestamp"` + Deadline *int64 `json:"deadline,omitempty" jsonschema:"description=deadline of the todo in unix timestamp"` + Done *bool `json:"done,omitempty" jsonschema:"description=done status"` +} + +// 使用 InferTool 创建工具 +updateTool, err := utils.InferTool("update_todo", "Update a todo item, eg: content,deadline...", UpdateTodoFunc) +``` + +### **方式三:实现 Tool 接口** + +对于需要更多自定义逻辑的场景,可以通过实现 Tool 接口来创建: + +```go +type ListTodoTool struct {} + +func (lt *ListTodoTool) Info(ctx context.Context) (*schema.ToolInfo, error) { + return &schema.ToolInfo{ + Name: "list_todo", + Desc: "List all todo items", + ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ + "finished": { + Desc: "filter todo items if finished", + Type: schema.Boolean, + Required: false, + }, + }), + }, nil +} + +func (lt *ListTodoTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) { + // 具体的调用逻辑 +} +``` + +### **方式四:使用官方封装的工具** + +除了自己实现工具,我们还提供了许多开箱即用的工具。这些工具经过充分测试和优化,可以直接集成到你的 Agent 中。以 Google Search 工具为例: + +```go +import ( + "github.com/bytedance/eino-ext/components/tool/googlesearch" +) + +func main() { + // 创建 Google Search 工具 + searchTool, err := googlesearch.NewGoogleSearchTool(ctx, &googlesearch.Config{ + APIKey: os.Getenv("GOOGLE_API_KEY"), // Google API Key + SearchEngineID: os.Getenv("GOOGLE_SEARCH_ENGINE_ID"), // 自定义搜索引擎 ID + Num: 5, // 每次返回的结果数量 + Lang: "zh-CN", // 搜索结果的语言 + }) + if err != nil { + log.Fatal(err) + } +} +``` + +使用 eino-ext 提供的工具不仅能避免重复开发的工作量,还能确保工具的稳定性和可靠性。这些工具都经过充分测试和持续维护,可以直接集成到项目中使用。 + +## **用 Chain 构建 Agent** + +在构建 Agent 时,ToolsNode 是一个核心组件,它负责管理和执行工具调用。ToolsNode 可以集成多个工具,并提供统一的调用接口。它支持同步调用(Invoke)和流式调用(Stream)两种方式,能够灵活地处理不同类型的工具执行需求。 + +要创建一个 ToolsNode,你需要提供一个工具列表配置: + +```go +func main() { + conf := &compose.ToolsNodeConfig{ + Tools: []tool.BaseTool{tool1, tool2}, // 工具可以是 InvokableTool 或 StreamableTool + } + toolsNode, err := compose.NewToolNode(ctx, conf) +} +``` + +下面是一个完整的 Agent 示例,它使用 OpenAI 的 ChatModel 并结合了上述的 Todo 工具: + +```go +func main() { + // 初始化 tools + todoTools := []tool.BaseTool{ + getAddTodoTool(), // 使用 NewTool 方式 + updateTool, // 使用 InferTool 方式 + &ListTodoTool{}, + searchTool, // 使用结构体实现方式, 此处未实现底层逻辑 + } + + // 创建并配置 ChatModel + temp := float32(0.7) + chatModel, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{ + Model: "gpt-4", + APIKey: os.Getenv("OPENAI_API_KEY"), + Temperature: &temp, + }) + if err != nil { + log.Fatal(err) + } + + // 获取工具信息, 用于绑定到 ChatModel + toolInfos := make([]*schema.ToolInfo, 0, len(todoTools)) + for _, tool := range todoTools { + info, err := tool.Info(ctx) + if err != nil { + log.Fatal(err) + } + toolInfos = append(toolInfos, info) + } + + // 将 tools 绑定到 ChatModel + err = chatModel.BindTools(toolInfos) + if err != nil { + log.Fatal(err) + } + + + // 创建 tools 节点 + todoToolsNode, err := compose.NewToolNode(context.Background(), &compose.ToolsNodeConfig{ + Tools: todoTools, + }) + if err != nil { + log.Fatal(err) + } + + // 构建完整的处理链 + chain := compose.NewChain[*schema.Message, []*schema.Message]() + chain. + AppendChatModel(chatModel, compose.WithNodeName("chat_model")). + AppendToolsNode(todoToolsNode, compose.WithNodeName("tools")) + + // 编译并运行 chain + agent, err := chain.Compile(ctx) + if err != nil { + log.Fatal(err) + } + + // 运行示例 + resp, err := agent.Invoke(context.Background(), &schema.Message{ + Content: "帮我创建一个明天下午3点截止的待办事项:准备Eino项目演示文稿", + }) + if err != nil { + log.Fatal(err) + } + + // 输出结果 + for _, msg := range resp { + fmt.Println(msg.Content) + } +} +``` + +这个示例有一个假设,也就是 ChatModel 一定会做出 tool 调用的决策。实际上这个例子是 tool calling agent 的一个简化版本。更完整的 toolcalling agent 可以参考: [Tool Calling Agent](/zh/docs/eino/usage_guide/examples_collection/todo_manager_implementation) + +## **使用其他方式构建 Agent** + +除了上述使用 Chain/Graph 构建的 agent 之外,Eino 还提供了常用的 Agent 模式的封装。 + +### **ReAct Agent** + +ReAct(Reasoning + Acting)Agent 结合了推理和行动能力,通过思考-行动-观察的循环来解决复杂问题。它能够在执行任务时进行深入的推理,并根据观察结果调整策略,特别适合需要多步推理的复杂场景。 + +> 更详细的 react agent 可以参考: [Eino: React Agent 使用手册](/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual) + +### **Multi Agent** + +Multi Agent 系统由多个协同工作的 Agent 组成,每个 Agent 都有其特定的职责和专长。通过 Agent 间的交互与协作,可以处理更复杂的任务,实现分工协作。这种方式特别适合需要多个专业领域知识结合的场景。 + +> 更详细的 multi agent 可以参考: [Eino Tutorial: Host Multi-Agent ](/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting) + +## **总结** + +介绍了使用 Eino 框架构建 Agent 的基本方法。通过 Chain、Tool Calling 和 ReAct 等不同方式,我们可以根据实际需求灵活地构建 AI Agent。 + +Agent 是 AI 技术发展的重要方向。它不仅能够理解用户意图,还能主动采取行动,通过调用各种工具来完成复杂任务。随着大语言模型能力的不断提升,Agent 将在未来扮演越来越重要的角色,成为连接 AI 与现实世界的重要桥梁。我们期待 Eino 能为用户带来更强大、易用的 agent 构建方案,推动更多基于 Agent 的应用创新。 + +## **关联阅读** + +- 快速开始 + - [实现一个最简 LLM 应用-ChatModel](/zh/docs/eino/quick_start/simple_llm_application) + - [和幻觉说再见-RAG 召回再回答](/zh/docs/eino/quick_start/rag_retrieval_qa) + - [复杂业务逻辑的利器-编排](/zh/docs/eino/quick_start/complex_business_logic_orchestration) diff --git a/content/zh/docs/eino/quick_start/complex_business_logic_orchestration.md b/content/zh/docs/eino/quick_start/complex_business_logic_orchestration.md new file mode 100644 index 0000000000..0c137f98f1 --- /dev/null +++ b/content/zh/docs/eino/quick_start/complex_business_logic_orchestration.md @@ -0,0 +1,194 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 复杂业务逻辑的利器-编排 +weight: 4 +--- + +## **使用 Chain 优雅地组织代码** + +> 💡 +> 本文中示例的代码片段详见:[eino-examples/quickstart/legalchain](https://github.com/cloudwego/eino-examples/blob/main/quickstart/legalchain/main.go) + +## **什么是 Chain?** + +Chain 是 Eino 框架中用于组织和管理代码流程的核心功能。它让你可以像搭积木一样,把不同的组件串联起来,构建复杂的处理流程。 + +## **为什么需要 Chain?** + +让我们先看一个常见的场景。在 RAG(检索增强生成)系统中,一个典型的处理流程是这样的: + +```go +// 1. 使用检索器查找相关文档 +docs, err := retriever.Retrieve(ctx, userQuery) + +// 2. 处理检索结果,整理成字符串 +var docsContext string +for _, doc := range docs { + docsContext += doc.Content + "\n" +} + +// 3. 使用模板生成 prompt +messages, err := template.Format(ctx, map[string]interface{}{ + "docsContext": docsContext, + "question": userQuery, +}) + +// 4. 使用 ChatModel 生成回答 +resp, err := chatModel.Generate(ctx, messages) +``` + +这种写法虽然可以工作,但存在一些明显的问题: + +- 代码结构松散,每个步骤都需要**手动处理错误和类型转换** +- **难以复用**,如果其他地方也需要类似的处理流程,就得复制一遍代码 +- 缺乏**统一的监控和日志机制**等 + +使用 Chain,我们可以做到: + +- 用清晰的结构定义处理流程,代码更**清晰易读** +- 轻松添加调试日志,查看每个节点的输入输出 + + - 可参考 [Eino: 公共切面 - Callbacks](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects) 和 [Eino IDE 插件使用指南](/zh/docs/eino/core_modules/application_development_toolchain/ide_plugin_guide) +- 添加通用的切面能力,比如 tracing、metrics 等 + + - 更多详细信息可以参考: [Eino: 公共切面 - Callbacks](/zh/docs/eino/core_modules/chain_and_graph_orchestration/callbacks_common_aspects) +- **复用**已有的处理流程,在此基础上扩展新功能 (把流程拆分成可复用的组件) + +## **示例 - 使用 Chain 重构 RAG 逻辑** + +在 [🚧 和幻觉说再见-RAG 召回再回答](/zh/docs/eino/quick_start/rag_retrieval_qa) 示例中,我们实现了一个 RAG 系统,让我们看看如何用 Chain 来重构这个 RAG 系统,使其更加优雅和易于维护。 + +```go +package main + +import ( + "context" + "fmt" + "os" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino-ext/components/retriever/fornaxknowledge" + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +const ( + DefaultSystemPrompt = `你是一个法律助手,请基于以下内容回答用户的问题: + +=====参考内容===== +{context} +====FINISH==== +` + DefaultUserPrompt = `问题:{query}` +) + +func main() { + ctx := context.Background() + // 1. 创建 retriever + retriever, err := fornaxknowledge.NewKnowledgeRetriever(ctx, &fornaxknowledge.Config{ + AK: os.Getenv("FORNAX_AK"), + SK: os.Getenv("FORNAX_SK"), + KnowledgeKeys: []string{os.Getenv("FORNAX_KNOWLEDGE_KEY")}, + }) + if err != nil { + panic(err) + } + + // 2. 创建 ChatModel + temp := float32(0.7) + chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{ + Model: "gpt-4", + APIKey: os.Getenv("OPENAI_API_KEY"), + Temperature: &temp, + }) + if err != nil { + panic(err) + } + + // 3. 创建一个 Chain,用于处理知识库检索和问答 + chain := compose.NewChain[string, *schema.Message]() + chain. + // 并行节点,用于同时准备多个参数 + AppendParallel(compose.NewParallel(). + // 透传 query 参数 + AddLambda("query", compose.InvokableLambda(func(ctx context.Context, input string) (string, error) { + return input, nil + }), compose.WithNodeName("PassthroughQuery")). + // 处理上下文信息 + AddGraph("context", + // 创建一个子 Chain 用于获取上下文 + compose.NewChain[string, string](). + // 使用检索器获取相关文档 + AppendRetriever(retriever, compose.WithNodeName("KnowledgeRetriever")). + // 将文档转换为字符串 + AppendLambda(compose.InvokableLambda(func(ctx context.Context, docs []*schema.Document) (string, error) { + var context string + for _, doc := range docs { + context += doc.Content + "\n" + } + return context, nil + }), compose.WithNodeName("DocumentConverter")), + compose.WithNodeName("ContextPreparer"), + ), + ). + // 此处的 input 为 {"query": "什么是合同?", "context": "xxx"} + // 使用模板生成 prompt + AppendChatTemplate( + prompt.FromMessages( + schema.FString, + schema.SystemMessage(DefaultSystemPrompt), + schema.UserMessage(DefaultUserPrompt), + ), + compose.WithNodeName("QAPromptTemplate"), + ). + // 此处的 input 为两条消息的 []*schema.Message, 第一条为系统消息,第二条为用户消息。 + // 使用 ChatModel 生成回答 + AppendChatModel(chatModel, compose.WithNodeName("QAChatModel")) + + // 3. 编译 + r, err := chain.Compile(ctx, compose.WithGraphName("RAGChain")) + if err != nil { + panic(err) + } + + // 4. 调用 chain + resp, err := r.Invoke(ctx, "什么是合同?") + if err != nil { + panic(err) + } + + fmt.Println(resp.Content) +} +``` + +## **使用编排的优点** + +Eino 的编排系统是一种简单而直观的方式来组织你的代码逻辑。 + +它的核心思想是把一个个节点前后连接起来,就**像搭积木**一样,你可以一块接一块地把不同的功能组件连接起来。每个节点都会处理数据,然后把结果传给下一个节点,形成一个完整的处理链条。 + +> 更多详细信息可参考: [Eino: 编排的设计理念](/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles) + +Chain 有一些特征: + +- 基于 Go 的泛型系统,这意味着你在写代码的时候就能确保数据类型是正确的,不用担心运行时会出现意外的类型错误,可极大降低开发时的心智负担。 +- 提供了简洁的链式调用接口,让你可以像搭积木一样,轻松地把不同的节点连接在一起。 + +编排能力解决了复杂逻辑开发过程中的一部分复杂性,但依然在调试时具备复杂性,因此,我们也提供了 `eino-dev` 的工具,能够可视化的查看编排的情况。 + +> 更多详细信息可以查看: [Eino IDE 插件使用指南](/zh/docs/eino/core_modules/application_development_toolchain/ide_plugin_guide) + +## 其他编排方式 + +虽然 Chain 只能处理简单的串行逻辑(DAG),但这已经能满足大多数日常开发的需求了。更复杂的业务逻辑需要使用 `Graph` 或者 `StateChain`、`StateGraph` 等。编排的详细介绍,可以参考:[Eino: Chain/Graph 编排功能](/zh/docs/eino/core_modules/chain_and_graph_orchestration/chain_graph_introduction) + +## **关联阅读** + +- 快速开始 + - [实现一个最简 LLM 应用-ChatModel](/zh/docs/eino/quick_start/simple_llm_application) + - [Agent-让大模型拥有双手](/zh/docs/eino/quick_start/agent_llm_with_tools) + - [和幻觉说再见-RAG 召回再回答](/zh/docs/eino/quick_start/rag_retrieval_qa) diff --git a/content/zh/docs/eino/quick_start/simple_llm_application.md b/content/zh/docs/eino/quick_start/simple_llm_application.md new file mode 100644 index 0000000000..3845fc37d5 --- /dev/null +++ b/content/zh/docs/eino/quick_start/simple_llm_application.md @@ -0,0 +1,218 @@ +--- +Description: "" +date: "2025-01-07" +lastmod: "" +tags: [] +title: 实现一个最简 LLM 应用 +weight: 1 +--- + +本指南将帮助你快速上手使用 Eino 框架中的 ChatModel 构建一个简单的 LLM 应用。我们将通过实现一个"程序员鼓励师"的例子,来展示如何使用 ChatModel。 + +> 💡 +> 本文中示例的代码片段详见:[flow/eino-examples/quickstart/chat/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chat/main.go) + +## **ChatModel 简介** + +ChatModel 是 Eino 框架中对对话大模型的抽象,它提供了统一的接口来与不同的大模型服务(如 OpenAI、Ollama 等)进行交互。 + +> 组件更详细的介绍参考: [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide) + +## **Messages 的结构和使用** + +在 Eino 中,对话是通过 `schema.Message` 来表示的,这是 Eino 对一个对话消息的抽象定义。每个 Message 包含以下重要字段: + +- `Role`: 消息的角色,可以是: + + - `system`: 系统指令,用于设定模型的行为和角色 + - `user`: 用户的输入 + - `assistant`: 模型的回复 + - `tool`: 工具调用的结果 +- `Content`: 消息的具体内容 + +## **实现程序员鼓励师** + +让我们通过实现一个程序员鼓励师来学习如何使用 ChatModel。这个助手不仅能提供技术建议,还能在程序员感到难过时给予心理支持。 + +### **1. 创建对话模板** + +Eino 提供了强大的模板化功能来构建要输入给大模型的消息。你可以使用占位符来插入变量和模板消息: + +1. 变量占位符:在消息中插入变量,支持三种格式: + + - FString: `{variable}` + - Jinja2: `{{variable}}` + - GoTemplate: `{{``.variable}}` +2. 消息占位符:用于插入一组消息(如对话历史) + +```go +// optional=false 表示必需的消息列表,找不到对应变量会报错 +schema.MessagesPlaceholder("chat_history", false) +``` + +> 更详细的组件介绍可参考: [Eino: ChatTemplate 使用说明](/zh/docs/eino/core_modules/components/chat_template_guide) + +下面是完整的模板创建代码: + +```go +import ( + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/schema" +) + +func main() { + // 创建模板,使用 FString 格式 + template := prompt.FromMessages(schema.FString, + // 系统消息模板 + schema.SystemMessage("你是一个{role}。你需要用{style}的语气回答问题。你的目标是帮助程序员保持积极乐观的心态,提供技术建议的同时也要关注他们的心理健康。"), + + // 插入可选的示例对话 + schema.MessagesPlaceholder("examples", true), + + // 插入必需的对话历史 + schema.MessagesPlaceholder("chat_history", false), + + // 用户消息模板 + schema.UserMessage("问题: {question}"), + ) + + // 使用模板生成消息 + messages, err := template.Format(context.Background(), map[string]any{ + "role": "程序员鼓励师", + "style": "积极、温暖且专业", + "question": "我的代码一直报错,感觉好沮丧,该怎么办?", + // 对话历史(必需的) + "chat_history": []*schema.Message{ + schema.UserMessage("你好"), + schema.AssistantMessage("嘿!我是你的程序员鼓励师!记住,每个优秀的程序员都是从 Debug 中成长起来的。有什么我可以帮你的吗?", nil), + }, + // 示例对话(可选的) + "examples": []*schema.Message{ + schema.UserMessage("我觉得自己写的代码太烂了"), + schema.AssistantMessage("每个程序员都经历过这个阶段!重要的是你在不断学习和进步。让我们一起看看代码,我相信通过重构和优化,它会变得更好。记住,Rome wasn't built in a day,代码质量是通过持续改进来提升的。", nil), + }, + }) + if err != nil { + log.Fatal(err) + } +} +``` + +### **2. 创建并使用 ChatModel** + +ChatModel 是 Eino 框架中最核心的组件之一,它提供了与各种大语言模型交互的统一接口。Eino 目前支持以下大语言模型的实现: + +- OpenAI:支持 GPT-3.5/GPT-4 等模型 (同样支持 azure 提供的 openai 服务) +- Ollama:支持本地部署的开源模型 +- Ark:火山引擎上的模型服务 (例如字节的豆包大模型) +- 更多模型正在支持中 + +> 支持的模型可以参考:[Eino: 生态集成](/zh/docs/eino/ecosystem_integration) + +下面我们以 OpenAI 和 Ollama 为例,展示如何创建和使用 ChatModel: + +#### **使用 OpenAI (和下方 ollama 2 选 1)** + +```go +import ( + "github.com/cloudwego/eino-ext/components/model/openai" +) + +func main() { + // 创建 OpenAI ChatModel, 假设使用 openai 官方服务。 + chatModel, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{ + Model: "gpt-4o", // 使用的模型版本 + APIKey: "", // OpenAI API 密钥 + + // 可选的 Azure OpenAI 配置 + ByAzure: true, // 是否使用 Azure OpenAI + BaseURL: "", + }) + if err != nil { + log.Fatal(err) + } + + // 使用 Generate 获取完整回复 + response, err := chatModel.Generate(context.Background(), messages) + if err != nil { + log.Fatal(err) + } + + fmt.Println(response.Content) // 输出模型回复 +} +``` + +> OpenAI 相关信息,可以参考:[ChatModel - OpenAI](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_openai) + +#### **使用 Ollama(和上方 openai 2 选 1)** + +Ollama 支持在本地运行开源模型,适合对数据隐私有要求或需要离线使用的场景。 + +```go +import ( + "github.com/cloudwego/eino-ext/components/model/ollama" +) + +func main() { + // 创建 Ollama ChatModel + chatModel, err := ollama.NewChatModel(context.Background(), &ollama.ChatModelConfig{ + BaseURL: "http://localhost:11434", // Ollama 服务地址 + Model: "llama2", // 模型名称 + }) + if err != nil { + log.Fatal(err) + } + + // 使用 Generate 获取完整回复 + response, err := chatModel.Generate(context.Background(), messages) + if err != nil { + log.Fatal(err) + } + fmt.Println(response.Content) // 输出模型回复 +} +``` + +> OpenAI 相关信息,可以参考:[ChatModel - Ollama](/zh/docs/eino/ecosystem_integration/chat_model/chat_model_ollama) + +无论使用哪种实现,ChatModel 都提供了一致的接口,这意味着你可以轻松地在不同的模型之间切换,而无需修改大量代码。 + +### **3. 处理流式响应** + +在实际应用中,有很多场景需要使用流式响应,主要的场景例如「提升用户体验」:像 ChatGPT 一样逐字输出,让用户能够更早看到响应开始。 + +对于需要流式输出的场景,可以使用 ChatModel 的 Stream 方法: + +```go +func main() { + // 使用 Stream 获取流式响应 + stream, err := chatModel.Stream(context.Background(), messages) + if err != nil { + log.Fatal(err) + } + + // 处理流式响应 + for { + chunk, err := stream.Recv() + if err == io.EOF { + break + } + if err != nil { + log.Fatal(err) + } + + // 处理响应片段 + fmt.Print(chunk.Content) + } +} +``` + +## **总结** + +本示例通过一个程序员鼓励师的案例,展示了如何使用 Eino 框架构建 LLM 应用。从 ChatModel 的创建到消息模板的使用,再到实际的对话实现,相信你已经对 Eino 框架有了基本的了解。无论是选择 OpenAI、Ollama 还是其他模型实现,Eino 都提供了统一且简单的使用方式。希望这个示例能帮助你快速开始构建自己的 LLM 应用。 + +## **关联阅读** + +- 快速开始 + - [Agent-让大模型拥有双手](/zh/docs/eino/quick_start/agent_llm_with_tools) + - [和幻觉说再见-RAG 召回再回答](/zh/docs/eino/quick_start/rag_retrieval_qa) + - [复杂业务逻辑的利器-编排](/zh/docs/eino/quick_start/complex_business_logic_orchestration) diff --git a/static/img/eino/big_challenge_graph.png b/static/img/eino/big_challenge_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..ee0bfa0475bb9ebb4c31339e8977a424995b8f2c GIT binary patch literal 337436 zcmeFY`#;nF|3BWdb5uI86wM3v<)EM}*D@ z?b@~Lh|#@2EO+fX^lR6yJrM`@3qG02BNy%3^>mlf9|qQ8?-#j;YTSS@7K{(7CJ$Ms z#+-@UwSMe_XX51SLe9&MKTbaA*{Aw6$k0euT?37-uQ$3c`uXzBo5%LO8T|!*6Uho| zib#&J6b0UVcF^<0$qS?BtV2z6tI3#kd%`v4I%Kyqquz7;a>_Y5aChcJw2n@}24U%**JhwC^K1qcaA3=i>m9B|+#!siv}+J; z-q@Y6QYzY}xl~lyXpUf0bD?j@Ndn=SDLv$(FHBsiJEyFFs6h9K$iE@}p?T$C%#Tz4 zPV&Eg`@f=Iu?znD<(n42|NXB>{T3%0K$V_JuDeTHF@?+HHn6hU!S?7bz<{ zzgdaF)a*pO+KfN#Y=F+oR9X{T`}r`Fb%C+6+-Dr&Ce!#Ob6hyDjPjq=c2@qMrG=jg zh5!Avj&NJ^xA)58r!op>eim3dS;8lj`AT4Grnn*Q>@4-U+vUb7{DcGup`d>#kz^45 zB@CQbvNL|Fg`SbVDv!%Ao@fH{Y{}*N+6tTLtQDV3`c^#e{@4F1{qPs&|3vgZC$sD6 zzMJR%iLuA04->cW`)b|NpnHzIl?UF2kr(XD*fpPw(a5gFmtunoN%LM;#Y_B%YoAZM zEy!B}tbG)W$%b8Gqv~%(9nJt8OkP7j%GVlI0*B}c^=al^u@kkGW3}!3>wlU$`)gQC z_+DmmMCB`$6!@73K0<|dldEpl7RuM^5Xvd7vlk5?d{OLzr!xjz5l7Z64xBU~(f=a9D2+Z@ZQ`IXf^t3(3?TNhQqrQ+z|gv^jP}D~G3p!!Ex- zUCZYwKbLLjyB^G-Y;OPRfHmJ?85R$n&YR9!`hzescm4W!%D^_i`%CJwC+^Pnv^qam zf;0f%^eCtJvR2;LbE7R*5Bw7(yyL-ta&t*R&isS<$23I?9O;VQ;oGG{ujOZAs_d2% zo_%mcsaq9&M=Ncs&U7&xQBvG8w1J*^cA@2`k7dNPv_BTupCQ1mK)2ZQJA=||G+44+ zV*Kw}#)T?}t2b{Z(`~F#1N7}tMhF9U^)N#%W!kAZv;#+&AovDudtKD2HBY-(>Aks{ zT00b7D+`yV5(d_{*SA=AZr^3rZ9f0x7`bCxSDR8>3Puxk zn&0WM-O*u3=;BgDDoYQ8>j`&apX;094v;2v14$fCfSIEMMZ6($Dkaq6(?A7lHgIQX zW}Mmjlo!#=;B87sQ3oQoRu9(d0pUGd9S;7awcZ8aK$t%?czPkQQ-TAIHak0jd zF}$r;uO$vhpI@0)*;+m6g(bJ1OT%SITqn^e9j7|e$#%rsXqAoB z{VVSKorns-g#dQ9>ImJNt`D<`j{cet*<6`PY>HDL5lopyI>%mtay69Z;Z}X-1|$xc2npP5kkCm-Pf7ZOl6U`?aS45%y5+2 z={vCxKK))EcT`7kGdv~o5(l18dU{@BV4R0jo|wksGwM0HxtQ>2U5i~yFX|$Htgk++ zqy%e83FmGK!F{P7D)(nQpsMCGRDx_p-K!njjZN)*e{tw1XL+KHXXK@gg?Q8vW)lvD zuZ@nsqp&L_r8q}|Kd{+sekb1xf3cho`}q1{o^##^hRX3C_6%;6)zbJUj_}HVpYEsO zCfz?MA9ly_EhX{r`yPyLqda?USo=KKlTDR$YW+$0mJ8b5QOynOKCISN;an~y{fdQZ0* zK9N<<@TR<#&Cbgkr0cJiJ~~7`)%Yc&f)QuoCr)SJFuXLOwjMXK;h=vg^tpj41hM zn1KaP-^D039R^?`fL2C?=wDyg_tPsWS<%zCGLR2d($Bnu-n4Fk=zXt?1Y$iR4(sBr z%e~*AQ#9=3`g`}0u+AA@vPF_pc#}_{K~Og5VNE&CW@DApB{jXZ{g`m!-vIi_6xcozzfKoy9LI>&T=xU} z@tNfItGm=177-#xHm??lNNjRext!N}Hw2g3n@Wz#L=wNBz%#|8`{w7p%AN6(vu9bR}? zfk)?oy|Df#Lq!ip^f~t%>S^2S-6ud2Zzy2C1P z&fxuvJ5D9wkNthPq$ny|3dP*7RRj890<6SMnye|&+j;W2wdFZWG zbxTQit^ao5S$b>;e5YEmy4iQM!T@Q59_f_LFLUr~3KloE(meYqeG;BGq2?rErZs<2 zA`)BAVOD#bH=j{hg;E-b?Y~~#K05H*0m%>8W_J0i)}+k`+)9@MyB6Ejn}VN;mso6s z_eAII1pb8$~0js+6B941!)6c~q+{q9{wb3^Te2M@RtWOeE0G-nX+%5t|Y z+5u~-3|~%S*y${N*#mXfAL*0HbS!Z2t#6yH%E9T?(PCj{GHZYF2?FN8*=g^}-$xt3 zx1TK~OPhVN+B-UwR9$~VzBp7$lm{Q!Bm7Cxxp@K+1Z`M;V;J194{>ar8>ulrLfFA9 zup9Q-D;^%1wXtra(BY&8NpYCovJj9Jyg8b z-}OV)f|GcB>(kzE-=61QFgigd*t7-GR)O@L&}Nb51@h2TAFXeCTt`eW7NKijMxR;Jrn%u80(#?=oJ+S1=5<;52Ajyh9($Ew@_D~mx|>Hyii zu%C@7oK?>KY{S+vz! zS1Cz~=BUcmx^y-=c2(y-dw0%UU&`~xWJhJ1Sib_HC?pIxVv6Y5S8idTN-`%O5p+RlzIR(`R6F= zqzyf!HzM#p1x^@$d)TrbRAe{Me$nKu0_odA9Z8tH zNi=Q^4%_gka$51>6`09Wp$fBJvw4R|>n{D~t%0(>0e9M(ScUaa;?_G+&@eqSu$oLA z1K|~Y@{rE)^3@Zm>@O+w;!+S=)5b#bvT)T@)M>7Bc5&&&z(W9664Xe@2 zhECLle2)=SPhP67-$x1AnMC>Jr7(_;RKV!*)><6(kn8mH_%(8sX86YIyK<)wFNU^A z(OErEdL@$3edfdNrv8?5sb-BIuk?sgxVoSDVVPayOk61(C7Q|=9 zGGD}JqJ*rBcie9NCDRsqU0akyU&u%&`dl7ec?Uwexzz7Dsy`vWu3I>6={DMFqlUDW z2T{7%3W?{DPiBG|4B6L_?@t*T_A3T$dnX@5ZoiwUmIaj{)LQ}0I+k`~rgt`l_w%+r~U>^1`T9DO_4N{IC>n^vn!&6CE zJQs@7jy168?16=lq1gGwz`j}-juKQiQE~?{8jOR{6L87?qL5cJBh8T-6#c_XoAQzB zlm4nN0Yrot&K*m_a+qQKTEI?zV`>(vjTt4dhgnJ>PZ_rXB} z;S22j2oiFEGvrd(@{AsRtYl%9^Y9^OCfi%jg;0jeRL8e|N1c|pmTW)s%di4PW{rD` z{?WP)oX+hY-;(j5aB{&NDftSoPTBUW*l3Ni;V)B3&`ukYhjTqEQ2}kfDL4K;h(*)F z?^M;f&UkwbWj z$+Le(jNP=8yw{rzXMCFu8rJmiq%6MWK{wBH$!Y#-WQSq#o`oq>hwCO8av(!5St7lt zNHub`H{!<%-dZNh!tC|9T?yFS!cy9%i8L6Nhs={jgt!kIBaLGd$HO1y@JQvxJi1$i z-NeYZX+8o=9TD~VOf?xu*(za1PuBUOFBv<-Kcv(#7Yf+K?DmU98DbaI z*$^(VvM!bWCr|Z=ZtTx;ZOvRrBJwhHJL4vKzM-*S(5Z6f; za>wb18y>n#0_hh|=)Ra9W!iuy14I4X-LVTmFSmkF2}9Z;qaJUjlZNS>B=|4G z=p9z3EV<>J;%=yg=hq&K>FMCyCoP8nYu@TPTO%;)U_)@|SSa&aRyKri`kg|=iObbA z?F#>c6j_7ggE>$}*|?^jy6@=zQ-kXz27?)Pv zb;bHknk0NfHBFM{RJr-ke|4(s+=@#L|l?)^5)Kld$-h0y)bCE7Lb`1}i%j zl>Fe;XQ_>2F}Ex}l|FV)xFjoE9y7i+{5C_$!Tq;v2nHd*BXi9Xel_}%zrvfZ2dPy_ zy4seOfRuK-_#dNp=<_N^oZ)dWlBa4}jP>*FRWzq59Ndx# zjcod(*WD>nKU!KU?28G81;?Xc;H6vK@g&aBDLyX=%;I({I^A^SMlvBw`GaU;JO3)iqeQqwmJaF~9 z&DiR1B|)$3Z(cJxDg3Nno%4p*$Q+1jrzU3seQGL*#|?qCbMpsyW#tw+WmU5ZJ8^Bs zUyKh7eha&8Q|_ewKF}ibQ@^gBmwya~895va_qMHStc8 zkVN_Rhs!@_&t@W|HlbG5E7rb@RjON;s-6HbnqoD4?ur7^Sy7laT_{ttiW)nkyi0=< z<%VloAMb8&AVS8tfrK>Y^P>pf5la}e8av14&$C0EBbxG>;q-FD3UvkbzqjOFI@o11 z^}mZ~7@(^g;b_pjdUCAlO&blyF%mD4-Uo8KZ*qxvW1eB3w!W53_9GQA^an@3J#Rno z(cK;v-0}Io(!TO?`)iZKEN)nWJAX5J-Z{cGV&=3*37+uSV!0dLJ?d>jL^y@M;t+Qh zo3fi0Rhp_MfihBa%UKERf>TZR^eH2iS9>29e~i%T$D_qbYil5A__PGun+3}sCKS3` zg`bfx0jZt77j7^5ja(S(hTqSd=H0X?v_)9S-@4zk#; zd5GlDNw29AvLD{6LkbJIkq6RM?v77q&ZfC-UabqBm&vraIE?S^onOB?;f<_#;peAgscE)wQO0Yi=azowS z+Y#gS-kXKCvFG}Kwk0K0KcT`n)vNWa$fmrwcKh-SrBZtPBn*rMR9X~OpK{)mkO`f^OGAG3jOV2-C*nku$(V;zf!rf)e)w_j>cy7oZL@lmkt zjxXosjAT}mBB2aTgdBfaMwD;hu4{|#wK)DxQer4YO8f1l&LwLRy`>LZMxWe=I9sN zG%cJ{%b7VbnEUKZvTS5u7i~h<8Dw{@YU!a2A_M!;HjPAWz0;%~<@{oggV4C{$tlUy z%1EC4%!BogJM2-=TFB~0@u8yj4q2$Q#ua9`@lbPia)T7VGzUcz^s)yKL~lTUbw4<7 zjCKA$seb(Xp1%%%*q9gf#3&dXvo)-Qh#KXlfY-P2epFe0MAAMp_lE1B(|%f>-sDa}`Vqufk(?4f_@te>PC5=y!39 zPjHOBbm>we!rtv3G-#7RMA~={Q9>R1=TB-x-Rh9UAr4E6nLM(W`4R>1PT#sZDU%u1 z)7~jX>x-ONe4zjMU0H1EJUT_uURI0lJ6nRu#+-}QS zs3u^>Si%EKbqb{xW@Uv!9u<6q6uLkAB&-~TWsU`yIjQ|>(?909M#vHYeAIT#nZVTg zQXpcBE&IM(#Tnd?Jir;BUkhR`5EnwWM+o5T(Gy>McXk@LDb#Hz;fb-8uKGp2y7lDL zRQj_6(lzG!+O^`DUuGW^3flhYy^wO_mW^Y0TtUer>b7P- zJvi@IPg`RkEr=Tm)m?jaJ7^1MihbKlm&IIG=gCav>~UCfpd%gp+@ zIl0{Tc)Mgyg`Uq?RoghNIgl_YAX&V>cAk@i7rTW+^hw^+>c#pSUq#0Kv6Na<<)|rP z_M5xWpIuWl@Az-5Ar=4eK>Qd>`Y(j5=d02Kduw94l^ zdE}Kt4V;{}1~b>Pq2%GN>oY?+7|uqW`$a7YlhIs#BJUepE6VQk#m#4ceU++11G7;d6w5sg4ZA{m5NgetA!}L@OV6i+ zmke1Jp;1{<(hB`Pxbil3gIGt`{ujFqh2(Mjr&YwhYB|T!^Ki_8_+$yCcL2lJTV4nj zj1`%kJ#G=7BHs|eV0R6kf3AA(gwxKXaM0R7#~jj`i*FE6D!~*?e*bBYJQtd9%$F9! zF`dkw-qrKgq0>h+Y@f&(oyZO4ZGv8)?Q^#YO3#4j6@-pnacXdkEDvjrqxNahC4~Va z5nbbhHuCCs1O#J?X;S8Wz_Tg{aA=6EwD=3|i?OeLxzcA| zs9k%|u5CakEqnD>$E3}QKr~R2=dx^&!PEhmC1l}zpie`$jLQ+^LLM|CSYN^ey_Xc^d**FcF1y63$?81h6uW8^Xf+yG$ zyg(&g2J+N;Rrp1E9kjk5J47C{Yq0}lTxEz*sHaZt(GTWiIN7c+u9l8oJ5UV{Q@ zKEw0{Op>u}s&BOQHWP;iM@$*kp7jLpN2VDRExQi|fMWkh^wtZ&(u8cVXoq46zeWQm zhXpko{oeTW*1s;tm!gH-Yl9R8)?aEr1Ne@EFzm~9q6!=4Z%XsJMZDPy*AtE_hb}rA zTs*=_zk=WCa*;s^UuiV=9x1C_fW&_7oAIG(B@96@KC1@O`1|vNg(eJ|AQlcA9)es2 zwe>n(ml7o|uQZmdk1#?KT;znK?B?VY6!zW$OlVI%cMa;P>=^YoI4v5;WxME*&N`0~ zlut*{q1bVZpxP?E)cCGi<>;$^_~$0qNt(xw~nw47Vss8!fGZx!MMGzUq zm~=NcEr$SA$wN8gRRu5NgkUor#of93%E@(9{Ku2rQULR{~<`W{GIJ^UOCmKjnylBt^xAEi3EOhllB*@xT4Sc`x8%+YoDoUer7r=hFWYcmHLh!T>A& zC;8Ir&qQk8YGy-@m7cLq-`E2QX3GE(!Q0myD;>8RBa`7b&dx{kS^RPh{VJ!ECnLQc zMLser?6~Bist%A_z^Z=V-YW0-JdfuVoHdn?SniZH5!peW<$Z}BFgTf&ZDBsQrJv0q_J9dP7gBW!(C?S+op!$R(JMT#(4!+4fMWU4T`~$!LGcxXQyq3= zghzL&y>f|)qk`61>EW=aYEq$iU(t+RNj9wC`@$@Om=b=C|O#x_kkg$ht;KBr&WKh|F$zUpVO5XoL+bMx1v&ne{g2ANR^|DxF~F$(CQc~ zEz12qvvth2tv2>(L^`H5L5dTUN12AiDk>nprE~PIkZqM)U&{j`Yw7ixRkIV-vpTGC zRyomeYh@C$U1s3?Dey*9(w-AEOPy$fG(-H=9;zMsG~|=nEQG_x?hb1 z_WM0waC5C$(fsZ5TVO01h;~raJPkY#o6T@oTZuhdC8J8vq2K>Y zb(CsakR$7-B&o~Hf5|X7^2xkB#6pU>n$K?p!f=A^p=PC`Rnb=c`~Jk1M?DeTZy6%Z zTiHmc;^fu{c43>!_mD9fNPv<mA-d7}~u0s&~q`(DFIq&eqRn<#v_+-xJQN zybq21Ir|gw3FP&cuKSRDtpjD54FHPJx{qMJL-S_Tv4y#<_-)ZeTx#qc&-r9mf%>4!fSPJW_QripKe)(d%JBG5=7Bd)g` z0umTy5E0yqLu`_h$M0=Y+97kdrMG@mNR)%_y1E7)sLJmBX{J)D)H!D5c<67WzPvp9 zB;Q!FgmM`XO{7;g`Kd1ec&~2>{{(sixxZxfPhY{^_ckR0$0Ta5S>qTi;@ih;Oqj_017@dNyhK-rPKa;ovVF1fn)V2XEU;g^aqi zt>mT`=yT!D{u343Y9l_j6^my4H8X}z6WHvO*pgN!3=8@FWV5Y-W0!B@0(SAy4qWqy_w7H_v;(G9E0a0HU!#l&e2?qmtUEj~|K))^YsrBDty zG^ic+$t#Zi2_Fq;UiG9seC6QQ2G+1RHmlBel#pLNIPF%C{#Fvu9EXG3fL&D`vu3gc zZ0@Y-MWBLqe)Z>kxiT9cBWGuzzuAsTmoMB7*jzVE*up#g8>*fE>?~U~1`#;9LAJnmud~Uy9q%j`~ zr(u;GRj+G%|E?^o!QKYMteE6Y84^eFmdF^tavQ1)BZCJ^}c}P%{ z3H+XMHBC(~u9s@!RWLROkiJq-(8Sy+2bqwZLz}pbt{q2V%Pjt6j1ePbprf)fev2E_ zH|qeb2*BR?adw^=K-RPv)>d-zzqupvF)9jJ{Lz|P#2Aq9B&8&dZ>RO z0n?jd0-szhV?~ZTMo#dOPAS)FPPZ4^3f-t$&Y^G2bZ3$s{as%b&0l@8u`$~lcqLOy z+TgTr5Hx%_$=T;wPioXJ*Vbbt5<}MsT0^tg?Y$yx2zU?#YMSHDgk z>=38|co(q-mDhmuZa&j1a1}fDlKmxC(37JG=%^V5rNbKeLjJzSBKyZGpg6p{ov({2 zRt)x?5js+mmDRT->1&nsjt&e3XB_!mj+edi+e{2n2H?_sP!kdZb%alw&IImql;i zdV=db$^s2k3X&CG4<^os_!zlqNIPjO{HLtut(Ls{+p+njy8j-uXa#c^xd^a71TPu} zszjaI_bkq_p;UD7dTF6I@1D!2!oLg|0@Rs@9{=@ORoA=wP2aaYGqH|%!1FVo>)~2# zSKa7**f5>e)ncAJe~-|eIk&7@l?cY->H719(mVXi7EYUCPO(*mARLnV`sa$-376ww zqfka%OVuEiNm-3!@4ziYZO_Sz+`?$1%1g%?%~d)Ww;Qb`2Qz^M{ku(xH$a8pfy$1v zZUzNFBxt@sSb1=srR9{Ww^b7NBx=dsEM#%?V`|jeC;R#pbKwfLoM1s+l&=J)=9q{B zuw5X^XR#I5NL^Qh+8Zf}G8XAaV()R`Hr(d5AF3*?;Lt=t=B?w^elt$U=#AZ?lPO zzK#>#0+sC8CyA4x`XSDh>SU;%4_UoXM`YMXJyAYz;9g{l?4jo~A7@dzHcwpV+Q%_% z6hb$u(LOV3(UO57fzL=tlcK}<+hy`OqTU@nM<1Bpx4&Tta1@Rw{+i%^@19AeyoX>t z%9E?hr2uDzC-_~>NA|yHQMsQ%7%(M;(x`Yo3(y}F$e06m9BzHGT3TUK`g}c`JYgE* zg^!Vq*p*a$bmdt??&CT~#-fkO$!XX1W|EoD#B2ndLSmRYVC-9Ge=RP){7ar0wKe?9 z{FFo$BO-w1J}F`ZL(h*LkD9moDV{RlD%}U-LAb)@m4DjzU3oeG@fbV4b;}Uyz6f;n zdPM{DM^5HUe%6qAQ43o<;}E|Z&Mt_^a;{ZFTPryVt~b(a)iw1pxH2^^|17_A+P>=X z3;5Ph{&+w|0-ghF^LGpLJy%N3%>Me&VEYG(p&puhBSp!aC~z5H7cNZ~BZ$TUDmn*; z$A{*Rti=(c4Gu`{K!lxj*GFCLYkjh$PVP61YlvvtsD811zggwS=sVN!j>}ps3eC2n zoXikGLV@zjCux7?J=caxihZrHFE27o0;E&AMCIfOV)h5`U3iwCiT^p%z;_U_ms<)( zN9MBGKVpxL2zad^0dGIHOv?{#1}`Vtlz>u`W(<>l?{81e3HtRqp;Sc=T>J5$=H}Hr zx|L+Yco3nes*3WCw~BMG$>l|t-HcmG0Neqg+(ZM)#S2Fy0oCF0HGOeO{&FeQE zZ!2-Y^hr?sM;(VV>Ovu=b>)L(C?-(3w?4yNJ^E*U}{!Qw69Lb!Rh~Ri!}C~78kXv>@eyv zPDy-z&!cBi$54)tYY@m?kA!suE21&Go81zcTij{zAfHRd6ZzM1L_PS|bJl4y*{Can z*6)z%>S_{hXxl?jAD^BKDM~FgXI~=1?Sq_@XWh3_ZPxJcTctoRsM(;)t8$WwH4^AE zKkgn#aJj?)-0NEH+3hwoG!$~co0zB+J63W^?2FH3;#F%bwelu0n%{Ej?aZ@aJo!Pw z*RR+eCIfJg9;6jwwnMW#-flAJ7&;kVY^t?+v5BL5YHn@FOgg?~;#i25A=$}UFQW6V z(y`!QukVg?xL{{jS2ck^N}y~sTnk?H7XK!H+r;ilZa|l|VhIRM!a#@v4FU}<&f6kUv6*shX%;kK_D44uN5lK3y^EY>Rz)G6+PYaw5{p!lPs_$ z%P8&wTPoktx4Sr`>;W0;NBSMV41sZQ)PZCTxN&iA2qLk^^|Q$p?w{(ENVFaYn_um5 z&Z_9f%Y^jV{dUU}xXQrQZbV1i$D_Jy$J#7D8{~~oAZ?N~9bUMS0~ngG#N}#aGRchE z-g+?w&}iVGnM&NMxUdFT@8G*4Seu1ES71?4vTOY10jLb#c5};#d7Y!8J|f=i7dj58 zTBT9mzz1zGmY8rFb(=QxWetMP1PHW1Ilf6Yx~zP`zgY#?uZZ(O!%5lzus0_H zVCjvi9pk_+4^{Ja2%+FlWSh}HmEIN_PO^(U186-M&us3ub^mG=Po?uMFLAsvlJ)p@vS zx7@JIW_KO4E%8Nri;XcG=wLz5^bydd&6yx>wQDrHOb4_c(O76)B`6{1;t=gK?>_GV z{`Aa5M8zs%jiUm^48j;GWipv^u-C zj=GZj;`>&Qc>XIn*&P3OxHd02YVyM6-OAyGVGp|a!)P}{%Zmyedd30OE|D!a&&z#7 z1qGnDa)kXtF<=WGJui~|6J+y-i`r+AW?WKnnyiv;q+*^ed*)(kRShx%Q%WPO4ZokC z|H=<31qY9e4f5UyWj`BiEOz{OUbt@W8I#+kC*(wu+@uu3k1rugY%_oc13Yly!9z1{ zKif#}B_u?DYf()7Z1C{6de!s-TjQI7$THA}) z%Z2QZxv$%oikfUs!Z??lFd^mpXX>28XMhbq6ET3JCTV|!pA}^t(1k6oC^s-BEIjXJvUgrvT3nl|<5~7E zD_-@!k4N=3G}iQa%+-kA7k9m<<8ft9ES=5VtqB_c&d`vOlD(??9^2&B*QYK2gc1J` z)-F;HP3nq(j}3ikU#{xDg1PC2RQtq<{;_8~jFt=7)wBOKvp(|EelPd#{sujVt0=0C zV>y6u=h$I7c5|XIGWlmgjI+#{BXx|Z-q`zlLVe_%{AW5sVR@@gdmM@OKKVc-@zLbW zi(Y!=9qV{?N&H1ypLRpeZ(`pat3?zLo}oB5&*&oA)AH0*Ql6A8mINd z*9>?fC`>O1#J;zEx0%4Y6&)Ka6{yxH7GL?cxWR*>1>?6Q)dY=M7BZCVYdl{Uk*@=M4t3lTu01So zm|nBI?61q)m>wLQ0fcN0lA7I#MeF<_eGr}ka(mR+_)tC=XBk+jXrN)4PS-#GGBh6C zVh$+qhi1KhU*o3Cs;8Z8+7UZ|B+c8`-LXR$%a~({0^&<~OIozM&ONpV5m?Xr%X;6J zHPbfw+@;d7ysOYvx&D`>@ciSko3vc)xE_aCx8E(@K)OW2sX4&C=#y)zHmLeKRQL)J z@<4d!{{8z1GQ)PR2L@jsquI7y>0lRv(GYu1?`IVlyh$&-(B`b_*W|DgYf4uOCj7Pf)Ny)$FnqZL9s} z^$EORRzVDhcfk5$Srj!&-F>qMW8USU>g^|AemgM7Wkut!G_6@S(VbX>k&}^?M@RWL zaT@fIa)p*hZZ|F5Y%9ogdPa`lz^7C_gkbQ(fhw^2ty6k6sAb$}?k=g&34bjQHblk< z9KSrPuC@NDyLL-J{K1_C(-{J5j1^!af|DIeZ{B8B*H5=E3I3iT&{%?OFZZ^Dwe^C$ z-S70icu{;E7EA=%u+}?KzoWotS5gRPP~N;fy(;Ztulx_g#IlyD_GF1*+7>%xM@g{q zbgKS%h9heDd~cs^KxV6&rhCAk0DC4VEeNWTsfwGiiy|8 z=3HRfCC=K`svkU0rgE?NlgO2>3B9b+Qs_uZy){xhvnLpzKcPmk-Msoy;J!dBbvVQ? zwYRq~Hsi`IOUGbX8pd*9BW$S84WEq|6tqZ>yR}nO%^*@6cK?Vo7@dvbrW!GqFWV&q~i-9`S6NT3sk>GVZY6 zj|eFy9zt5mL+AHqzq3Wa?1w^jYCj|0Wrjqy_7Fjf<(&qG47h{eLeOyYj63vCZ7oRe znPAxrIg0{F;qSmR2u#R5%=o>e?xSx_D!1G-@{gWsTnlc9c|yxqDZ-k04-6WD_AR1t z>Yfasx6){J*zzL>NEH!M3QAVe$vm=O;2ywX7D`&v3$s*&&vs@Yn|N>XYaw>N{1BY3 zqrI8}9bD)I`Mq9193e>$e5k2T>(?kS0;OSV0b{Rf_q@90YE2?o;GoTT53GLZ_+;jRA&3pFDft>9C)j;1|iZx^aeVttjQ>czhEynhURNH zu5*SoqKyT8Ss7)zqUUC?){8UAx_qN_^cEgT3T{3aX6X8+UkEGxmuh(^E zHowf4JI9POB8L&57TT(D5Kv(*paXLt6OX3f^Yit$&`0rHTIxsnX6P&t#4X zRffy&^G9u~LkBMi2vMc}jEkLDOR5F%P07A$qD3XuZ?a6F10wW5*r#@l9ATyJ*VTws z(-AI^-!mQXaHLdpe*&+o!yaQ> zHJD3aPyaOu_LnYeP!!3mzk3QC_S$#r*O6LDgop}}9*oqoL5{pVs$w{E2pZLO>=iny zHY)s@fITL*Si)ZnR!A1RSKC>CF-h`Ql7B76=%#QbN{9ba96t2h>=kak>2}2`(;1$H zQ8q6M&Bxvm3J{)l3s*0@H;WDz@K}g}G6gBo^!_a)caw0fOvivYRMWZ^I}Md`qK-ZAwcYp`kSwK`Ba{iu)wQ3BpbZr-ywfA6x12$yYnk~ zl4i8yG+oFBc_C%O9aGk z_j)J{#%f~`$#6br5$spDYAQUf>1(?2#ip17a$WpC?7e4DQ*HY%ij~I_JEC+E1nJT{ zA|g#d5JCw>1VT$d2))`s0i{ZBq7)HA3xo~|Qj-vh5JD(Qix5I5lt9?aN8fkm?0r6* zIdjgpe?BnbmYFP9u637RyXKiIRp~^0vRwaUi*-&kaIDKh^eWI)1yj0w?5CDeY&&G| z(bQ96TYic=c@JTswRMf|i$=cp%Q8OO#8&%zp8t}OntJ89QZ)bT=sNOt&#U#H zOTE|Cg1-sP01Lb}tId<2PqH_HjtsdX%!tvg9|AwRzH41I@*49((};i6G#7PJHz`a%$DGJkNMtk+_~G2ZUa`Q2e0@s-3o{89}1{!raO!4$(NBOxND&+$un)D#DW)z9dg2Zx(fYwz34sl?~Z+HiDECCDRRkku0YX`;H z-m`IVyjx~aT6!{xbe)DQ6VRKxV|=xUeof(7TgI%b=a~E=7>SliEl^iga}Tl*`F8CN zA_ztN-YIJ7{bsS5UhLRCU+5Y=)UZ_laH5tl0kDu~3r$x!01uCVj@}RLHbc)9+`4jF z*h=uuny8$!@K9z(qARl0PLXs^9@)nlSp|vDY}|IRXmM|XO*C3WwI@Wp^1ARkvB))f zT{)x~%sjX`GYhawb&WKcvSD41SuX__T)j!>kZbkeYEsnor0=Z{@{Dgra>pEf*LXkZ z5sCxVBHZLjf0)5s^-~5}ep%DS8QJgLoiW*17K3^q*rnn(dHpQR|G{N!#z&IV+{SOr z-ZO)o9oSRkxgzo|FCB8z+E2`>BO&qU&-4vivXagDkHU4t5WBg&IB{*{M4WCNAHX;s zn2|emviseor(N9?;=^BI39?_&v&-}t##5`Xw#|XTi{fsN9z7^9TuyOMRa<_4t21lJ zdDz=VM9CH!v`FcA6VjWhAn7-u?$t@H!JJXTzH3pTnFp*D1nM=-@2 zZJ8q{EzP$aOja7~d4v{vk4zxA((AFh`5#C-t*6Ah#_ABWpJmKk8Xn@;tA?o0n0wEw zy?z*59jISZL9MAinQ2jV^@T+#AIpZf>KGq~MdT4%yDKjFvg~L{d%??`pf71YBM-7H zvJ*uxB7+fyRh5KisLjbaPe_&VBcozGU@tb^>JFONcP|1Fywx(ILBYq=<6fpG_y`by zZCm>no^v6+RW5@A6Y;Vxe)jKy?55)-Wb#!a^hi81;avfWy0fdw%i zhhn&c+F8tg$z$z9*AlrggNACHW+taTNWXDHPtRWF|EYyC#5UJdG~H=_uWo=)uCWZQ zpLB2DxfimZZr{E*Xn#QSM>3HMl4cTql#=J&I+Vq&%`WAu%XfMX=tf?wqoupaFcq7! zatf5zf9edi*CnNF-FyCeR+pngn&c)HDiI)vUwpAxOI2Onul1=!Z|Hx9ro(0J-^2nz zXPfs_LYhX&>GWzg$8$Y^@H+>rDNWkxYf~E_%&$Dr(0KW$vcBB?&jIn|CH@pH&yB}= zp+ksVo5lSBT`{}hBHcXJ?r3mlqOeCC_x?_hqtmjPajD8Rm0aO=u%OZ+y0EHA)de=r zYfyZR!EVDJ%iQMG8GmY~uTxH&k@s*mNQru*P1B4~W12LVOm@6SOnl}FX|>X%eTtA= zCnCKT=UDTCH`(f(h&slhv=rXAX!)<&R|?_c2EmaWX5tsZSNY$F8{}DosnD(j{zC11 zi+H@3>{BEK{i zn^i0dr|<4=OrE+d^8INH=orgd_VHg>KtWM(oTSTd2A~l+!us70 zd=DIt=)l$b@;L%#au&?#boy*BH{$`g%;saR&h#6p>@pXsCHoN(a~?cVbQ z-?PLhd#K_aki;3+Sy|K>I4J#SF^u%Ef~EI^He@)^h9g3GCT^$x-@?;4tIFp_Ptm%qZ*&!)3fkD!c*BZxYGPH4=HVlFwB z_^QO{9IM5!1h9!T!c~|@_rjy^aDA z!--F@@4cA2tu9j zjDoUq-g*ll)y9Og+LT}@)VC0Yf1MRfOc$^H1+Te33MRZ7E=}?%v&_#;%FgBj5diuE zv~&9OY3JJo#zoGzf6tgna*aPhYmq}Kk^b3>o~zn49<*2CGh?U07V$A7QLF5gv$Yn=> zG`NuAsI<5ezV?E9?>4!^lVf~XuE}N6Xj&Ry_HJ6&L)OZ%!-*>g;bpvX(%55||2_l3 zX(rr|6y?c;!$g8-rKaIEB(@t;9E72j43J@(qY|&A$At2J4UuuMTXvAZl2X#QlOqdH z&qO;^Z0J)iw#*hDQbgT{oWP1;n}ee3klq?&sgh z<{JXxlxZeCY}lX!`eYf)8X89YaIJk7W0-PhAbCW&chLjIa4W65SJS_ET}n>a%!UpB zjJx1#V@l7}Ej68D#g3nPhQ%k-JzX%PJa2?gT~h5IVq=9 zL!OluTgBA$i?}^`@?fh8$yV5DCsxSH-j@-jC*EMP)e=C>socKAiBcG@9$&e1xqtBC z_xF#Atm1%!i2x{>%jY}E8zNv|3{&i0g`x>!ZC0e@{<&I7dSzLec-)54O%5d6&he5g1QtzNr?vth2VTfLc2q7XAGLnO_i=#2R=wpmZ!bgiWaj>vXti zN6K;VfyI*IDrY5$Q%=`p)JS~obJna(j=Y&JIa_QpaNl5U@p3#*OJd_^%lL`Os#&?D zbY&9hToJ>vfLy$`R4A*v8kI3)cCc*`pQC!yz2Sy?vCzOLU7N#K&sf%9)%VO3>(~A% z1-U7HlXGrVTxwv&JkO3#4yO}tNi8uE%cGk7-Z=cAPZ}2gz-1|!0T!C`3IM|4UD4Lo z#%3J#A%4Nh$T%L0uEoxJvDE^8*2mFbcS89*0@g#wEt=*m*(^7Ms(n{`ilzKMr z%4-en(T0&IulUzyD|y5>8iv^^hGFQh8lbjtBSl}S z!#Fucx6RsDP44gS3ujqQ&R60R<~Y3Ha|Dj$vZYE=cKa$i^Ql28qSiO`l0}9kviW|> zH8293(y(v?GA5z}cI<9Y7S|!2_Yyg=TaH&Et-{9NT3RgCk!|{1gUS1ClbGRW>RGFO zXE)Cwc>6aqrl;S(nCpP2t!D=_TE)BSnPs(m5&4|2NA5f){xGj0ji5(!BOD+|u;tf2 zjwo3{S9kGC%2^oShBUC8GQ8VGQSB91;OpRdJ-N4U#9zN~g{GmL@LY(jk1>S%pWL53 zktcTeyO%Xixq5zl(CVwF8(^3$4*5VQ=<~wWNnu9&t-a9-1wFHk?uo>(_Q$v2Ep8+u z!*Ec)BZLYS-gA*JGr#|$jizj8>jo`dlpGCJA{y9wKx!X7i@_;F0bXoAv;rIA65d0b zxpwDIHSewdhX?fSI=gRVmn7ZDqPaE_w)bkx_vXi{*kKk#WwRwD~&9}{apKUecwa;~B#HfHL4w|>zUgujPU9a3o!MIe{At5zA z8}oxQC67yiGPCmA_gTw;ctoez4Vww+H1x+Aiy72`hMA@8swb_RYzR^A9fwd`DdMH( zxhw~&p-XjMgZgqelG>-9^Z~vYqC~E>u|P_SjFs=s zY_TJwacB1m!BgfaEB{Xwh-oPi2DN+_v!8Q(D#3>+k$v@JS{qa_yy&H{`F(H(6joVd<=-E!cv-5&Z#*Cc1_bJG1+S=&ALM)K#G=3-b47hfxB@XH zu8(CDMe-tIV-l*g)VSVT)wGi4B&tW2)wPTr)j(T`6|jt9$J$>R2!q^-Zm3KsDyz3H z(WJZKfI**4%-PN3dI2@S0=NXjKTWks&AO1%!Md)DqLOpMvYXwpV@u_2XV&lsdln&? zleEDHDaalump$#R0%IDK+IV+tJ4lsF-ezU!m#gD+4Z{ZcMk{1 z-+q3%9s99+W=#H_oV@v900$S>a0qI$nGS8Oo<@<;5-0ipnWlZ8OD)eeMl@t`OSz-Y zpn|(iLODu7Zb`buKUngH++Et0*Y^U}=jPX8r+(3wC*RXGB|CEzZotnGIGolbUrPN& z$af}uFJ`1cNJh+kVO_z%V3HIqc^_ zl7P*2cq8>8tFw^Ua=<`ov`G(3yjRQjJ@h5`5A7SLYEnT4cVv3C<9dOB&Y|hc_1uh= zipuF2saZ~7XC_~lSrrnWoBKH!h)fv#@qYU5ZYFR;q7t(vJ!ej>u1ycus#HGtdS|CQ zHjE=y@Wc@p9{kc7IrlBLa8G{m&KQdh1MZzrh*x+phte27Ax$)eQi7#uWq8vQWAs?*# zCJa*C%9_B|5WVMWVa_!(tGauihw7CX+ZWf&!uGj#hUHpHO$UGzZl_h5oHrfEkyaO} zN7c_3MDVHoWO&FP0qv&kq~!J3j)F$eA_mXx=59eJzf*iTF|Faug`H(8287ZkGu?du z-l@;-{VypsX-yLoQAeL?*IkPBo8?c@t1wTNfX%N~1n+od)dZ^+(tBdM(RaD}ccQ#D zMoKqKW*9zOl(94gtbRp)^)j||C3vaHdB9Zr;Kc1iC9yMRk>li^If3Y)w0gYpw(KF*cgC{sJ9{{SymT3t1k>) z*CX1;5<2XZNhb&NE{GfD*(s~7@I@H^U}R+T}sE?2}O2}w;wH60pezoup5Ik56z)sghn_Zb3k;domvfP+VlYBM3Zmxaw~Gfi zH!f*i=89_cjU{x8){G^9*IlJ8C7pUX;8aMFiG#DVHep`gz|ypLQ3)}Y>Is|?x?aU9 z88q)BJupCU!?jpdT54t(L*N^3cEIFa85Nw7fT?49EQRQM7zs%n{Tw;uYT67W*cEJ< z359zz^zxs_sB3to#bpFEr!hlLJV-caE(T(|XL+^EM5Jq}EEH~u!*cljUQtA>6qMkHuJ}jKVj~nM?%Mo^@dwJDn3ScXj8>E$#v8iB@f4B=NJ`T-|Oh^r1#%;t6V4 zd{fsay^2&l;gz&#g^5<|GZzwVG~gCtu9_plv%C-dY~8Cg!Wgb(Ddh(!RY&rs+0)$C zEu&D4I4B$Q`{-}}QETDBnN6j4PEy4#QOX!>69a`ja97C%` z%((RRCA}%~-6}4NzRM>2NC6_B|2Yj)-}Sx#q_eKMXIZ!WgB2aQMi#HWjg=S^7i#n^0QoEmS| z7Jqj2PNV0=;R*zY05X|{A8yP3Hi991`Uvn4nFACQ`QI zLPRHhXG}{euenoq{%OZ4xa2olBI8Ydu%CD~uZjz*{PiNN`0Fi{XtkyOkyij;S-S4p zkpsGy?$bWBJ@o_9z{%WUpgKR3cQWI8Mhz@piLtKcxLQ;gGipYB@0e>cP(I^e)!ok8 zsV^qE@OZA$YN0>Gt<7)r!fA^24Kn)%>z7 ziRRSV?BF#W7p8s=d#C~2KyuxTV*I-~Zbv8AOXSG@07u>6`ctx@ipBAUj_Zjv-o9L%hHc_{KHz*@E{6$GQrA>y- zVM}!`ZhDAu+g~Z7kmHW!^B6##C{+E-&IO$=c_~6NY$7;-?3m)}QqpHAH0@$49REBc z50Nlnk^B0ca{O&xH`d8n5np2SrYz+ee!bBbY~J~R=TTjsJ=?i z$tqIJ{Th20i&;rL25fsnqfLfWoSlVW|+OK1?-pca3#;eV_`iaB4UP7y@mM~ znCyd8yCx~u1=lrtE;m(O$RwV^ft9inZB=*lvPcqd9Dwl8-OS)?g{DMh@j~iT$lHi_ zLM%m~vwFEE{X7=UKf2i&4jw03AG`=h-h)gqVrnp)HIvs~ym;ZYH4d2_Dx1*A4w!SX zhUT8*_Lk*vPKFN`f33;mEFyO3^1I7X=yu9BS`O6FYOmb^$2CAT*HSMgBIFXG}0Gw@`+>JjT?F`^-6Q&L1ZMMDw(E1Gf!7_V-pLN|vrCzmh z4pGe$@*O>su#%gYu`OIwu_F9|&$lb*IR%+q081 zCE`XC-%Dq*y%2Wp^jSIY`_Kc}Sgp|&7J^;tcr~?r_@5g8LickOJ*xkncE*jq;x)= zCQs3YHw(Z5iLYcaEuxALMqshHdf&Tp3Bl^&_psB2dUm@8k_hLIRbl(WJ40^1@qsD{ zT9ccy3f}Gf7m}S^KINyCl<~sn#iPIH^KHHtlNyWq9`P9IoN#)**i>`W z&LB{@G_Sp%gWWvJ;{=~gze7Bl=br&|V%;58kZ612<=?es3kt$zR=+r&b2;RR(dZ4| zTFb=j?4CUOdy^OqBlRoYfUf5QMH+)e-x`Xt)-f&hsuMvLh(lyO;(aJwTuw!+zPrt3 z>rfISFk85KwEXqO0@8!Bp}es{0ObnE-&-fEmL2NUwo-6jQeW#w^d#$kywwQ2UySQVPK#>-LF>NrD<0P)6^J7bmKqlK}@?8aiu ztmGbwiWai$3F#xq7`)@Uo^Ya# zSXZNER6LY?$LoHE<2zk`&@5-@NbW^&%A1ty#z@)fw-n$^+Rb8SmCJG-7n;yDgf4*w z=Nqf1z+(eYXVw+Vr>_{2V)>R8egg$AS9%*|0SgW5xK`jIS>T{Y@}H?`ix&kkzRkl- zc11Ju!${kM>j6J^^an_TWp*ts^oGQZYWI#*JZnA}K5t%Vm?LIW%9H=t!e;Zc8KssN zl-^-)4lt)Xgs}b6<1}FPy}i`rfy62~8?Z^BNgg(Z9l*q{I6v~|fLb2%A9T?7lS!0t zcK2wt=M$lSq;DI8rkM1uM6mJ9izZ2%qW~c-(XS>GsU)tc^r4e=)?yJCV82bI4 zS82&X$;RgEZMgL#9`!MyD&u|!Yz+5qTyWlaGFg>=74#LmrVPZs7H{B2_|LS4^J-}L zcm{HEPO9xstM%;IGAd`*mmDl|Z{8v;@I8|p_Z2@Ow9}mFKNVTOKUio;Sb#a@_@GUC z_+9AFz}G0$qWg+R;q2_~NmX8@A9FLCn^nhUQHwmM`N(t9C717AKi2nqMo($k%NGH3 zgp0=no+%-PQt*ZFThZ-}$CCzDlBThoVfFiz{gidLpf+S=9T}T7Z^DKvUBy?NvA9EC7%%n2DwM+>c?89fVB+ zJl1ZYo}|3I$o_tl<+Hv|ptG2ZYCA=Z2O-OYPr*ed1NJ}BWx<*g8wDYG56&r2s}jA( z%&a%X*M3LVR#M!RFb$LI?mOQm!>q{8yBiFD)ert5rPDVM6c;z}$KwiluhX;R{%5S>UyyO} zWx<;O^x6^->)w*aEOk&8G*0=Q-VK~@$=@i>(l(bi*b#C{Su8wRF?4I|R|?r!)vbi6 zAkh~7V`XvZuIg(Kk-deYkgRRqgyZ+coBcKGCkqB*EcnjU0OuITp&nrez%f>TfOBj2 zc(t~9u(esGS)OJOX$Lq=<)wBK?QsCU|72oHN#ordAU4U7IN<2g@Uy5~T^Vs&4LAf_ z!dLsXxcdnsS$>nnL#xA4Q!rgVEk}`vB?8X4Lg~;yD*~+LKkTCJyQ4L*jr^U2zT68nfbN16P)*w zDX-rhZfKT&I!NibmLpL#0956Wl~sO^az)%l4+5uSO>EBf7dC>n`=e%#z4)-36IGRs zj)UuL^=Y|7OLu!-WH{cd6QP9dUXP!9Iuq!W7Ot0?_l={1uzFkf0&^5^w_Y*T&A-qx zw%BIA_PKoOjN3VZW;O4rC^L4hVC@O|#Fp#pZiLl|{fJC@KVl{?(O`fo>I^aI_bow# z3Mf(F55hB3zV8kK=w}^>0}uIow-k)an2p8A4*T`5+C7wv#WwX&=UuPhy%PH#6wkXo zfIWHgV7_5AFH~8wb)bfhOh+NyEq{tQ*L=f~ z+Yy|gWrb{kXUAK;2AqfU3r)WF0FM&?YMZuDlZql9Sh{|PYl%K1ARBCt=((Tk0%*KF zddoq>V$Edeg=5Wka|_oSVDm0)ef19QCyPL!DDrEo;C{`uI(#Tt)pH_%?R^#8m7+7- zDKVc3D}ii1<=FapR#MD>AUND3d~(d+j^T9Vb$nwxnJ(sfJ^I*OY3M*h1G8t9d_0nT~Y@* z)M$^B*^lv3p<^F5lOd%UE3`6dVXPN5;J%3{WJ1E zaF+K6z>yb`qlAID>7JzG!Qm38oa+1B_BQ9U$*%8>s(P|Pv}0`E!M}1rUOTfXK>W;J zu4)aq*sc9*aarBHGwJl?s@80HRtAWHmnJ7%)9mxa7owVeKGTZ|{j0rgK^rFD!_{gW?^~|h`Vnt^# z0GIbQ@r{+ur7!>X31Ip{3;!F+Mf#ZMZ32iQrQ@dP%@I1h=T|o`^gKD&gTSi@oVtun zIRZ$`q~BiB=E=T4=H+FqBk8={qGU9|9X)L(b9t9L4mJE{N}I9%IJYS> z@#r8Qg^-*OV916`2G>XpPt?f5F-4~B%Hr_qUDmK2!rDw?OJ8~1wES-By4sG80$_Tq z9yM#=bbeZBj@1(*=RI_81TvOW^vsY0w_^jaz!i$w&A67k15{_i!QRiav#$&N#K`p+C{arhuw*|VY?Ok)Hl98UPr?V!Nx2NEfC3a^COq1n z%fk3|Qgsq~+&zDYo>?Ob!Rq+5S?=+gA?tKi55nn{7v5nYpcViCaCe|iCe#h%H~?dvX^ zGALElgp6Bh!cehz3Xr*PTKqX}9)mt9i;Yb}pXfIe&xd0q?>sA+)UK=eyl% zsZ-)MB3t7@IWcy-8}pW?_$4JNRm6Sw(JGx9`<=Bx9}z0}6WnY1q#r*>G~Fka(8`}u zl5nbJCA?~l1c(!SxJs>1_wXzaJzH0~;{p1Hy^_9E77y5=v{D>tP7N%-luZH=z`4qg z;?~76-PF`n={;|(a=g;?WT$=1@{6-JJTWNU7tf)jYJTtHe2nyZ<7CFxAY5&!t+PA# zRqr5^eg1`ILSO-|G|QI#zD#F=g#kHKZTrUyAM6}Wi!0!+Z`e@w+G@)|Dq1Hw557v8 z4E_FY?r-lPlHA`|u0~#Bxk;voZm}0Xp)})SH|yOAM%YY|p*0V?t|mxCf6~Rzg8p6H zVcVx{P-0@^`hm$AbJRVD(KG36J^TkvzfK)-^1f(h);d$z|5br>Zcup3*}uzEIbd)N zTw>W8=$X>FFz za3xOu{^o7kz?t!g%=%pKP>*-}9UGgiU6sMUcXeEBYzc;Y`)bZt$^iDp^y{J`u%SM_ z?phsjJOjYik@n+21kUkH)q^^GWQMr&Ip;@@ZWH9-fmXScUmqtgI$hJyY4gWHdD@@mz9d`<#T3?VQCKo~7g_Dfgf~Dv`N>IrZ z$Jrje*(i)Sft=x~Qfv1jVhqcW#K_R+CV~h(x45^Tf;CXrgiuRIp!j4UR#G5WRbafv zPF_+D32qD%czomu5GxUT6*Upq1UMMNPqFsk!x%41EPd)Cel`a0nNNCPa$j-sBF>~| zfk&eyO}E%^=}TG&j?sXe*F4PbKjSaRYcRy&C6Y41AY75BrT0jgI4K?8~~*=?viH}{%#&DG@-0-5ih z6$cM^jey%x>Wwh@Zq?qg0;EbJNm_z6a>Jy@)9r`$Od>f<5@bqkIXJY2%3;)qK$=aq zULZfQ@*~^`1Q>d-&#GzS9iZZ~hJ1959)4`ZSh~*LzYrS!JSAp1PUCVHaE|??mSOL_ z)EMB^-IK%?7X=xO_&7Jn!3s5>o9k}%A22%KXOmGeBKxUh-YN5jq1xst1)xWXj~I5< zZ}2U_MaiBNFa*7d8vvx||ES!HQCtwTp18}3;*y%2dmJh|-5oK6Rh6ZWF8bl4bAF%y z#tn(ST~MpARo1X;w)6$UZ%U_+ys_DyGzr64qQn6W`iQC-l4|GX3($(pc9@BaB+S*HdPI z&MxoD4#I5XfrJ+Iwn!-BqXP%j69;MB0Um~Y z%ok3X1gPYDsIf=yJ$&XfJ5iR)DzuArbE9tgG?bRiqSxXvR_$fT1aD|Ak4=T(5FeAH zn?*iOIMME9?=LC?AW^>a-~hr(W)HDc$DK%MVQd$-?AdGK0JI|MEob7*aQxr$| z5)bN>VFOvmfId}|s2gOSf9{0?UKuji{QUeNnN&Usm?PrQcdA)v10gIOrUS>BU*)&7 z$W3JGG_%Z?R5~VKHJgfh0mGrouF0$YQ+=$&$U|BS?gt1-jXm?Ii?b=VH)|$6Uj_h`vB1x?X7uBT!-XU1FkyxgO**lvG>_?8%pkHO^XZe{dWb zMiWVvqP8zW5&>D83yILHa1K--t{4h=<^jyzGEZvos`Ov>IOX)!b_83jy?^Zu6_q3h zk&E0+Srp8|m{ykkXE25B0);}+u{@=vQ{CiG$06XGUuqS{q6yVdT#Iwi&zdHxp%(mw zLNadboi1&gD=ZV(7R3TNy|?BRZ7f&IY|8Zb^c8^CK;9N}oAyh^K2`BqClQNj)ORyJ zm(2pb2kW}P2U4AhL;+}MU|^5E7#{pmdb$Dudl59Z`M51UR%Le*5XA-C{?70X!M28< znyH8v5~p z)_gtf^LZBgyhESw=10lrC%kmGY8QRCbaZXcLB!^|yHmPHgeUK3uGibm+HpsZ(E~MI zW#WOkkqr0b_52p^RoS?DIKcNU@wTPx!T{|^H=q+?{P^+ye$H^&7Mzn~pvO%CdE1N- zr!N}^6%KGLjwjOLj?yRXocP4TcTAQk z+1gfiM2-c0INc%!x$8WK5g{FKfc?VxWZfbq4O4J@wF&`_b@4l!i4_8!zFO_7>=M!l zXfixT4FMzm$hsvVAvjpr+1Uj8XhH1zl%|C8z$s2WaJ)HwhTSVMFKe;O%78;e%D%f- z>&myG!|Q1rIU)P)(=H1Y*EsD)R#UIFQyr%530z9VzK>nM>?MQVKsDY%8&E9&P#Lo1 zw-XaK`@{(-2qat0?}MCk6CQI#-c(U(XT?#Y|GER2wnKRT$nBU4N~F#Z0)mw4xJu9H zh8ebWJ<(rFih9lM;x;0x5-#uW#5L&J{ie06h5wo~p=7+FLf}dU)3)S&zwDHbXo;E3FV`GccUP~*xDb)R3>v>q65hleu^ZKDGo;Sr1 zmNgr;E{tpSbX@A-YPdG~uq9g3@?*a?{L>D!>X1Sl1~=La zWXqi4(x+8xqrHI5m&=1*v;>Gh;c>1Y(%0DTK1b2;=X#iUib=z%Bi$~2l4lgooB`>E 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

mF-#_=V1^n|JIBa-v~Bkf_w!-u_G{}jOrqb86H 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%0ACIY|niK74k0 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

    nbuL{7&qN}pI%%7v6ml1h8GT? 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 z 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%0Aptc=yK~9 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%0D&sLBG^EBztD$5at7ou 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