Type something to search...
多模态人工智能助手:结合本地模型和云模型

多模态人工智能助手:结合本地模型和云模型

使用 LangGraph、mlx 和 Florence2 构建一个能够回答复杂图像问题的智能体,支持本地运行。

在本文中,我们将结合 LangGraph 和多个专业模型,构建一个基础的智能体,能够回答有关图像的复杂问题,包括图像描述、边界框和 OCR。最初的想法是仅使用本地模型构建,但经过一些迭代后,我决定添加对基于云的模型(即 GPT4o-mini)的连接,以获得更可靠的结果。我们也将探讨这一方面,所有项目的代码可以在 这里 找到。

在过去的一年中,多模态大型语言模型——超越文本的推理和生成能力,能够处理图像、音频和视频等媒体——变得越来越强大、可访问,并可在生产 ML 系统中使用。

闭源的基于云的模型如 GPT-4o、Claude Sonnet 和 Google Gemini 在处理图像输入的推理方面表现出色,且比几个月前的多模态产品便宜且快速得多。Meta 通过发布其 Llama 3.2 系列中多个竞争性多模态模型的权重,加入了这一行列。此外,AWS Bedrock 和 Databricks Mosaic AI 等云计算服务现在也托管了许多这些模型,使开发者能够快速试用,而无需自己管理硬件要求和集群扩展。最后,出现了一个有趣的趋势,即有大量较小的、专门的开源模型可供从 Hugging Face 等仓库下载。一个能够访问这些模型的智能体应该能够选择以何种顺序调用它们,以获得良好的答案,这避免了对单一大型通用模型的需求。

最近一个具有迷人图像能力的例子是 Florence2。该模型于 2024 年 6 月发布,是一个针对图像特定任务(如图像描述、物体检测、OCR 和短语定位)基础模型。从 LLM 的标准来看,它的参数量也相对较小——最强版本为 0.77B 参数——因此可以在现代笔记本电脑上运行。Florence2 在专门的图像任务上优于大型多模态 LLM,如 GPT4o,因为尽管这些更大的模型在回答文本中的一般问题上表现出色,但它们并不真正设计用于提供诸如边界框坐标等数值输出。通过在指令微调阶段使用合适的训练数据,它们当然可以改进——例如,GPT4o 可以微调以在物体检测方面表现良好——但许多团队没有资源去做到这一点。有趣的是,Gemini 实际上被 宣传为能够即刻进行物体检测,但在它能够完成的图像任务范围上,Florence2 仍然更具多样性。

阅读有关 Florence2 的内容激发了这个项目的想法。如果我们能将其连接到一个擅长推理的仅文本 LLM(例如 Llama 3.2 3B)和一个擅长回答图像一般问题的多模态 LLM(如 Qwen2-VL),那么我们就可以构建一个系统,能够回答关于图像的复杂问题。它将首先规划调用哪些模型以及使用哪些输入,然后运行这些任务并汇总结果。智能体编排库 LangGraph,为设计这样的系统提供了良好的框架,我在最近的项目文章中 这里 探索过。

此外,我最近购买了一台新笔记本电脑:一台配备 24GB RAM 的 M3 Macbook。这种机器可以以合理的延迟运行这些模型的最小版本,使得本地开发图像智能体成为可能。这种日益强大的硬件与缩小模型(或智能压缩/量化大模型的方法)的结合非常令人印象深刻!但它也面临实际挑战:首先,当 Florence2-base-ft、Llama-3.2–3B-Instruct-4bit 和 Qwen2-VL-2B-Instruct-4bit 都加载时,我几乎没有足够的 RAM 用于其他任何任务。这对于开发来说没问题,但对于一个可能对人们有用的应用程序来说,这将是一个大问题。此外,正如我们将看到的,Llama-3.2–3B-Instruct-4bit 在生成可靠的结构化输出方面表现不佳,这让我在项目开发过程中转向了 GPT4-o-mini 进行推理步骤。

图像代理

那么这个项目到底是什么呢?让我们通过对将要构建的系统进行一次巡演和演示来介绍它。StateGraph(可以查看这篇文章进行入门)看起来是这样的,每个查询由图像和文本输入组成。

我们通过各个阶段进行,每个阶段都与一个提示相关联。

  • 规划。这里的目标只是用文本制定一个关于如何最好地利用可用工具回答问题的计划。提示包含工具及其各种模式的列表。一个更复杂的系统可能在这个阶段使用RAG来收集最适合问题的工具列表并制定计划。
  • 结构化计划。 这里的目的是创建一个计划组件的列表,以便代理可以逐步执行。我们将计划文本进行处理,强制模型生成一个根据Pydantic模型这里一致格式化的列表。为了评估目的,保留计划文本和结构化计划都是有用的。
  • 执行计划。 结构化计划中的每个元素包含一个工具名称和输入。然后我们按顺序调用这些工具并收集它们的结果。我们的代理只有两个可用工具:特殊视觉(调用Florence2)或一般视觉(调用Qwen2或GPT4o),路由节点用于跟踪当前计划阶段。
  • 评估结果。 一旦计划的每一步都执行完毕,模型就可以查看输入和输出,并评估问题是否得到了回答。如果没有,我们将返回到规划步骤,尝试利用这些新见解修改旧计划。如果是,我们将继续到最后。如果模型修改计划的次数过多,则会触发超时,允许循环中断。

这里有许多可能的改进和扩展,例如在当前实现中,代理只是简单地输出所有先前步骤的结果,但如果需要,可以在这里调用LLM将其整理成一个漂亮的答案。

作为一个示例,仅仅是为了看到它的实际效果,我们给它以下图像和输入文本,这是一组相当复杂的问题。

query = """
What city is in this photo? Tell me what the tallest building is, then find all the skyscrapers and bridges
"""

经过所有步骤后,我们得到了以下结果。

[
{1: 'This photo is of Austin, Texas. The tallest building in the image is likely The Independent, also known as the "Jenga Tower."'},
{2: 
  '{"bboxes": [
    [961.9750366210938, 293.1780090332031, 1253.5550537109375, 783.6420288085938], 
    [77.67500305175781, 41.65800094604492, 307.1150207519531, 401.64599609375], 
    [335.7950134277344, 310.4700012207031, 474.4150085449219, 753.7739868164062], 
    [534.1650390625, 412.6499938964844, 684.7350463867188, 774.2100219726562], 
    [1365.885009765625, 510.114013671875, 1454.3150634765625, 774.2100219726562], 
    [1824.76513671875, 583.9979858398438, 1927.5350341796875, 758.489990234375]
  ], "labels": ["skyscraper", "skyscraper", "skyscraper", "skyscraper", "skyscraper", "skyscraper"]}'},
{3:
  '{"bboxes": [
    [493.5350341796875, 780.4979858398438, 2386.4150390625, 1035.1619873046875]
  ], "labels": ["bridge"]}'}
}
]

我们可以绘制这些结果以确认边界框。

令人印象深刻!人们可能会争论它是否真的找到了所有的摩天大楼,但我觉得这样的系统具有相当强大的潜力和实用性,特别是如果我们增加裁剪边界框、放大和继续对话的能力。

在接下来的部分中,让我们更详细地深入主要步骤。我的希望是其中一些可能对你的项目也有启发。

代理状态、节点和边

我的上一篇文章中对代理和LangGraph进行了更详细的讨论,因此在这里我只会简要提及本项目的代理状态。AgentState对LangGraph图中的所有节点可用,它是存储与查询相关信息的地方。

每个节点都可以被指示写入状态中的一个或多个变量,默认情况下它们会被覆盖。这不是我们对计划输出所期望的行为,计划输出应该是每个步骤结果的列表。为了确保在代理执行工作时该列表能够追加,我们使用了add reducer,您可以在这里了解更多信息。

上面图中的每个节点都是类AgentNodes中的一个方法。它们接收状态,执行一些操作(通常是调用LLM),并将更新输出到状态中。作为示例,这里是用于结构化计划的节点,复制自代码这里

   def structure_plan_node(self, state: dict) -> dict:

        messages = state["plan"]
        response = self.llm_structure.call(messages)
        final_plan_dict = self.post_process_plan_structure(response)
        final_plan = json.dumps(final_plan_dict)

        return {
            "plan_structure": final_plan,
            "current_step": 0,
            "max_steps": len(final_plan_dict),
        }

路由节点也很重要,因为在计划执行过程中会多次访问它。在当前代码中,它非常简单,仅更新当前步骤状态值,以便其他节点知道要查看计划结构列表的哪一部分。

   def routing_node(self, state: dict) -> dict:

        plan_stage = state.get("current_step", 0)
        return {"current_step": plan_stage + 1}

这里的一个扩展是,在路由节点中添加另一个LLM调用,以检查计划的前一步输出是否需要对后续步骤进行任何修改,或者是否可以提前终止问题的回答。

最后,我们需要添加两个条件边,这些边使用存储在AgentState中的数据来确定下一个应该运行的节点。例如,choose_model边查看AgentState中携带的plan_structure对象中的当前步骤名称,然后使用简单的if语句返回在该步骤中应该调用的相应节点的名称。

    def choose_model(state: dict) -> str:

        current_plan = json.loads(state.get("plan_structure"))
        current_step = state.get("current_step", 1)
        max_step = state.get("max_steps", 999)

        if current_step > max_step:
            return "finalize"
        else:
            step_to_execute = current_plan[str(current_step)]["tool_name"]
            return step_to_execute

整个代理结构如下所示。

edges: AgentEdges = AgentEdges()
nodes: AgentNodes = AgentNodes()
agent: StateGraph = StateGraph(AgentState)

### Nodes
agent.add_node("planning", nodes.plan_node)
agent.add_node("structure_plan", nodes.structure_plan_node)
agent.add_node("routing", nodes.routing_node)
agent.add_node("special_vision", nodes.call_special_vision_node)
agent.add_node("general_vision", nodes.call_general_vision_node)
agent.add_node("assessment", nodes.assessment_node)
agent.add_node("response", nodes.dump_result_node)

### Edges
agent.set_entry_point("planning")
agent.add_edge("planning", "structure_plan")
agent.add_edge("structure_plan", "routing")
agent.add_conditional_edges(
            "routing",
            edges.choose_model,
            {
                "special_vision": "special_vision",
                "general_vision": "general_vision",
                "finalize": "assessment",
            },
        )
agent.add_edge("special_vision", "routing")
agent.add_edge("general_vision", "routing")
agent.add_conditional_edges(
            "assessment",
            edges.back_to_plan,
            {
                "good_answer": "response",
                "bad_answer": "planning",
                "timeout": "response",
            },
        )
agent.add_edge("response", END)

可以通过使用这里的教程在笔记本中进行可视化。

编排模型

规划、结构和评估节点非常适合可以推理并生成结构化输出的基于文本的 LLM。这里最简单的选择是使用像 GPT4o-mini 这样的大型通用模型,它具有 对 Pydantic 模式的 JSON 输出的优秀支持

借助一些 LangChain 功能,我们可以创建一个类来调用这样的模型。

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

class StructuredOpenAICaller:
   def __init__(
       self, api_key, system_prompt, output_model, temperature=0, max_tokens=1000
   ):
       self.temperature = temperature
       self.max_tokens = max_tokens
       self.system_prompt = system_prompt
       self.output_model = output_model
       self.llm = ChatOpenAI(
           model=self.MODEL_NAME,
           api_key=api_key,
           temperature=temperature,
           max_tokens=max_tokens,
       )
       self.chain = self._set_up_chain()


   def _set_up_chain(self):
       prompt = ChatPromptTemplate.from_messages(
           [
               ("system", self.system_prompt.system_template),
               ("human", "{query}"),
           ]
       )
       structured_llm = self.llm.with_structured_output(self.output_model)
       chain = prompt | structured_llm


       return chain
   def call(self, query):
       return self.chain.invoke({"query": query})

要设置这个,我们需要提供一个系统提示和一个输出模型(有关这些的示例,请参见 这里),然后我们可以使用调用方法和输入字符串来获取符合我们指定的输出模型结构的响应。按照这样的代码设置,我们需要为代理中使用的每个不同的系统提示和输出模型创建一个新的 StructuredOpenAICaller 实例。我个人更喜欢这样来跟踪不同模型的使用情况,但随着代理变得更加复杂,可以通过另一种方法直接更新类的单个实例中的系统提示和输出模型进行修改。

我们也可以用本地模型做到这一点吗?在 Apple Silicon 上,我们可以使用 MLX 库和 Hugging Face 上的 MLX 社区,轻松尝试开源模型,如 Llama3.2。LangChain 也支持 MLX 集成,因此我们可以按照上面类的结构设置本地模型。

from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.llms.mlx_pipeline import MLXPipeline
from langchain_community.chat_models.mlx import ChatMLX

class StructuredLlamaCaller:
   MODEL_PATH = "mlx-community/Llama-3.2-3B-Instruct-4bit"

   def __init__(
       self,
       system_prompt: Any,
       output_model: Any,
       temperature: float = 0,
       max_tokens: int = 1000,
   ) -> None:


       self.system_prompt = system_prompt
       # 这是定义我们想要输出的结构的 Pydantic 模型的名称
       self.output_model = output_model
       self.loaded_model = MLXPipeline.from_model_id(
           self.MODEL_PATH,
           pipeline_kwargs={"max_tokens": max_tokens, "temp": temperature, "do_sample": False},
       )
       self.llm = ChatMLX(llm=self.loaded_model)
       self.temperature = temperature
       self.max_tokens = max_tokens
       self.chain = self._set_up_chain()

   def _set_up_chain(self) -> Any:
       # 设置解析器
       parser = PydanticOutputParser(pydantic_object=self.output_model)


       # 提示
       prompt = ChatPromptTemplate.from_messages(
           [
               (
                   "system",
                   self.system_prompt.system_template,
               ),
               ("human", "{query}"),
           ]
       ).partial(format_instructions=parser.get_format_instructions())


       chain = prompt | self.llm | parser
       return chain

def call(self, query: str) -> Any:
   return self.chain.invoke({"query": query})

这里有几个有趣的点。首先,我们可以像下载任何其他 Hugging Face 模型一样下载 Llama3.2 的权重和配置,然后在底层使用 LangChain 的 MLXPipeline 工具将它们加载到 MLX 中。当模型首次下载时,它们会自动放置在 Hugging Face 缓存中。有时需要列出模型及其缓存位置,例如,如果您想将模型复制到新环境中。实用程序 scan_cache_dir 将在这里提供帮助,并可以与此功能一起使用以获得有用的结果。

from huggingface_hub import scan_cache_dir

def fetch_downloaded_model_details():

    hf_cache_info = scan_cache_dir()

    repo_paths = []
    size_on_disk = []
    repo_ids = []
    
    for repo in sorted(
        hf_cache_info.repos, key=lambda repo: repo.repo_path
    ):
        repo_paths.append(str(repo.repo_path))
        size_on_disk.append(repo.size_on_disk)
        repo_ids.append(repo.repo_id)
    repos_df = pd.DataFrame({
        "local_path":repo_paths,
        "size_on_disk":size_on_disk,
        "model_name":repo_ids
    })

    repos_df.set_index("model_name",inplace=True)
    return repos_df.to_dict(orient="index")

Llama3.2 不支持像 GPT4o-mini 那样的结构化输出,因此我们需要使用提示来强制它生成 JSON。LangChain 的 PydanticOutputParser 可以提供帮助,尽管也可以实现您自己的版本,如 这里 所示。

根据我的经验,我这里使用的 Llama 版本,即 Llama-3.2–3B-Instruct-4bit,对于超出最简单模式的结构化输出并不可靠。给定带有几个示例的提示,它在我们的代理的“计划生成”阶段相当不错,但即使在 PydanticOutputParser 提供的指令的帮助下,它也常常无法将该计划转换为 JSON。更大和/或量化程度较低的 Llama 版本可能会更好,但如果与我们代理中的其他模型一起运行,它们可能会遇到 RAM 问题。因此,在项目的后续过程中,编排模型将设定为 GPT4o-mini。

通用视觉模型:Qwen2-VL

为了能够回答诸如“这张图片在讲什么?”或“这是哪个城市?”这样的问题,我们需要一个多模态的 LLM。可以说,Florence2 在图像字幕模式下可能能很好地回答这类问题,但它并不是专为对话输出设计的。

可以在笔记本电脑上运行的多模态模型仍处于起步阶段(最近编制的列表可以在 这里 找到),但来自阿里巴巴的 Qwen2-VL 系列 是一个有前景的发展。此外,我们可以利用 MLX-VLM,这是一个专门为视觉模型的调优和推理设计的 MLX 扩展,以在我们的代理框架内设置这些模型之一。

from mlx_vlm import load, apply_chat_template, generate

class QwenCaller:
   MODEL_PATH = "mlx-community/Qwen2-VL-2B-Instruct-4bit"

   def __init__(self, max_tokens=1000, temperature=0):
       self.model, self.processor = load(self.MODEL_PATH)
       self.config = self.model.config
       self.max_tokens = max_tokens
       self.temperature = temperature

   def call(self, query, image):
       messages = [
           {
               "role": "system",
               "content": ImageInterpretationPrompt.system_template,
           },
           {"role": "user", "content": query},
       ]
       prompt = apply_chat_template(self.processor, self.config, messages)
       output = generate(
           self.model,
           self.processor,
           image,
           prompt,
           max_tokens=self.max_tokens,
           temperature=self.temperature,
       )
       return output

这个类将加载最小版本的 Qwen2-VL,然后使用输入图像和提示调用它以获取文本响应。有关此模型及其他可以以相同方式使用的模型的更多详细信息,请查看 MLX-VLM GitHub 页面上的 示例列表。Qwen2-VL 似乎还能够生成边界框和物体指向坐标,因此也可以探索这一能力并与 Florence2 进行比较。

当然,GPT-4o-mini 也具有视觉能力,并且可能比较小的本地模型更可靠。因此,在构建这些应用程序时,增加调用基于云的替代方案的能力是有用的,万一本地模型之一失败。请注意,输入图像必须在发送到模型之前转换为 base64,但一旦完成,我们还可以使用如下所示的 LangChain 框架。

import base64
from io import BytesIO
from PIL import Image
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser


def convert_PIL_to_base64(image: Image, format="jpeg"):
   buffer = BytesIO()
   # Save the image to this buffer in the specified format
   image.save(buffer, format=format)
   # Get binary data from the buffer
   image_bytes = buffer.getvalue()
   # Encode binary data to Base64
   base64_encoded = base64.b64encode(image_bytes)
   # Convert Base64 bytes to string (optional)
   return base64_encoded.decode("utf-8")


class OpenAIVisionCaller:
   MODEL_NAME = "gpt-4o-mini"

   def __init__(self, api_key, system_prompt, temperature=0, max_tokens=1000):
       self.temperature = temperature
       self.max_tokens = max_tokens
       self.system_prompt = system_prompt
       self.llm = ChatOpenAI(
           model=self.MODEL_NAME,
           api_key=api_key,
           temperature=temperature,
           max_tokens=max_tokens,
       )
       self.chain = self._set_up_chain()

   def _set_up_chain(self):
       prompt = ChatPromptTemplate.from_messages(
           [
               ("system", self.system_prompt.system_template),
               (
                   "user",
                   [
                       {"type": "text", "text": "{query}"},
                       {
                           "type": "image_url",
                           "image_url": {"url": "data:image/jpeg;base64,{image_data}"},
                       },
                   ],
               ),
           ]
       )

       chain = prompt | self.llm | StrOutputParser()
       return chain


   def call(self, query, image):
       base64image = convert_PIL_to_base64(image)
       return self.chain.invoke({"query": query, "image_data": base64image})

专门的视觉模型:Florence2

Florence2 在我们的代理上下文中被视为一个专业模型,因为尽管它具有许多功能,但其输入必须从预定义的任务提示列表中选择。当然,该模型可以微调以接受新的提示,但就我们的目的而言,从 Hugging Face 直接下载的版本效果很好。这个模型的美妙之处在于它使用单一的训练过程和权重集,但却在多个图像任务中实现了高性能,而这些任务之前通常需要各自的模型。成功的关键在于其大型且经过精心策划的训练数据集 FLD-5B。要了解更多关于数据集、模型和训练的信息,我推荐 这篇优秀的文章

在我们的上下文中,我们使用编排模型将查询转换为一系列 Florence 任务提示,然后按顺序调用它们。我们可用的选项包括图像描述、物体检测、短语定位、OCR 和分割。对于其中一些选项(即短语定位和区域到分割),需要输入短语,因此编排模型也会生成该短语。相比之下,像图像描述这样的任务只需要图像。Florence2 有许多用例,这些用例在代码中 这里 被探讨。我们将自己限制在物体检测、短语定位、图像描述和 OCR,尽管通过更新与计划生成和结构化相关的提示,添加更多任务是相对简单的。

Florence2 似乎得到了 MLX-VLM 包的支持,但在撰写时我找不到任何使用示例,因此选择了一种使用 Hugging Face transformers 的方法,如下所示。

from transformers import AutoModelForCausalLM, AutoProcessor
import torch
 
def get_device_type():


   if torch.cuda.is_available():
       return "cuda"
   else:
       if torch.backends.mps.is_available() and torch.backends.mps.is_built():
           return "mps"
       else:
           return "cpu"


class FlorenceCaller:

   MODEL_PATH: str = "microsoft/Florence-2-base-ft" 
   # See https://huggingface.co/microsoft/Florence-2-base-ft for other modes 
   # for Florence2 
   TASK_DICT: dict[str, str] = {
       "general object detection": "<OD>",
       "specific object detection": "<CAPTION_TO_PHRASE_GROUNDING>",
       "image captioning": "<MORE_DETAILED_CAPTION>",
       "OCR": "<OCR_WITH_REGION>",
   }


   def __init__(self) -> None:
       self.device: str = (
           get_device_type()
       )  # Function to determine the device type (e.g., 'cpu' or 'cuda').


       with patch("transformers.dynamic_module_utils.get_imports", fixed_get_imports):
           self.model: AutoModelForCausalLM = AutoModelForCausalLM.from_pretrained(
               self.MODEL_PATH, trust_remote_code=True
           )
           self.processor: AutoProcessor = AutoProcessor.from_pretrained(
               self.MODEL_PATH, trust_remote_code=True
           )
           self.model.to(self.device)


   def translate_task(self, task_name: str) -> str:
       return self.TASK_DICT.get(task_name, "<DETAILED_CAPTION>")


   def call(
       self, task_prompt: str, image: Any, text_input: Optional[str] = None
   ) -> Any:


       # Get the corresponding task code for the given prompt
       task_code: str = self.translate_task(task_prompt)

       # Prevent text_input for tasks that do not require it
       if task_code in [
           "<OD>",
           "<MORE_DETAILED_CAPTION>",
           "<OCR_WITH_REGION>",
           "<DETAILED_CAPTION>",
       ]:
           text_input = None

       # Construct the prompt based on whether text_input is provided
       prompt: str = task_code if text_input is None else task_code + text_input

       # Preprocess inputs for the model
       inputs = self.processor(text=prompt, images=image, return_tensors="pt").to(
           self.device
       )

       # Generate predictions using the model
       generated_ids = self.model.generate(
           input_ids=inputs["input_ids"],
           pixel_values=inputs["pixel_values"],
           max_new_tokens=1024,
           early_stopping=False,
           do_sample=False,
           num_beams=3,
       )

       # Decode and process generated output
       generated_text: str = self.processor.batch_decode(
           generated_ids, skip_special_tokens=False
       )[0]

       parsed_answer: dict[str, Any] = self.processor.post_process_generation(
           generated_text, task=task_code, image_size=(image.width, image.height)
       )

       return parsed_answer[task_code]

在 Apple Silicon 上,设备变为 mps,这些模型调用的延迟是可以接受的。这段代码也应该可以在 GPU 和 CPU 上工作,尽管尚未进行测试。

另一个例子及其局限性

让我们通过另一个例子来看看每个步骤的代理输出。要在输入查询和图像上调用代理,我们可以使用 Agent.invoke 方法,该方法遵循我之前的文章中描述的相同过程,将每个节点输出添加到结果列表中,并将输出保存在 LangGraph InMemoryStore 对象中。

我们将使用以下图像,如果我们问一个棘手的问题,比如“这张图像中有树吗?如果有,请找到它们并描述它们在做什么”,这将呈现一个有趣的挑战。

from image_agent.agent.Agent import Agent
from image_agent.utils import load_secrets

secrets = load_secrets()

## use GPT4 for general vision mode
full_agent_gpt_vision = Agent(
openai_api_key=secrets["OPENAI_API_KEY"],vision_mode="gpt"
)

## use local model for general vision 
full_agent_qwen_vision = Agent(
openai_api_key=secrets["OPENAI_API_KEY"],vision_mode="local"
)

在理想的情况下,答案是简单明了的:没有树。

然而,这对代理来说是一个困难的问题,比较使用 GPT-4o-mini 和 Qwen2 作为通用视觉模型时的响应是很有趣的。

当我们用这个查询调用 full_agent_qwen_vision 时,我们得到了一个糟糕的结果:Qwen2 和 Florence2 都被这个伎俩所欺骗,并报告说图像中存在树(有趣的是,如果我们将“树”改为“狗”,我们会得到正确的答案)。

Plan: 
Call generalist vision with the question 'Are there trees in this image? If so, what are they doing?'. Then call specialist vision in object specific mode with the phrase 'cat'.

Plan_structure: 
{
  "1": {"tool_name": "general_vision", "tool_mode": "conversation", "tool_input": "Are there trees in this image? If so, what are they doing?"}, 
  "2": {"tool_name": "special_vision", "tool_mode": "specific object detection", "tool_input": "tree"}
}

Plan output:
[
  {1: 'Yes, there are trees in the image. They appear to be part of a tree line against a pink background.'}
[
  {2: '{"bboxes": [[235.77601623535156, 427.864501953125, 321.7920227050781, 617.2275390625]], "labels": ["tree"]}'}
]

Assessment: 
The result adequately answers the user's question by confirming the presence of trees in the image and providing a description of their appearance and context. The output from both the generalist and specialist vision tools is consistent and informative.

Qwen2 似乎盲目跟随提示,暗示这里可能存在树。Florence2 也在这里失败,报告了一个边界框,而实际上不应该有。

如果我们用相同的查询调用 full_agent_gpt_vision,GPT4o-mini 不会被这个伎俩所欺骗,但对 Florence2 的调用没有改变,因此仍然失败。我们随后看到查询评估步骤的实际应用,因为通用模型和专业模型产生了相互冲突的结果。

Node : general_vision
Task : plan_output
[
  {1: 'There are no trees in this image. It features a group of dogs sitting in front of a pink wall.'}
]

Node : special_vision
Task : plan_output
[
  {2: '{"bboxes": [[235.77601623535156, 427.864501953125, 321.7920227050781, 617.2275390625]], "labels": ["tree"]}'}
]

Node : assessment
Task : answer_assessment
The result contains conflicting information. 
The first part states that there are no trees in the image, while the second part provides a bounding box and label indicating that a tree is present. 
This inconsistency means the user's question is not adequately answered.

代理随后尝试多次重构计划,但 Florence2 坚持为“树”生成一个边界框,而答案评估节点总是将其捕捉为不一致。这比 Qwen2 代理的结果要好,但指出了 Florence2 的假阳性问题。可以通过让路由节点在每一步后评估计划,只有在绝对必要时才调用 Florence2 来解决这个问题。

在基本构建模块就位的情况下,该系统适合进行实验、迭代和改进,我可能会在接下来的几周内继续向库中添加内容。不过,现在这篇文章已经足够长了!

感谢您阅读到最后,希望这里的项目能为您的项目激发一些灵感!在代理框架中协调多个专业模型是一种强大且日益可及的方法,可以让 LLM 在复杂任务中发挥作用。显然,这个领域还有很多改进的空间,我期待看到未来一年该领域的想法如何发展。

Related Posts

使用 ChatGPT 搜索网络功能的 10 种创意方法

使用 ChatGPT 搜索网络功能的 10 种创意方法

例如,提示和输出 你知道可以使用 ChatGPT 的“搜索网络”功能来完成许多任务,而不仅仅是基本的网络搜索吗? 对于那些不知道的人,ChatGPT 新的“搜索网络”功能提供实时信息。 截至撰写此帖时,该功能仅对使用 ChatGPT 4o 和 4o-mini 的付费会员开放。 ![](https://images.weserv.nl/?url=https://cdn-im

阅读更多
在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和科技这样一个动态的行业中,保持领先意味着不断提升你的技能。无论你是希望深入了解人工智能模型性能、掌握数据分析,还是希望通过人工智能转变传统领域如法律,这些课程都是你成功的捷径。以下是一个精心策划的高价值课程列表,可以助力你的职业发展,并让你始终处于创新的前沿。 1. 生成性人工智能简介课程: [生成性人工智能简介](https://genai.works

阅读更多
10 个强大的 Perplexity AI 提示,让您的营销任务自动化

10 个强大的 Perplexity AI 提示,让您的营销任务自动化

在当今快速变化的数字世界中,营销人员总是在寻找更智能的方法来简化他们的工作。想象一下,有一个个人助理可以为您创建受众档案,建议营销策略,甚至为您撰写广告文案。这听起来像是一个梦想? 多亏了像 Perplexity 这样的 AI 工具,这个梦想现在成为现实。通过正确的提示,您可以将 AI 转变为您的 个人营销助理。在本文中,我将分享 10 个强大的提示,帮助您自动

阅读更多
10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

人工智能技术,如机器学习、自然语言处理和数据分析,正在重新定义传统设计方法。从自动化重复任务到实现个性化用户体验,人工智能使设计师能够更加专注于战略思维和创造力。随着这一趋势的不断增长,UI/UX 设计师越来越多地采用 AI 驱动的工具来促进他们的工作。利用人工智能不仅能提供基于数据的洞察,还为满足多样化用户需求的创新设计解决方案开辟了机会。 1. 用户角色开发 目的

阅读更多
在几分钟内完成数月工作的 100 种人工智能工具

在几分钟内完成数月工作的 100 种人工智能工具

人工智能(AI)的快速发展改变了企业的运作方式,使人们能够在短短几分钟内完成曾经需要几周或几个月的任务。从内容创作到网站设计,AI工具帮助专业人士节省时间,提高生产力,专注于创造力。以下是按功能分类的100个AI工具的全面列表,以及它们在现实世界中的使用实例。 1. 研究工具 研究可能耗时,但人工智能工具使查找、分析和组织数据变得更加容易。**ChatGPT, Cop

阅读更多
你从未知道的 17 个令人惊叹的 GitHub 仓库

你从未知道的 17 个令人惊叹的 GitHub 仓库

Github 隐藏的宝石!! 立即收藏的代码库 学习编程相对简单,但掌握编写更好代码的艺术要困难得多。GitHub 是开发者的宝藏,那里“金子”是其他人分享的精心编写的代码。通过探索 GitHub,您可以发现如何编写更清晰的代码,理解高质量代码的样子,并学习成为更熟练开发者的基本步骤。 1. notwaldorf/emoji-translate *谁需

阅读更多