Skip to content

场景 1: 智能图片助手

模块:Tools + Conditional Edges优先级:🔴 P0(最高)业务价值:增强图片处理能力,提升用户体验

一、业务背景

1.1 当前痛点

当前项目已支持图片上传和图片生成,但存在以下问题:

具体痛点:

  • AI 只能"看图说话",无法对图片执行操作
  • 用户需要手动描述"帮我生成一张类似风格的图",无法自动识别
  • 图片处理(裁剪、滤镜、格式转换)需要用户自己完成
  • 搜索相似图片、识别图片中的文字等需要第三方工具

1.2 期望效果


二、工具设计

2.1 工具清单

基于项目实际需求,设计以下工具:

工具名称功能触发场景
image_crop图片裁剪"帮我裁剪这张图"、"把左边切掉"
image_ocr图片文字识别"图片里写了什么"、"提取文字"
image_style_extract提取图片风格"生成类似风格的图"、"模仿这个风格"
image_similarity_search搜索相似图片"找类似的图"、"这张图像什么"
get_weather获取天气"今天天气"、"北京天气"
search_web联网搜索"搜索xxx"、"查一下xxx"

2.2 工具流程


三、代码实现

3.1 工具定义

创建文件: services/tools/image_tools.py

python
"""图片处理相关工具

基于项目现有的图片处理能力,封装为 LangGraph 工具。
"""
from langchain.tools import tool
from typing import Optional, Dict, Any, List
import logging

logger = logging.getLogger(__name__)


@tool
def image_crop(image_url: str, x: int, y: int, width: int, height: int) -> Dict[str, Any]:
    """
    裁剪图片。

    当用户想要裁剪图片、截取图片某部分时使用此工具。

    Args:
        image_url: 图片 URL(支持 COS key 和完整 URL)
        x: 裁剪起始 X 坐标
        y: 裁剪起始 Y 坐标
        width: 裁剪宽度
        height: 裁剪高度

    Returns:
        裁剪后的图片信息
    """
    from services.image_utils import download_image_as_base64
    from PIL import Image
    import io
    import base64

    try:
        # 下载图片
        if image_url.startswith("cos://"):
            from services.image_storage import get_signed_url_from_key
            image_url = get_signed_url_from_key(image_url)

        image_data = download_image_as_base64(image_url)

        # 解码 base64
        image_bytes = base64.b64decode(image_data.split(",")[1])
        img = Image.open(io.BytesIO(image_bytes))

        # 裁剪
        cropped = img.crop((x, y, x + width, y + height))

        # 转回 base64
        buffer = io.BytesIO()
        cropped.save(buffer, format="PNG")
        result_base64 = "data:image/png;base64," + base64.b64encode(buffer.getvalue()).decode()

        return {
            "success": True,
            "cropped_image": result_base64,
            "message": f"已裁剪图片,区域: ({x}, {y}) 到 ({x + width}, {y + height})"
        }
    except Exception as e:
        logger.error(f"图片裁剪失败: {e}")
        return {"success": False, "error": str(e)}


@tool
def image_ocr(image_url: str, language: Optional[str] = "chi_sim+eng") -> Dict[str, Any]:
    """
    识别图片中的文字。

    当用户想要提取图片中的文字、识别图片内容时使用此工具。

    Args:
        image_url: 图片 URL
        language: 识别语言(默认中英文)

    Returns:
        识别出的文字内容
    """
    # TODO: 集成 OCR 服务(如腾讯云 OCR、百度 OCR)
    # 这里返回模拟结果
    return {
        "success": True,
        "text": "(OCR 识别结果)图片中的文字内容...",
        "confidence": 0.95
    }


@tool
def image_style_extract(image_url: str) -> Dict[str, Any]:
    """
    提取图片风格特征。

    当用户想要生成类似风格的图片、模仿某个图片风格时使用此工具。

    Args:
        image_url: 参考图片 URL

    Returns:
        风格描述和提示词
    """
    from services.image_utils import download_image_as_base64
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage
    import os

    try:
        # 下载图片
        if image_url.startswith("cos://"):
            from services.image_storage import get_signed_url_from_key
            image_url = get_signed_url_from_key(image_url)

        image_data = download_image_as_base64(image_url)

        # 使用 LLM 分析风格
        llm = ChatOpenAI(
            model="openai/gpt-4o-mini",
            api_key=os.getenv("OPENROUTER_API_KEY"),
            base_url=os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1")
        )

        prompt = """分析这张图片的视觉风格,生成一个用于 AI 图片生成的提示词。

请从以下维度描述:
1. 主题/内容
2. 艺术风格(如油画、水彩、像素风等)
3. 色调(如暖色调、冷色调、高饱和等)
4. 构图特点
5. 光影效果

输出格式:
{
    "style_description": "风格描述",
    "prompt_for_generation": "用于生成的英文提示词"
}"""

        response = llm.invoke([
            HumanMessage(content=[
                {"type": "text", "text": prompt},
                {"type": "image_url", "image_url": {"url": image_data}}
            ])
        ])

        import json
        result = json.loads(response.content)

        return {
            "success": True,
            "style_description": result.get("style_description", ""),
            "generation_prompt": result.get("prompt_for_generation", "")
        }
    except Exception as e:
        logger.error(f"风格提取失败: {e}")
        return {"success": False, "error": str(e)}


@tool
def get_weather(city: str) -> Dict[str, Any]:
    """
    获取指定城市的天气信息。

    当用户询问天气、温度、是否下雨等问题时使用此工具。

    Args:
        city: 城市名称(如"北京"、"上海")

    Returns:
        天气信息
    """
    # TODO: 集成天气 API
    # 示例返回
    return {
        "success": True,
        "city": city,
        "temperature": "25°C",
        "condition": "晴",
        "humidity": "60%",
        "message": f"{city}今天天气晴朗,温度 25°C,湿度 60%"
    }


@tool
def search_web(query: str) -> Dict[str, Any]:
    """
    搜索网络获取实时信息。

    当用户询问实时信息、新闻、最新数据时使用此工具。

    Args:
        query: 搜索关键词

    Returns:
        搜索结果摘要
    """
    # TODO: 集成搜索 API(Tavily、SerpAPI 等)
    return {
        "success": True,
        "query": query,
        "results": [
            {"title": "搜索结果 1", "snippet": "相关内容摘要...", "url": "https://example.com/1"},
            {"title": "搜索结果 2", "snippet": "相关内容摘要...", "url": "https://example.com/2"},
        ],
        "summary": f"关于「{query}」的搜索结果摘要..."
    }

3.2 Agent 实现

创建文件: services/tool_agent.py

python
"""带工具调用的智能 Agent

基于项目实际需求,实现图片智能处理等功能。
"""
from typing import Literal, Optional
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage
import os
import logging

from services.tools.image_tools import (
    image_crop,
    image_ocr,
    image_style_extract,
    get_weather,
    search_web,
)

logger = logging.getLogger(__name__)


class ImageAssistantAgent:
    """智能图片助手 Agent"""

    def __init__(self):
        self.tools = [
            image_crop,
            image_ocr,
            image_style_extract,
            get_weather,
            search_web,
        ]
        self.tools_by_name = {tool.name: tool for tool in self.tools}

    def _get_llm(self, model: Optional[str] = None):
        """获取绑定了工具的 LLM"""
        llm = ChatOpenAI(
            model=model or os.getenv("OPENROUTER_MODEL", "openai/gpt-4o"),
            api_key=os.getenv("OPENROUTER_API_KEY"),
            base_url=os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1"),
            temperature=0.7
        )
        return llm.bind_tools(self.tools)

    def _should_continue(self, state: MessagesState) -> Literal["tools", END]:
        """判断是否需要调用工具"""
        last_message = state["messages"][-1]
        if last_message.tool_calls:
            return "tools"
        return END

    def _call_model(self, state: MessagesState, model: Optional[str] = None):
        """调用模型"""
        llm = self._get_llm(model)

        system_prompt = """你是一个智能图片助手。你可以:

1. **分析图片**:识别图片内容、风格、文字等
2. **处理图片**:裁剪、提取文字、分析风格
3. **回答问题**:天气查询、网络搜索等

当用户上传图片并要求处理时,使用相应的工具。
如果不确定用户意图,先询问确认。"""

        messages = [SystemMessage(content=system_prompt)] + state["messages"]
        response = llm.invoke(messages)
        return {"messages": [response]}

    def build_graph(self, checkpointer, model: Optional[str] = None):
        """构建 Agent 工作流"""
        tool_node = ToolNode(self.tools)

        workflow = StateGraph(MessagesState)
        workflow.add_node("agent", lambda state: self._call_model(state, model))
        workflow.add_node("tools", tool_node)

        workflow.add_edge(START, "agent")
        workflow.add_conditional_edges(
            "agent",
            self._should_continue,
            {"tools": "tools", END: END}
        )
        workflow.add_edge("tools", "agent")

        return workflow.compile(checkpointer=checkpointer)

四、用户交互流程

4.1 场景示例

4.2 前端集成示例

javascript
// static/js/tool-agent.js

class ToolAgentChat {
    constructor() {
        this.eventSource = null;
    }

    async sendMessage(prompt, images = [], options = {}) {
        const response = await fetch('/api/chat/tool-agent/stream', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                prompt,
                images,
                model: options.model || 'openai/gpt-4o',
                conversation_id: options.conversationId
            })
        });

        // 处理 SSE 流
        const reader = response.body.getReader();
        const decoder = new TextDecoder();

        while (true) {
            const { done, value } = await reader.read();
            if (done) break;

            const chunk = decoder.decode(value);
            const lines = chunk.split('\n');

            for (const line of lines) {
                if (line.startsWith('data: ')) {
                    const data = JSON.parse(line.slice(6));

                    if (data.tool_call) {
                        // 显示工具调用
                        this.showToolCall(data.tool_call);
                    } else if (data.tool_result) {
                        // 显示工具结果
                        this.showToolResult(data.tool_result);
                    } else if (data.content) {
                        // 流式内容
                        this.appendContent(data.content);
                    } else if (data.done) {
                        this.finishMessage();
                    }
                }
            }
        }
    }

    showToolCall(toolCall) {
        const toolElement = document.createElement('div');
        toolElement.className = 'tool-call-indicator';

        const toolIcons = {
            'image_crop': '✂️',
            'image_ocr': '📝',
            'image_style_extract': '🎨',
            'get_weather': '🌤️',
            'search_web': '🔍'
        };

        toolElement.innerHTML = `
            <div class="tool-header">
                <span class="tool-icon">${toolIcons[toolCall.name] || '🔧'}</span>
                <span class="tool-name">${this.getToolDisplayName(toolCall.name)}</span>
            </div>
            <div class="tool-args">${JSON.stringify(toolCall.args, null, 2)}</div>
        `;

        this.messageContainer.appendChild(toolElement);
    }

    showToolResult(result) {
        const resultElement = document.createElement('div');
        resultElement.className = 'tool-result';

        if (result.success) {
            resultElement.innerHTML = `
                <div class="result-success">
                    ✅ ${result.message || '工具执行成功'}
                </div>
            `;
        } else {
            resultElement.innerHTML = `
                <div class="result-error">
                    ❌ ${result.error || '工具执行失败'}
                </div>
            `;
        }

        this.messageContainer.appendChild(resultElement);
    }

    getToolDisplayName(name) {
        const names = {
            'image_crop': '图片裁剪',
            'image_ocr': '文字识别',
            'image_style_extract': '风格提取',
            'get_weather': '天气查询',
            'search_web': '网络搜索'
        };
        return names[name] || name;
    }
}

五、预期收益

5.1 功能增强

功能改进前改进后
图片处理只能看图说话可裁剪、识别文字、提取风格
实时信息无法获取可查询天气、搜索网络
用户体验需要多轮对话一次完成目标

5.2 技术收益

  • 建立可扩展的工具体系
  • 为后续更多工具集成打好基础
  • 提升 Agent 智能化程度

六、实施计划

步骤任务预估时间
1创建 services/tools/image_tools.py2h
2创建 services/tool_agent.py2h
3添加 API 端点 /api/chat/tool-agent/stream1h
4前端工具调用展示组件2h
5测试和优化1h
总计8h (1天)