第15章(2)——项目十三:使用Gradio MCP与Apps SDK构建ChatGPT应用——提亮图像

第15章(2)------项目十三:使用Gradio MCP与Apps SDK构建ChatGPT应用------提亮图像

    • [15.6 项目十三:使用Gradio MCP与Apps SDK构建ChatGPT应用------提亮图像](#15.6 项目十三:使用Gradio MCP与Apps SDK构建ChatGPT应用——提亮图像)

15.6 项目十三:使用Gradio MCP与Apps SDK构建ChatGPT应用------提亮图像

ChatGPT应用(🖇️链接15-4)让用户能够在熟悉的聊天界面中,通过对话形式直接体验机器学习模型或其他应用。OpenAI已发布Apps SDK(🖇️链接15-5)供开发者构建完整的应用程序,但借助Gradio MCP服务器,开发者可以基于Gradio极为快速地搭建ChatGPT应用。另外,Gradio内置的分享链接让构建ChatGPT应用极为便利。使用Gradio构建ChatGPT应用需要完成两项任务:

  • 构建一个至少暴露一个工具的Gradio MCP服务器。
  • 使用HTML、JavaScript和CSS构建一个自定义界面,当工具被调用时显示该界面,并将其作为MCP资源暴露。

接下来构建ChatGPT图像增强应用,该应用包含一个"提亮"按钮,用户可以直接通过应用界面调用该工具。该应用如代码15-10所示:
代码15-10

py 复制代码
import gradio as gr
import tempfile
from PIL import Image
import numpy as np

def power_law_image(input_path: str, gamma: float = 0.5) -> str:
    """Applies a power-law (gamma) transformation to an image file and saves
    the result to a temporary file.
    Args:
        input_path (str): Path to the input image.
        gamma (float): Power-law exponent. <1 brightens, >1 darkens.
    Returns:
        str: Path to the saved temporary output image."""
    img = Image.open(input_path).convert("RGB")
    arr = np.array(img, dtype=np.float32) / 255.0
    arr = np.power(arr, gamma)
    arr = np.clip(arr * 255, 0, 255).astype(np.uint8)
    out_img = Image.fromarray(arr)
    tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
    out_img.save(tmp_file.name)
    tmp_file.close()
    return tmp_file.name

@gr.mcp.tool(
    _meta={"openai/outputTemplate": "ui://widget/app.html",
        "openai/resultCanProduceWidget": True,
        "openai/widgetAccessible": True})

@gr.mcp.resource("ui://widget/app.html", mime_type="text/html+skybridge")
def app_html():
    visual = """
    <style>
        #image-container {position: relative;
            display: inline-block;
            max-width: 100%;}
        ...
    </style>
    <div id="image-container">
        <img id="image-display" alt="Processed image" />
        <button id="brighten-btn">Brighten</button>
    </div>
    <script>
        const imageEl = document.getElementById('image-display');
        const btnEl = document.getElementById('brighten-btn');
        function extractImageUrl(data) {
            if (data?.text?.startsWith('Image URL: ')) {
                return data.text.substring('Image URL: '.length).trim();}
            if (data?.content) {
                for (const item of data.content) {
                    if (item.type === 'text' && item.text?.startsWith('Image URL: ')) {
                        return item.text.substring('Image URL: '.length).trim();}}}}

        function render() {
            const url = extractImageUrl(window.openai?.toolOutput);
            if (url) imageEl.src = url;}

        async function brightenImage() {
            btnEl.disabled = true;
            btnEl.textContent = 'Brightening...';
            const result = await window.openai.callTool('power_law_image', {
                input_path: imageEl.src});
            const newUrl = extractImageUrl(result);
            if (newUrl) imageEl.src = newUrl;
            btnEl.disabled = false;
            btnEl.textContent = 'Brighten';}

        btnEl.addEventListener('click', brightenImage);
        window.addEventListener("openai:set_globals", (event) => {
            if (event.detail?.globals?.toolOutput) render();
        }, { passive: true });

        render();
    </script>
    """
    return visual

with gr.Blocks() as demo:
    with gr.Row():
        with gr.Column():
            original_image = gr.Image(label="Original Image", type="filepath")
            btn = gr.Button("Brighten Image")
        with gr.Column():
            output_image = gr.Image(label="Output Image", type="filepath")
            html = gr.Code(language="html", max_lines=20)
    btn.click(power_law_image, inputs=original_image, outputs=original_image)
    btn.click(app_html, outputs=html)
if __name__ == "__main__":
    demo.launch(mcp_server=True, share=True)

代码实现了一个完整的MCP应用,结合Gradio UI、图像处理工具和交互式前端组件。核心亮点是将图像处理工具暴露为MCP工具,并通过Skybridge协议实现前端交互。详细拆解如下:

(1)Gamma校正。函数使用Gamma校正使图像变暗或变量,通过幂律变换,当gamma< 1时提高亮度,当gamma>1时降低亮度。

(2)为MCP工具添加_meta属性,将创建的MCP工具与为应用创建的UI关联起来。关键点是"openai/outputTemplate" 必须与创建的MCP资源的URI保持一致。

(3)gr.mcp.resource提供了一个URI:ui://widget/app.html,与_meta的属性保持一致。同时将资源的MIME类型指定为mime_type="text/html+skybridge"。请注意,在JavaScript的最后为"openai:set_globals"添加了一个事件监听器,控制小部件在每次触发新的工具调用时进行更新页面。

(4)为ChatGPT应用创建UI,并将其作为资源暴露。使用前端代码HTML、Javascript和CSS创建图像显示页面,window.openai对象由ChatGPT自动插入,其中包含了用户工具调用的数据(imageEl.src)。通过window.openai.callTool()直接通过按钮btnEl调用MCP工具,无需经过ChatGPT调用。返回结果content数组保存在window.openai?.toolOutput,需要extractImageUrl()提取数据。

(5)在Gradio应用中创建一个与资源函数对应的事件。这一步非常必要,因为Gradio应用只有在MCP工具、资源、提示等与Gradio事件关联时才会识别它们。通常的做法是将MCP资源的代码直接显示在一个gr.Code组件中。

重新启动Gradio应用并开启共享功能,终端打印出的MCP服务器URL,例如https://xxx.gradio.live/gradio_api/mcp/。Gradio运行界面如图15-7所示:

图15-7

设置为ChatGPT应用 。现在,进入ChatGPT(https://chat.com/)。如前所述,您需要在 ChatGPT 中开启"developer mode",路径为:Settings → Apps & Connectors → Advanced settings。然后,进入Settings → Apps & Connectors,点击"Create"按钮。为连接器设置肖像、名称、描述(可选),并粘贴终端中打印出的MCP服务器URL,选择"No authentication"后完成创建。效果如图15-8所示:

图15-8

大功告成!连接器创建完成后,就可以向其上传照片并可持续变亮。本项目展示了如何构建简单的响应式小部件,以及能够直接从界面调用工具的更高级交互式应用。结合Gradio的MCP服务器能力与OpenAI的Apps SDK,可以创建更丰富的ChatGPT集成,通过自定义可视化来提升用户交互的对话体验。