Gradio Agents 和 MCP 黑客马拉松
获奖者Gradio Agents 和 MCP 黑客马拉松
获奖者Gradio 具有 块 功能,可轻松布局应用程序。要使用此功能,您需要堆叠或嵌套布局组件并创建它们的层次结构。对于小型项目来说,这不难实现和维护,但项目变得更复杂后,这种组件层次结构将变得难以维护和重用。
在本指南中,我们将探讨如何封装布局类,以创建更易于维护和阅读的应用程序,同时不牺牲灵活性。
我们将参考这个 Huggingface Space 示例中的实现。
封装实用程序包含两个重要的类。第一个是 `LayoutBase` 类,另一个是 `Application` 类。
为简洁起见,我们将查看它们的 `render` 和 `attach_event` 函数。您可以从示例代码中查看完整实现。
那么,我们从 `LayoutBase` 类开始。
渲染函数
我们来看看 `LayoutBase` 类中的 `render` 函数
# other LayoutBase implementations
def render(self) -> None:
with self.main_layout:
for renderable in self.renderables:
renderable.render()
self.main_layout.render()
这最初可能有点令人困惑,但如果您考虑默认实现,就会很容易理解。让我们来看一个示例
在默认实现中,我们正在做的是:
with Row():
left_textbox = Textbox(value="left_textbox")
right_textbox = Textbox(value="right_textbox")
现在,请注意 Textbox 变量。这些变量的 `render` 参数默认为 true。因此,当我们使用 `with` 语法创建这些变量时,它们会在 `with` 语法下调用 `render` 函数。
我们知道 `render` 函数是在构造函数中通过 `gradio.blocks.Block` 类的实现调用的
class Block:
# constructor parameters are omitted for brevity
def __init__(self, ...):
# other assign functions
if render:
self.render()
所以我们的实现是这样的
# self.main_layout -> Row()
with self.main_layout:
left_textbox.render()
right_textbox.render()
这意味着通过在 `with` 语法下调用组件的渲染函数,我们实际上是在模拟默认实现。
现在我们考虑两个嵌套的 `with` 语句,看看外部的 `with` 是如何工作的。为此,我们用 `Tab` 组件来扩展我们的示例
with Tab():
with Row():
first_textbox = Textbox(value="first_textbox")
second_textbox = Textbox(value="second_textbox")
这次请注意 Row 和 Tab 组件。我们已经在上面创建了 Textbox 变量,并使用 `with` 语法将它们添加到 Row 中。现在我们需要将 Row 组件添加到 Tab 组件中。您可以看到 Row 组件是使用默认参数创建的,因此其渲染参数为 true,这就是为什么渲染函数将在 Tab 组件的 `with` 语法下执行的原因。
为了模仿这种实现,我们需要在 `main_layout` 变量的 `with` 语法之后调用 `main_layout` 变量的 `render` 函数。
所以实现看起来像这样
with tab_main_layout:
with row_main_layout:
first_textbox.render()
second_textbox.render()
row_main_layout.render()
tab_main_layout.render()
默认实现和我们的实现是相同的,但是我们是自己调用 `render` 函数。所以这需要一些额外的工作。
现在,我们来看看 `attach_event` 函数。
附加事件函数
该函数未实现,因为它特定于类,因此每个类都必须实现其 `attach_event` 函数。
# other LayoutBase implementations
def attach_event(self, block_dict: Dict[str, Block]) -> None:
raise NotImplementedError
查看 `Application` 类的 `attach_event` 函数中的 `block_dict` 变量。
# other Application implementations
def _render(self):
with self.app:
for child in self.children:
child.render()
self.app.render()
从 `LayoutBase` 类的 `render` 函数的解释中,我们可以理解 `child.render` 部分。
那么我们来看看底部,为什么我们要调用 `app` 变量的 `render` 函数呢?调用这个函数很重要,因为如果我们查看 `gradio.blocks.Blocks` 类中的实现,可以看到它正在将组件和事件函数添加到根组件中。换句话说,它正在创建和构建 Gradio 应用程序。
附加事件函数
让我们看看如何将事件附加到组件
# other Application implementations
def _attach_event(self):
block_dict: Dict[str, Block] = {}
for child in self.children:
block_dict.update(child.global_children_dict)
with self.app:
for child in self.children:
try:
child.attach_event(block_dict=block_dict)
except NotImplementedError:
print(f"{child.name}'s attach_event is not implemented")
您可以从示例代码中看到为什么 `LayoutBase` 类中使用了 `global_children_list`。有了它,应用程序中的所有组件都被收集到一个字典中,因此组件可以通过其名称访问所有组件。
这里再次使用 `with` 语法来将事件附加到组件。如果我们查看 `gradio.blocks.Blocks` 类中的 `__exit__` 函数,可以看到它正在调用 `attach_load_events` 函数,该函数用于为组件设置事件触发器。因此,我们必须使用 `with` 语法来触发 `__exit__` 函数。
当然,我们可以不使用 `with` 语法来调用 `attach_load_events`,但该函数需要一个 `Context.root_block`,它是在 `__enter__` 函数中设置的。因此,我们在这里使用了 `with` 语法,而不是自己调用该函数。
在本指南中,我们探讨了:
由于本指南中使用的类仅用于演示目的,它们可能尚未完全优化或模块化。但那样会使指南篇幅过长!
我希望本指南能帮助您对布局类有新的认识,并为您提供如何根据自身需求使用它们的想法。在此查看我们示例的完整实现。