diff --git a/app/section_2_customer_runnable.py b/app/section_2_customer_runnable.py new file mode 100644 index 0000000..6471bc1 --- /dev/null +++ b/app/section_2_customer_runnable.py @@ -0,0 +1,50 @@ +from typing import List + + +class CusotmerPiplineComponent: + steps: List["CustomerRunnable"] + + def __init__(self, *steps): + self.steps = steps + + def __or__(self, other: "CusotmerPiplineComponent"): + return CusotmerPiplineComponent( + *self.steps, other + ) + + def __ror__(self, other: "CusotmerPiplineComponent"): + print(other) + + def __str__(self): + return ",".join(map(lambda item: item.name, self.steps)) + + def invoke(self,**kwargs): + print("kwargs:", kwargs) + for item in self.steps: + print(item.name) + + +class CustomerRunnable: + name: str + + def __init__(self, name: str): + self.name = name + + def __or__(self, other: "CustomerRunnable"): + # print(self.name,",",other.name) + return CusotmerPiplineComponent( + self, other + ) + + def __ror__(self, other: "CustomerRunnable"): + if not other: + return + # print(self.name,"+",other.name) + + def __str__(self): + return self.name + + +if __name__ == '__main__': + res = CustomerRunnable("a") | CustomerRunnable("b") | CustomerRunnable("c") + res.invoke(**{"input":12}) \ No newline at end of file diff --git a/content/section_1.ipynb b/content/section_1.ipynb index dc641af..542ea30 100644 --- a/content/section_1.ipynb +++ b/content/section_1.ipynb @@ -29,8 +29,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T16:35:35.528615Z", - "start_time": "2024-06-13T16:35:35.050369Z" + "end_time": "2024-06-18T13:28:22.199803Z", + "start_time": "2024-06-18T13:28:21.515717Z" } }, "cell_type": "code", @@ -56,8 +56,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T16:35:46.518080Z", - "start_time": "2024-06-13T16:35:42.832893Z" + "end_time": "2024-06-18T13:28:40.308087Z", + "start_time": "2024-06-18T13:28:22.201318Z" } }, "cell_type": "code", @@ -67,7 +67,7 @@ { "data": { "text/plain": [ - "AIMessage(content='你好,我是OpenAI的人工智能助手。我可以帮你回答问题、提供信息、并帮助你完成各种任务。', response_metadata={'token_usage': {'completion_tokens': 43, 'prompt_tokens': 13, 'total_tokens': 56}, 'model_name': 'gpt-4', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-48d47b95-f12d-4593-833a-077fd52fcf3c-0', usage_metadata={'input_tokens': 13, 'output_tokens': 43, 'total_tokens': 56})" + "AIMessage(content='你好!我是ChatGPT,一个由OpenAI训练的大型语言模型。我可以帮助解答问题、提供信息、聊天等等。有什么我可以帮你的吗?', response_metadata={'token_usage': {'completion_tokens': 59, 'prompt_tokens': 13, 'total_tokens': 72}, 'model_name': 'gpt-4', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-ab503481-2ac9-4e1c-ab9e-d02e2ef89e2c-0', usage_metadata={'input_tokens': 13, 'output_tokens': 59, 'total_tokens': 72})" ] }, "execution_count": 2, @@ -89,8 +89,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T15:19:52.217780Z", - "start_time": "2024-06-13T15:19:45.010014Z" + "end_time": "2024-06-18T13:28:50.368283Z", + "start_time": "2024-06-18T13:28:40.309155Z" } }, "cell_type": "code", @@ -119,11 +119,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='看他们如何为你闪耀\\n还有你所做的一切\\n是的,他们都是黄色的\\n我过来了\\n我为你写了一首歌\\n还有你做的所有事情\\n那首歌叫做“黄色”\\n然后轮到我了\\n哦,做这件事情真是太棒了' response_metadata={'token_usage': {'completion_tokens': 98, 'prompt_tokens': 87, 'total_tokens': 185}, 'model_name': 'gpt-4', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None} id='run-88b99115-3b2c-405b-abc5-27aa8899685c-0' usage_metadata={'input_tokens': 87, 'output_tokens': 98, 'total_tokens': 185}\n" + "content='看它们如何为你闪耀\\n以及你所做的一切\\n是的,它们全都是黄色的\\n我走了过来\\n我为你写了一首歌\\n以及你所做的一切\\n这首歌叫做“黄色”\\n然后轮到我了\\n哦,这是多么了不起的事情' response_metadata={'token_usage': {'completion_tokens': 100, 'prompt_tokens': 87, 'total_tokens': 187}, 'model_name': 'gpt-4', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None} id='run-90c06452-82db-404e-940a-6d720243c613-0' usage_metadata={'input_tokens': 87, 'output_tokens': 100, 'total_tokens': 187}\n" ] } ], - "execution_count": 9 + "execution_count": 3 }, { "metadata": {}, @@ -138,8 +138,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T15:19:52.235707Z", - "start_time": "2024-06-13T15:19:52.231152Z" + "end_time": "2024-06-18T13:28:50.382258Z", + "start_time": "2024-06-18T13:28:50.369448Z" } }, "cell_type": "code", @@ -154,15 +154,15 @@ { "data": { "text/plain": [ - "'看他们如何为你闪耀\\n还有你所做的一切\\n是的,他们都是黄色的\\n我过来了\\n我为你写了一首歌\\n还有你做的所有事情\\n那首歌叫做“黄色”\\n然后轮到我了\\n哦,做这件事情真是太棒了'" + "'看它们如何为你闪耀\\n以及你所做的一切\\n是的,它们全都是黄色的\\n我走了过来\\n我为你写了一首歌\\n以及你所做的一切\\n这首歌叫做“黄色”\\n然后轮到我了\\n哦,这是多么了不起的事情'" ] }, - "execution_count": 10, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], - "execution_count": 10 + "execution_count": 4 }, { "metadata": {}, @@ -176,8 +176,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T15:21:29.712149Z", - "start_time": "2024-06-13T15:21:13.466240Z" + "end_time": "2024-06-18T13:28:57.548445Z", + "start_time": "2024-06-18T13:28:50.387233Z" } }, "cell_type": "code", @@ -190,15 +190,15 @@ { "data": { "text/plain": [ - "'看他们如何为你闪耀\\n和你所做的一切\\n是的,他们都是黄色的\\n我走过来\\n我为你写了一首歌\\n和你所做的一切\\n这就是名为“黄色”的那首歌\\n然后我开始轮到我\\n哦,做了这样的事情真是太了不起了'" + "'看它们如何为你闪耀\\n和你所做的一切\\n是的,它们全都是黄色的\\n我来了\\n我为你写了一首歌\\n和你所做的一切\\n它被称为“黄色”\\n于是我轮到我了\\n哦,做这件事真是太棒了'" ] }, - "execution_count": 13, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], - "execution_count": 13 + "execution_count": 5 }, { "metadata": {}, @@ -229,8 +229,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T15:38:19.189956Z", - "start_time": "2024-06-13T15:38:19.185742Z" + "end_time": "2024-06-18T13:28:57.557293Z", + "start_time": "2024-06-18T13:28:57.550225Z" } }, "cell_type": "code", @@ -246,7 +246,7 @@ ], "id": "cda983a976bd0b4a", "outputs": [], - "execution_count": 15 + "execution_count": 6 }, { "metadata": {}, @@ -257,8 +257,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T15:40:55.015489Z", - "start_time": "2024-06-13T15:40:55.011524Z" + "end_time": "2024-06-18T13:28:57.569174Z", + "start_time": "2024-06-18T13:28:57.559177Z" } }, "cell_type": "code", @@ -292,12 +292,12 @@ "langchain_core.prompt_values.ChatPromptValue" ] }, - "execution_count": 33, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], - "execution_count": 33 + "execution_count": 7 }, { "metadata": {}, @@ -308,8 +308,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T15:40:38.185104Z", - "start_time": "2024-06-13T15:40:38.181207Z" + "end_time": "2024-06-18T13:28:57.572555Z", + "start_time": "2024-06-18T13:28:57.570110Z" } }, "cell_type": "code", @@ -323,12 +323,12 @@ " HumanMessage(content=\"\\nLook how they shine for you\\nAnd everything you do\\nYeah' they were all Yellow\\nI came along\\nI wrote a song for you\\nAnd all the things you do\\nIt was called Yellow\\nSo then I took my turn\\nOh what a thing to have done\\n\")]" ] }, - "execution_count": 32, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], - "execution_count": 32 + "execution_count": 8 }, { "metadata": {}, @@ -339,8 +339,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T15:46:50.356501Z", - "start_time": "2024-06-13T15:46:39.338836Z" + "end_time": "2024-06-18T13:29:08.233368Z", + "start_time": "2024-06-18T13:28:57.573238Z" } }, "cell_type": "code", @@ -366,18 +366,18 @@ "output_type": "stream", "text": [ "너를 위해 어떻게 빛나는지 봐\n", - "그리고 네가 하는 모든 일에\n", + "그리고 네가 하는 모든 것들\n", "그래, 그들은 모두 노란색이었어\n", - "나는 왔어\n", - "나는 너를 위한 노래를 썼어\n", - "그리고 너가 하는 모든 일에 대해\n", - "그것은 '노란색'이라고 불렸어\n", - "그래서 차례를 기다렸어\n", - "오, 그런 일을 한 것이야\n" + "나는 이곳에 왔어\n", + "너를 위한 노래를 썼어\n", + "그리고 네가 하는 모든 것들\n", + "그것은 '노란색'이라는 제목이었어\n", + "그래서 나는 차례를 가졌어\n", + "아, 그런 일을 한 것이 참 대단한 일이었어\n" ] } ], - "execution_count": 36 + "execution_count": 9 }, { "metadata": {}, @@ -388,8 +388,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-13T15:47:32.751931Z", - "start_time": "2024-06-13T15:47:26.257325Z" + "end_time": "2024-06-18T13:29:17.785976Z", + "start_time": "2024-06-18T13:29:08.234077Z" } }, "cell_type": "code", @@ -399,15 +399,15 @@ { "data": { "text/plain": [ - "'看看我如何为你闪耀\\n还有你做的所有事情\\n是的,它们都是黄色的\\n我来了\\n我为你写了一首歌\\n关于你做的所有事情\\n它被称为“黄色”\\n所以我等待着轮到我\\n哦,我做过这样的事情'" + "'看看你是如何闪耀的\\n以及你所做的一切\\n是的,它们都是黄色的\\n我来到这里\\n为你写了一首歌\\n以及你所做的一切\\n它的标题是\"黄色\"\\n所以我轮到了\\n哦,做这样的事情真是太棒了'" ] }, - "execution_count": 38, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], - "execution_count": 38 + "execution_count": 10 }, { "metadata": {}, @@ -432,15 +432,20 @@ "id": "ccf6c60dae8e1709" }, { - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-18T13:29:17.789707Z", + "start_time": "2024-06-18T13:29:17.787840Z" + } + }, "cell_type": "code", - "outputs": [], - "execution_count": null, "source": [ "# ! pip install \"langserve[all]\"\n", "# 我已经在requirements.txt中已经安装过了,这里不需要了" ], - "id": "a1b1183955736351" + "id": "a1b1183955736351", + "outputs": [], + "execution_count": 11 }, { "metadata": {}, @@ -449,7 +454,12 @@ "id": "b7e21d291f347755" }, { - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-18T13:29:18.191364Z", + "start_time": "2024-06-18T13:29:17.790651Z" + } + }, "cell_type": "code", "source": [ "import uvicorn\n", @@ -496,8 +506,16 @@ "# uvicorn.run(app, host=\"localhost\", port=8000)" ], "id": "ac3255e570257601", - "outputs": [], - "execution_count": null + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "这里的代码需要新建一个py文件来跑,因为jupyter-book里面不能再启动event loop了\n" + ] + } + ], + "execution_count": 12 }, { "metadata": {}, diff --git a/content/section_2.ipynb b/content/section_2.ipynb index 90a2baa..fcbb9cc 100644 --- a/content/section_2.ipynb +++ b/content/section_2.ipynb @@ -9,34 +9,234 @@ "LangChain表达语言,简写为LCEL,是一种声明式的方式来链接LangChain组件。\n", "\n", "## 优点\n", - "- 一流的流媒体支持 \n", - " 当您使用LCEL构建链时,您将获得最佳的首次令牌时间(直到第一个输出块出现所经过的时间)。对于某些链,这意味着例如我们直接从LLM向流媒体输出解析器传输令牌,并且您以与LLM提供商输出原始令牌相同的速度返回解析后的增量输出块。\n", - "- 异步支持 \n", - " 任何使用LCEL构建的链都可以通过同步API(例如,在原型设计时在你的Jupyter笔记本中)以及异步API(例如,在LangServe服务器中)进行调用。这使得在原型和生产环境中使用相同的代码成为可能,具有出色的性能,并能够在同一服务器上处理许多并发请求。\n", - "- 优化的并行执行 \n", - " 每当您的LCEL链中有可以并行执行的步骤(例如,如果您从多个检索器获取文档),我们会在同步和异步接口中自动进行,以实现尽可能小的延迟。\n", - "- 重试和回退 \n", - " 为您的LCEL链的任何部分配置重试和回退。这是使您的链在大规模下更可靠的好方法。我们目前正在努力添加对重试/回退的流媒体支持,这样您就可以在不增加延迟成本的情况下获得额外的可靠性。\n", - "- 访问中间结果 \n", - " 对于更复杂的链条,通常在最终输出生成之前访问中间步骤的结果非常有用。这可以用于让终端用户知道某些事情正在发生,甚至只是调试你的链条。你可以流式传输中间结果,这在每个LangServe服务器上都可用。\n", - "- 输入和输出模式 \n", - " 输入和输出模式为每个 LCEL 链提供从链的结构推断出的 Pydantic 和 JSONSchema 模式。这可以用于验证输入和输出,是 LangServe 的一个重要组成部分。\n", - "- 无缝LangSmith追踪 \n", - " 随着链条变得越来越复杂,了解每一步到底发生了什么变得越来越重要。使用LCEL,所有步骤都会自动记录到LangSmith,以实现最大的可观察性和调试能力。\n", - "- 无缝LangServe部署 \n", - " 使用LCEL创建的任何链都可以轻松通过LangServe进行部署。\n", + "以官网为准:https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel\n", + "我觉得最大的优点是 **具象化**特别好,很清晰的表达了chain的概念,而不是按照之前的构建对象来做。\n", "\n", "## Runnable接口\n", - " 为了尽可能简化创建自定义链条的过程,我们实现了一个“Runnable”协议。许多LangChain组件都实现了Runnable协议,包括聊天模型、LLM、输出解析器、检索器、提示模板等。此外,还有一些有用的基本构件可以用于处理可运行事务,您可以在下面阅读相关信息。\n", - "这是一个标准接口,使得定义自定义链以及以标准方式调用它们变得容易。标准接口包括:\n" + "`Runnable`接口可以简化chain的过程,LangChain的很多组件都实现了Runnable的协议,通过它实现了Pipline的概念,它重写了 `|`表达式,可以将他的输出作为写一个组件的输入。就像shell编程的管道一样,如:\n", + "```shell\n", + "cat /tmp/test.log | grep task_id\n", + "```\n", + "LangChain中实现的他组件有,chat models, LLMs, output parsers, retrievers, prompt templates等。\n", + "\n", + "`Runnable`接口方法和功能如下\n", + "- `stream`: 响应值流式返回\n", + "- `invoke`: 调用\n", + "- `batch`: 批量调用\n", + "同时他也支持异步方法\n", + "- `astream`: 异步流式返回\n", + "- `ainvoke`: 异步调用\n", + "- `abatch`: 异步批量调用\n", + "- `astream_log`: 异步流式返回,在最终的响应中,会增加中间步骤\n", + "- `astream_events`: 链中发生的beta流事件(在 langchain-core 0.1.14 中引入)\n", + "\n", + "### LangChain中实现Runnable的component的输入输出\n", + "![](../resource/img_5.png)\n", + "\n", + "# Runnable接口的实现\n", + "\n", + "Runnable接口是LangChain Expression Language (LCEL)的核心。通过它指定了规范,实现了管道符运算\n", + "实现管道符运算的重点在于 重写了python中的`__or__`和`__ror__`方法\n", + "\n", + "基本的思路\n", + "1. 定义`Runnable`接口,重写`__or__`和`__ror__`方法。\n", + "2. 在上面的方法中返回一个新的对象(`RunnableSequence`),新的对象封装了此对象和传递进来的写一个阶段。\n", + "3. 在封装的新对象中,也重写上面两个方法。\n", + "4. 之后的操作都是在`RunnableSequence`和`Runnable`对象的`__or__`和`__ror__`中,将传递进来的对象封装为`RunnableSequence`,在`RunnableSequence`中记录了 中间步骤\n", + "5. 通过层层包装,最后返回的是`RunnableSequence`对象,在这个对象中封装了所有的步骤。\n", + "\n", + "## Runnable\n", + "```python\n", + "class Runnable(Generic[Input, Output], ABC):\n", + " name: Optional[str] = None\n", + " \"\"\"The name of the runnable. Used for debugging and tracing.\"\"\"\n", + " ## 封装为RunnableSequence\n", + " def __or__(\n", + " self,\n", + " other: Union[\n", + " Runnable[Any, Other],\n", + " Callable[[Any], Other],\n", + " Callable[[Iterator[Any]], Iterator[Other]],\n", + " Mapping[str, Union[Runnable[Any, Other], Callable[[Any], Other], Any]],\n", + " ],\n", + " ) -> RunnableSerializable[Input, Other]:\n", + " \"\"\"Compose this runnable with another object to create a RunnableSequence.\"\"\"\n", + " return RunnableSequence(self, coerce_to_runnable(other))\n", + " ## 封装为RunnableSequence,将other和self调换\n", + " def __ror__(\n", + " self,\n", + " other: Union[\n", + " Runnable[Other, Any],\n", + " Callable[[Other], Any],\n", + " Callable[[Iterator[Other]], Iterator[Any]],\n", + " Mapping[str, Union[Runnable[Other, Any], Callable[[Other], Any], Any]],\n", + " ],\n", + " ) -> RunnableSerializable[Other, Output]:\n", + " \"\"\"Compose this runnable with another object to create a RunnableSequence.\"\"\"\n", + " return RunnableSequence(coerce_to_runnable(other), self)\n", + "```\n", + "\n", + "## RunnableSequence\n", + "```python\n", + "class RunnableSequence(RunnableSerializable[Input, Output]):\n", + " first: Runnable[Input, Any]\n", + " \"\"\"The first runnable in the sequence.\"\"\"\n", + " middle: List[Runnable[Any, Any]] = Field(default_factory=list)\n", + " \"\"\"The middle runnables in the sequence.\"\"\"\n", + " last: Runnable[Any, Output]\n", + " \"\"\"The last runnable in the sequence.\"\"\"\n", + " \n", + " ## 封装为 RunnableSequence\n", + " def __or__(\n", + " self,\n", + " other: Union[\n", + " Runnable[Any, Other],\n", + " Callable[[Any], Other],\n", + " Callable[[Iterator[Any]], Iterator[Other]],\n", + " Mapping[str, Union[Runnable[Any, Other], Callable[[Any], Other], Any]],\n", + " ],\n", + " ) -> RunnableSerializable[Input, Other]:\n", + " if isinstance(other, RunnableSequence):\n", + " return RunnableSequence(\n", + " self.first,\n", + " *self.middle,\n", + " self.last,\n", + " other.first,\n", + " *other.middle,\n", + " other.last,\n", + " name=self.name or other.name,\n", + " )\n", + " else:\n", + " return RunnableSequence(\n", + " self.first,\n", + " *self.middle,\n", + " self.last,\n", + " coerce_to_runnable(other),\n", + " name=self.name,\n", + " )\n", + " ## 这也是封装为RunnableSequence,只不过,将self和other的顺序变了一下\n", + " def __ror__(\n", + " self,\n", + " other: Union[\n", + " Runnable[Other, Any],\n", + " Callable[[Other], Any],\n", + " Callable[[Iterator[Other]], Iterator[Any]],\n", + " Mapping[str, Union[Runnable[Other, Any], Callable[[Other], Any], Any]],\n", + " ],\n", + " ) -> RunnableSerializable[Other, Output]:\n", + " if isinstance(other, RunnableSequence):\n", + " return RunnableSequence(\n", + " other.first,\n", + " *other.middle,\n", + " other.last,\n", + " self.first,\n", + " *self.middle,\n", + " self.last,\n", + " name=other.name or self.name,\n", + " )\n", + " else:\n", + " return RunnableSequence(\n", + " coerce_to_runnable(other),\n", + " self.first,\n", + " *self.middle,\n", + " self.last,\n", + " name=self.name,\n", + " )\n", + "```\n", + "\n", + "## 按照思路自己写一个" + ], + "id": "40a151bacfad1fdb" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-18T13:27:44.429696Z", + "start_time": "2024-06-18T13:27:44.424091Z" + } + }, + "cell_type": "code", + "source": [ + "from typing import List\n", + "\n", + "\n", + "class CusotmerPiplineComponent:\n", + " ## 通过\n", + " steps: List[\"CustomerRunnable\"]\n", + "\n", + " def __init__(self, *steps):\n", + " self.steps = steps\n", + "\n", + " def __or__(self, other: \"CusotmerPiplineComponent\"):\n", + " return CusotmerPiplineComponent(\n", + " *self.steps, other\n", + " )\n", + "\n", + " def __ror__(self, other: \"CusotmerPiplineComponent\"):\n", + " print(other)\n", + "\n", + " def __str__(self):\n", + " return \",\".join(map(lambda item: item.name, self.steps))\n", + "\n", + " def invoke(self,**kwargs):\n", + " print(\"kwargs:\", kwargs)\n", + " for item in self.steps:\n", + " print(item.name)\n", + "\n", + "\n", + "class CustomerRunnable:\n", + " name: str\n", + "\n", + " def __init__(self, name: str):\n", + " self.name = name\n", + "\n", + " def __or__(self, other: \"CustomerRunnable\"):\n", + " # print(self.name,\",\",other.name)\n", + " return CusotmerPiplineComponent(\n", + " self, other\n", + " )\n", + "\n", + " def __ror__(self, other: \"CustomerRunnable\"):\n", + " if not other:\n", + " return\n", + " # print(self.name,\"+\",other.name)\n", + "\n", + " def __str__(self):\n", + " return self.name\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " res = CustomerRunnable(\"a\") | CustomerRunnable(\"b\") | CustomerRunnable(\"c\")\n", + " print(res.steps)\n", + " print(type(res))\n", + " res.invoke(**{\"input\":12})" ], - "id": "23a6f647250a9ab" + "id": "408f24cdf177dc6a", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(<__main__.CustomerRunnable object at 0x1120f15d0>, <__main__.CustomerRunnable object at 0x1120f2c50>, <__main__.CustomerRunnable object at 0x1120f0d10>)\n", + "\n", + "kwargs: {'input': 12}\n", + "a\n", + "b\n", + "c\n" + ] + } + ], + "execution_count": 9 }, { "metadata": {}, "cell_type": "markdown", - "source": "todo 解析runnable接口的实现原理", - "id": "98493c2c4b73324e" + "source": [ + "在上面的例子中,会包装为`CusotmerPiplineComponent`对象,最终的到的res是`CusotmerPiplineComponent`,最终通过`invoke`调用。\n", + "\n", + "到此,这一章节就结束了。" + ], + "id": "c137275daf610c87" } ], "metadata": { diff --git a/resource/img_5.png b/resource/img_5.png new file mode 100644 index 0000000..6fc0b66 Binary files /dev/null and b/resource/img_5.png differ