实用模型选择指南,面向真实用例

目的与受众

本指南旨在作为您选择、提示和部署合适 OpenAI 模型(GPT 4.1、o3 和 o4-mini 之间)以应对特定工作负载的实用指南。我们不提供详尽的文档,而是提供可操作的决策框架和真实示例,帮助解决方案工程师、技术客户经理、合作伙伴架构师和半技术从业者快速构建可行的解决方案。内容侧重于当前模型功能、垂直行业特定实现以及当今行业需求,并提供从模型选择到生产部署的清晰路径。每个部分都提供简洁、可适应的代码示例,您可以立即将其应用于您的用例,同时指向现有资源以深入探讨特定主题。

注意:以下规范性指导和实验已在当今最新的 SOTA 模型上进行。随着不同场景和时间范围的考虑,这些指标未来可能会发生变化。

如何使用本指南

本指南分为不同的部分,以帮助您快速找到所需信息。每个部分涵盖模型选择、实施和部署的特定方面。

  1. 目的与受众:本指南的受众和内容概述。
  2. 模型指南:帮助您选择合适模型的快速参考,包括模型比较和基于映射不同用例场景的演进图。
  3. 用例: - 3A. 法律问答的长上下文 RAG:构建一个代理系统,用于回答复杂的法律文件中的问题。 - 3B. 制药研发的 AI 联合科学家:通过多代理系统加速制药研究中的实验设计。 - 3C. 保险索赔处理:使用视觉和推理功能数字化和验证手写保险表格。
  4. 从原型到生产:帮助您从原型过渡到生产的清单。
  5. 适应决策树:一个流程图,用于根据特定要求指导您的模型选择。
  6. 附录:参考资料,包括定价、延迟、提示模式和外部资源链接。

对于快速决策,请重点关注模型指南和适应决策树部分。有关实施细节,请探索与您的需求相关的具体用例。

================================================================================

模型指南

2.1 模型介绍矩阵

| 模型 | 核心优势 | 理想的首选 | 注意事项 | 升级/降级路径 |

模型 核心优势 理想的首选 注意事项 升级/降级路径
GPT‑4o 实时语音/视觉聊天 实时多模态代理 在文本 SOTA(最先进)方面略低于 4.1 需要深度推理 → o4‑mini
GPT‑4.1 100 万 token 文本准确性之王 长文档分析、代码审查 无法原生推理;成本高于 mini 型号 预算紧张 → 4.1‑mini / nano
o3 深度工具使用代理 高风险、多步骤推理 延迟和价格 成本/延迟 → o4‑mini
o4‑mini 廉价、快速推理 大批量“足够好”的逻辑 与 o3 相比的深度上限 准确性至关重要 → o3

(完整价格和效用表 → 第 6.1 节

2.2 模型演进概览

OpenAI 的模型系列已发展到能够满足不同维度上的专业需求。这些图表展示了当前的模型系列及其相互关系。

基本区别:“o 系列”与“GPT”模型

OpenAI 提供两个不同的模型系列,每个系列都有独特的优势:

  • GPT 模型(4o、4.1):针对通用任务进行了优化,具有出色的指令遵循能力。GPT-4.1 在长上下文(100 万 token)方面表现出色,而 GPT-4o 则有适用于实时语音、文本到语音和语音到文本的变体。GPT-4.1 还有 mini 和 nano 变体,而 GPT-4o 有 mini 变体。这些变体比其全尺寸对应模型更便宜、更快。

  • o 系列模型(o3、o4-mini):专门用于深度推理和循序渐进的问题解决。这些模型在需要逻辑思维和工具使用的复杂、多阶段任务方面表现出色。当准确性和推理深度至关重要时,请选择这些模型。这些模型还有一个可选的 reasoning_effort 参数(可以设置为 lowmediumhigh),允许用户控制用于推理的 token 数量。

OpenAI 模型演进

OpenAI 模型演进

关键特性

  • GPT-4.1 系列:针对长上下文处理进行了优化,具有 100 万 token 的上下文窗口。
  • o3:专门用于深度多步推理。
  • o4-mini:将推理能力与视觉相结合,成本更低。

每个模型在不同场景下都有其优势,并且具有互补的优势,可以组合用于复杂的流程。

在本指南中,我们仅对 GPT-4.1 系列模型、o3 和 o4-mini 进行了实验。我们没有对 GPT-4o 系列模型进行实验。

================================================================================

3A. 用例:法律问答的长上下文 RAG

长上下文法律问答用例

🗂️ 摘要矩阵

此表总结了此特定长上下文代理 RAG 实现的核心技术选择及其基本原理。

| 层 | 选择 | 效用 |

选择 效用
分块 句子感知分割器 将文档分割成 20 个相等的部分,并尊重句子边界。
路由 gpt-4.1-mini 使用自然语言理解来识别相关块,无需嵌入索引。
路径选择 select(ids=[...])scratchpad(text="...") 在钻取文档层次结构时记录推理过程。
引用 段落级别 平衡精度与成本;为答案提供有意义的上下文。
综合 gpt-4.1(结构化输出) 直接从选定的段落生成带有引用的答案。
验证 o4-mini(LLM 作为裁判) 验证事实准确性和引用正确性。

注意:价格和模型标识符截至 2025 年 4 月准确,可能会发生变化。

本节概述了检索增强生成(RAG)系统的构建,该系统旨在准确回答有关复杂且冗长的程序性文本的问题,并以商标审判和上诉委员会程序手册(TBMP)作为代表案例。TBMP 是一个重要的法律资源,详细说明了 USPTO 商标审判和上诉委员会商标诉讼的程序,知识产权律师和法律专业人士经常查阅。通过利用最新的 OpenAI 模型,该系统增强了对密集法律内容的理解和可解释性,通过先进的语言理解和动态检索能力实现精确、上下文感知的响应。

这些方法还可以应用于需要从复杂文档中精确检索信息的其他用例,例如医疗合规手册、财务监管框架或技术文档系统,在这些系统中,准确性、引用和可审计性是任务的关键要求。

1. 场景快照

  • 语料库:主要文档是 商标审判和上诉委员会程序手册(TBMP,2024 年版)。本手册包含详细的程序规则和指南,共 1194 页。
  • 用户:目标用户是知识产权(IP)诉讼律师和律师助理,他们需要根据 TBMP 快速、准确地回答程序性问题。
  • 典型提问:用户提出需要综合和引用的问题,例如:
    1. “根据 TBMP,提交强制发现动议的要求是什么?”
    2. “手册中规定的发现会议有哪些截止日期?”
    3. “根据 TBMP,解释委员会在沉积中如何处理律师-客户特权主张。”
    4. “根据 TBMP,列举委员会可以援引的 Fed. R. Civ. P. 11 制裁。”

注意:根据您的具体部署环境,您可能需要调整某些实现步骤以匹配您的基础设施要求。

虽然 OpenAI 的文件搜索工具为许多用例提供了一个良好的起点,但本节介绍了一种不同的方法,该方法利用百万 token 的上下文窗口来处理大型文档,而无需任何预处理或向量数据库。此处描述的代理方法支持零延迟摄取、动态检索粒度和细粒度引用可追溯性。

2. 代理 RAG 流程

在深入研究实现之前,让我们先了解整体方法:

  1. 将整个文档加载到上下文窗口中
  2. 分割成 20 个块,并尊重句子边界
  3. 询问模型哪些块可能包含相关信息
  4. 通过进一步分割选定的块来深入研究
  5. 重复直到我们达到段落级别的内容
  6. 基于选定的段落生成答案
  7. 验证答案以确保事实准确性

这种分层导航方法模仿了人类如何浏览文档,专注于相关章节,然后是特定部分,最后只阅读最相关的段落。

分层路由器

代理 RAG 系统:模型使用

| 流程阶段 | 使用的模型 | 目的 |

流程阶段 使用的模型 目的
初始路由 gpt-4.1-mini 识别哪些文档块可能包含相关信息
分层导航 gpt-4.1-mini 继续深入查找最相关的段落
答案生成 gpt-4.1 从选定的段落生成带有引用的结构化响应
答案验证 o4-mini 验证事实准确性和正确的引用使用

这种零预处理方法利用大型上下文窗口即时导航文档,模仿人类如何浏览文档以查找相关信息。

3. 实现

让我们一步一步地实现这种方法。

首先安装所需的软件包。

%pip install tiktoken pypdf nltk openai pydantic --quiet
注意:您可能需要重新启动内核才能使用更新的软件包。

3.1 文档加载

首先,让我们加载文档并检查其大小。在本指南中,我们将重点关注第 100-900 节,这些章节涵盖了通过委员会决定审查的核心程序方面。第 1000 节及更高部分(干扰、并发使用程序、单方上诉)是超出我们当前范围的专门程序。

import requests
from io import BytesIO
from pypdf import PdfReader
import re
import tiktoken
from nltk.tokenize import sent_tokenize
import nltk
from typing import List, Dict, Any

# 下载 nltk 数据(如果尚未存在)
nltk.download('punkt_tab')

def load_document(url: str) -> str:
    """从 URL 加载文档并返回其文本内容。"""
    print(f"正在从 {url} 下载文档...")
    response = requests.get(url)
    response.raise_for_status()
    pdf_bytes = BytesIO(response.content)
    pdf_reader = PdfReader(pdf_bytes)

    full_text = ""


    max_page = 920  # 第 1000 节之前的页面截止(干扰)
    for i, page in enumerate(pdf_reader.pages):
        if i >= max_page:
            break
        full_text += page.extract_text() + "\n"

    # 计算单词和 token 数量
    word_count = len(re.findall(r'\b\w+\b', full_text))

    tokenizer = tiktoken.get_encoding("o200k_base")
    token_count = len(tokenizer.encode(full_text))

    print(f"文档已加载:{len(pdf_reader.pages)} 页,{word_count} 个单词,{token_count} 个 token")
    return full_text

# 加载文档
tbmp_url = "https://www.uspto.gov/sites/default/files/documents/tbmp-Master-June2024.pdf"
document_text = load_document(tbmp_url)

# 显示前 500 个字符
print("\n文档预览(前 500 个字符):")
print("-" * 50)
print(document_text[:500])
print("-" * 50)
[nltk_data] Downloading package punkt_tab to
[nltk_data]     /Users/kmurali/nltk_data...
[nltk_data]   Package punkt_tab is already up-to-date!


正在从 https://www.uspto.gov/sites/default/files/documents/tbmp-Master-June2024.pdf 下载文档...
文档已加载:1194 页,595197 个单词,932964 个 token

文档预览(前 500 个字符):
--------------------------------------------------
TRADEMARK TRIAL AND
APPEAL BOARD MANUAL
OF PROCEDURE (TBMP)
 June 2024
June   2024
United States Patent and Trademark Office
PREFACE TO THE JUNE 2024 REVISION
The June 2024 revision of the Trademark Trial and Appeal Board Manual of Procedure is an update of the
June 2023 edition. This update is moderate in nature and incorporates relevant case law issued between March
3, 2023 and March 1, 2024.
The title of the manual is abbreviated as “TBMP.” A citation to a section of the manual may be written
--------------------------------------------------

我们可以看到文档的 token 数量超过了 90 万!虽然这可以放入 GPT 4.1 的上下文长度中,但我们也希望有可验证的引用,因此我们将继续采用递归分块策略。

3.2 改进的 20 块分割器,具有最小 token 大小

现在,让我们创建一个改进的函数来将文档分割成 20 个块,确保每个块具有最小的 token 大小并尊重句子边界。

20 是为该特定文档/任务经验性选择的数字,对于其他文档,可能需要根据大小和结构进行调整(数字越高,块的粒度越细)。然而,这里的关键原则是将文档的各个部分分开,以便让语言模型决定相关组件。同样的推理也适用于稍后将在指南中介绍的 max_depth 参数。

# 在整个代码中一致使用的全局分词器名称
TOKENIZER_NAME = "o200k_base"

def split_into_20_chunks(text: str, min_tokens: int = 500) -> List[Dict[str, Any]]:
    """
    将文本分割成最多 20 个块,尊重句子边界并确保
    每个块至少有 min_tokens(除非是最后一个块)。

    参数:
        text:要分割的文本
        min_tokens:每个块的最小 token 数(默认值:500)

    返回:
        一个字典列表,其中每个字典包含:

        - id:块 ID(0-19)
        - text:块的文本内容
    """
    # 首先,将文本分割成句子
    sentences = sent_tokenize(text)

    # 获取分词器以计算 token 数量
    tokenizer = tiktoken.get_encoding(TOKENIZER_NAME)

    # 创建尊重句子边界和最小 token 计数的块
    chunks = []
    current_chunk_sentences = []
    current_chunk_tokens = 0

    for sentence in sentences:
        # 计算此句子的 token 数量
        sentence_tokens = len(tokenizer.encode(sentence))

        # 如果添加此句子会使块过大,并且我们已经达到了最小 token 数,
        # 则完成当前块并开始一个新块
        if (current_chunk_tokens + sentence_tokens > min_tokens * 2) and current_chunk_tokens >= min_tokens:
            chunk_text = " ".join(current_chunk_sentences)
            chunks.append({
                "id": len(chunks),  # 使用整数 ID 而不是字符串
                "text": chunk_text
            })
            current_chunk_sentences = [sentence]
            current_chunk_tokens = sentence_tokens
        else:
            # 将此句子添加到当前块
            current_chunk_sentences.append(sentence)
            current_chunk_tokens += sentence_tokens

    # 如果还有剩余内容,则添加最后一个块
    if current_chunk_sentences:
        chunk_text = " ".join(current_chunk_sentences)
        chunks.append({
            "id": len(chunks),  # 使用整数 ID 而不是字符串
            "text": chunk_text
        })

    # 如果我们有超过 20 个块,则合并它们
    if len(chunks) > 20:
        # 重新组合所有文本
        all_text = " ".join(chunk["text"] for chunk in chunks)
        # 重新分割成正好 20 个块,不要求最小 token 数
        sentences = sent_tokenize(all_text)
        sentences_per_chunk = len(sentences) // 20 + (1 if len(sentences) % 20 > 0 else 0)

        chunks = []
        for i in range(0, len(sentences), sentences_per_chunk):
            # 获取此块的句子
            chunk_sentences = sentences[i:i+sentences_per_chunk]
            # 将句子合并成一个文本
            chunk_text = " ".join(chunk_sentences)
            # 创建一个带有 ID 和文本的块对象
            chunks.append({
                "id": len(chunks),  # 使用整数 ID 而不是字符串
                "text": chunk_text
            })

    # 打印块统计信息
    print(f"将文档分割成 {len(chunks)} 个块")
    for i, chunk in enumerate(chunks):
        token_count = len(tokenizer.encode(chunk["text"]))
        print(f"块 {i}: {token_count} 个 token")

    return chunks

# 将文档分割成 20 个块,并设置最小 token 大小
document_chunks = split_into_20_chunks(document_text, min_tokens=500)
将文档分割成 20 个块
块 0: 42326 个 token
块 1: 42093 个 token
块 2: 42107 个 token
块 3: 39797 个 token
块 4: 58959 个 token
块 5: 48805 个 token
块 6: 37243 个 token
块 7: 33453 个 token
块 8: 38644 个 token
块 9: 49402 个 token
块 10: 51568 个 token
块 11: 49586 个 token
块 12: 47722 个 token
块 13: 48952 个 token
块 14: 44994 个 token
块 15: 50286 个 token
块 16: 54424 个 token
块 17: 62651 个 token
块 18: 47430 个 token
块 19: 42507 个 token

3.3 带有改进工具模式的路由函数

现在,让我们创建路由函数,该函数将选择相关的块并维护一个便笺簿。

维护便笺簿允许模型跟踪决策标准和推理过程。此实现使用 GPT-4.1-mini 的两阶段方法:首先要求模型通过工具调用更新便笺簿(tool_choice="required"),然后请求结构化 JSON 输出以进行块选择。此方法提供了对模型推理过程更好的可见性,同时确保了下游处理的一致结构化输出。

from openai import OpenAI
import json
from typing import List, Dict, Any

# 初始化 OpenAI 客户端
client = OpenAI()

def route_chunks(question: str, chunks: List[Dict[str, Any]], 
                depth: int, scratchpad: str = "") -> Dict[str, Any]:
    """
    询问模型哪些块包含与问题相关的信息。
    维护一个便笺簿以供模型推理。
    使用结构化输出进行块选择,并使用必需的工具调用来更新便笺簿。

    参数:
        question:用户的问题
        chunks:要评估的块列表
        depth:导航层次结构中的当前深度
        scratchpad:当前的便笺簿内容

    返回:
        包含选定 ID 和更新的便笺簿的字典
    """
    print(f"\n==== 在深度 {depth} 处路由 ====")
    print(f"正在评估 {len(chunks)} 个块的相关性")

    # 构建系统消息
    system_message = """您是一位专业的文档导航员。您的任务是:

1. 确定哪些文本块可能包含回答用户问题的信息
2. 在便笺簿中记录您的推理过程以供将来参考
3. 选择最相关的块。要有选择性,但要彻底。选择您回答问题所需的任意数量的块,但避免选择过多。

首先仔细思考什么信息有助于回答问题,然后评估每个块。
"""

    # 构建包含块和当前便笺簿的用户消息
    user_message = f"问题:{question}\n\n"

    if scratchpad:
        user_message += f"当前便笺簿:\n{scratchpad}\n\n"

    user_message += "文本块:\n\n"

    # 将每个块添加到消息中
    for chunk in chunks:
        user_message += f"块 {chunk['id']}:\n{chunk['text']}\n\n"

    # 定义用于便笺簿工具调用的函数模式
    tools = [
        {
            "type": "function",
            "name": "update_scratchpad",
            "description": "记录您关于为什么选择某些块的推理过程",
            "strict": True,
            "parameters": {
                "type": "object",
                "properties": {
                    "text": {
                        "type": "string",
                        "description": "您关于块选择的推理过程"
                    }
                },
                "required": ["text"],
                "additionalProperties": False
            }
        }
    ]

    # 定义用于结构化输出(选定块)的 JSON 模式
    text_format = {
        "format": {
            "type": "json_schema",
            "name": "selected_chunks",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "chunk_ids": {
                        "type": "array",
                        "items": {"type": "integer"},
                        "description": "包含回答问题信息的选定块的 ID"
                    }
                },
                "required": [
                    "chunk_ids"
                ],
                "additionalProperties": False
            }
        }
    }

    # 第一阶段:调用模型更新便笺簿(必需的工具调用)
    messages = [
        {"role": "system", "content": system_message},
        {"role": "user", "content": user_message + "\n\n首先,您必须使用 update_scratchpad 函数来记录您的推理过程。"}
    ]

    response = client.responses.create(
        model="gpt-4.1-mini",
        input=messages,
        tools=tools,
        tool_choice="required"
    )

    # 处理便笺簿工具调用
    new_scratchpad = scratchpad

    for tool_call in response.output:
        if tool_call.type == "function_call" and tool_call.name == "update_scratchpad":
            args = json.loads(tool_call.arguments)
            scratchpad_entry = f"深度 {depth} 推理:\n{args.get('text', '')}"
            if new_scratchpad:
                new_scratchpad += "\n\n" + scratchpad_entry
            else:
                new_scratchpad = scratchpad_entry

            # 将函数调用和结果添加到消息中
            messages.append(tool_call)
            messages.append({
                "type": "function_call_output",
                "call_id": tool_call.call_id,
                "output": "便笺簿已成功更新。"
            })

    # 第二阶段:获取块选择的结构化输出
    messages.append({"role": "user", "content": "现在,选择可能包含回答问题信息的块。返回一个包含块 ID 列表的 JSON 对象。"})

    response_chunks = client.responses.create(
        model="gpt-4.1-mini",
        input=messages,
        text=text_format
    )

    # 从结构化输出中提取选定的块 ID
    selected_ids = []
    if response_chunks.output_text:
        try:
            # 由于模式,output_text 应该已经是 JSON 格式
            chunk_data = json.loads(response_chunks.output_text)
            selected_ids = chunk_data.get("chunk_ids", [])
        except json.JSONDecodeError:
            print("警告:无法将结构化输出解析为 JSON")

    # 显示结果
    print(f"选定的块:{', '.join(str(id) for id in selected_ids)}")
    print(f"更新的便笺簿:\n{new_scratchpad}")

    return {
        "selected_ids": selected_ids,
        "scratchpad": new_scratchpad
    }

3.4 递归导航函数

现在,让我们创建递归导航函数,该函数将深入文档。max_depth 是要深入的最大级别(同时考虑最小 token 数):

def navigate_to_paragraphs(document_text: str, question: str, max_depth: int = 1) -> Dict[str, Any]:
    """
    通过文档层次结构导航以查找相关段落。

    参数:
        document_text:完整的文档文本
        question:用户的问题
        max_depth:在返回段落之前的最大导航深度(默认为 1)

    返回:
        包含选定段落和最终便笺簿的字典
    """
    scratchpad = ""

    # 获取具有最小 500 个 token 的初始块
    chunks = split_into_20_chunks(document_text, min_tokens=500)

    # 导航器状态 - 跟踪块路径以维护层次结构
    chunk_paths = {}  # 将数字 ID 映射到路径字符串以供显示
    for chunk in chunks:
        chunk_paths[chunk["id"]] = str(chunk["id"])

    # 导航级别,直到达到 max_depth 或没有剩余块
    for current_depth in range(max_depth + 1):
        # 调用路由以获取相关块
        result = route_chunks(question, chunks, current_depth, scratchpad)

        # 更新便笺簿
        scratchpad = result["scratchpad"]

        # 获取选定的块
        selected_ids = result["selected_ids"]
        selected_chunks = [c for c in chunks if c["id"] in selected_ids]

        # 如果没有选择块,则返回空结果
        if not selected_chunks:
            print("\n未找到相关块。")
            return {"paragraphs": [], "scratchpad": scratchpad}

        # 如果我们已达到 max_depth,则返回选定的块
        if current_depth == max_depth:
            print(f"\n在深度 {current_depth} 返回 {len(selected_chunks)} 个相关块")

            # 更新显示 ID 以显示层次结构
            for chunk in selected_chunks:
                chunk["display_id"] = chunk_paths[chunk["id"]]

            return {"paragraphs": selected_chunks, "scratchpad": scratchpad}

        # 通过进一步分割选定的块来准备下一级别
        next_level_chunks = []
        next_chunk_id = 0  # 新块的计数器

        for chunk in selected_chunks:
            # 将此块分割成更小的部分
            sub_chunks = split_into_20_chunks(chunk["text"], min_tokens=200)

            # 更新 ID 并维护路径映射
            for sub_chunk in sub_chunks:
                path = f"{chunk_paths[chunk['id']]}.{sub_chunk['id']}"
                sub_chunk["id"] = next_chunk_id
                chunk_paths[next_chunk_id] = path
                next_level_chunks.append(sub_chunk)
                next_chunk_id += 1

        # 更新下一个迭代的块
        chunks = next_level_chunks

3.5 针对示例问题运行改进的导航

让我们针对一个示例问题运行导航,并采用改进的方法:

# 针对示例问题运行导航
question = "强制发现动议应以何种格式提交?签名应如何处理?"
navigation_result = navigate_to_paragraphs(document_text, question, max_depth=2)

# 示例检索段落
print("\n==== 前 3 个检索到的段落 ====")
for i, paragraph in enumerate(navigation_result["paragraphs"][:3]):
    display_id = paragraph.get("display_id", str(paragraph["id"]))
    print(f"\n段落 {i+1}(ID:{display_id}):")
    print("-" * 40)
    print(paragraph["text"])
    print("-" * 40)
将文档分割成 20 个块
块 0: 42326 个 token
块 1: 42093 个 token
块 2: 42107 个 token
块 3: 39797 个 token
块 4: 58959 个 token
块 5: 48805 个 token
块 6: 37243 个 token
块 7: 33453 个 token
块 8: 38644 个 token
块 9: 49402 个 token
块 10: 51568 个 token
块 11: 49586 个 token
块 12: 47722 个 token
块 13: 48952 个 token
块 14: 44994 个 token
块 15: 50286 个 token
块 16: 54424 个 token
块 17: 62651 个 token
块 18: 47430 个 token
块 19: 42507 个 token

==== 在深度 0 处路由 ====
正在评估 20 个块的相关性
选定的块:0, 1, 2, 3, 4, 5, 6, 7, 8
更新的便笺簿:
深度 0 推理:
用户想知道提交强制发现动议的格式要求以及如何处理此类动议的签名。

根据对块的评估:

- 块 0、1、2、3、4、5、6、7、8 与相关性最高,因为它们涵盖了提交、动议、签名、送达以及 TTAB 程序中特别是动议和发现的一般要求。
- 这些块包含有关电子文件(通过 ESTTA)、纸质文件例外情况、签名要求、送达要求、提交格式(包括动议)、时间规则和专业人员职责的详细信息。
- 此外,还特别概述了强制发现动议的规则,包括所需的附件、时间和解决发现争议的良好信誉的认证。
- 块 11-19 主要涵盖审判后和上诉程序,相关性较低。

我将选择这些相关的块,以提供关于如何提交强制发现动议以及如何处理这些动议的签名的全面答案。
将文档分割成 20 个块
块 0: 3539 个 token
块 1: 2232 个 token
块 2: 1746 个 token
块 3: 3078 个 token
块 4: 1649 个 token
块 5: 2779 个 token
块 6: 2176 个 token
块 7: 1667 个 token
块 8: 1950 个 token
块 9: 1730 个 token
块 10: 1590 个 token
块 11: 1964 个 token
块 12: 1459 个 token
块 13: 2070 个 token
块 14: 2422 个 token
块 15: 1976 个 token
块 16: 2335 个 token
块 17: 2694 个 token
块 18: 2282 个 token
块 19: 982 个 token
将文档分割成 20 个块
块 0: 2880 个 token
块 1: 1323 个 token
块 2: 2088 个 token
块 3: 1493 个 token
块 4: 2466 个 token
块 5: 2563 个 token
块 6: 2981 个 token
块 7: 2723 个 token
块 8: 2264 个 token
块 9: 1900 个 token
块 10: 2134 个 token
块 11: 1778 个 token
块 12: 2484 个 token
块 13: 1922 个 token
块 14: 2237 个 token
块 15: 2044 个 token
块 16: 2097 个 token
块 17: 1326 个 token
块 18: 2427 个 token
块 19: 962 个 token
将文档分割成 20 个块
块 0: 2341 个 token
块 1: 1724 个 token
块 2: 2042 个 token
块 3: 3225 个 token
块 4: 1617 个 token
块 5: 2247 个 token
块 6: 1741 个 token
块 7: 1914 个 token
块 8: 2027 个 token
块 9: 2596 个 token
块 10: 2366 个 token
块 11: 2164 个 token
块 12: 2471 个 token
块 13: 1821 个 token
块 14: 1496 个 token
块 15: 1712 个 token
块 16: 1909 个 token
块 17: 1961 个 token
块 18: 2309 个 token
块 19: 2419 个 token
将文档分割成 20 个块
块 0: 2304 个 token
块 1: 2140 个 token
块 2: 1845 个 token
块 3: 3053 个 token
块 4: 2008 个 token
块 5: 2052 个 token
块 6: 2240 个 token
块 7: 1943 个 token
块 8: 1732 个 token
块 9: 1507 个 token
块 10: 1453 个 token
块 11: 1976 个 token
块 12: 1871 个 token
块 13: 1620 个 token
块 14: 1906 个 token
块 15: 1558 个 token
块 16: 1889 个 token
块 17: 2233 个 token
块 18: 2208 个 token
块 19: 2259 个 token
将文档分割成 20 个块
块 0: 4620 个 token
块 1: 3446 个 token
块 2: 1660 个 token
块 3: 3203 个 token
块 4: 4373 个 token
块 5: 4233 个 token
块 6: 3651 个 token
块 7: 3820 个 token
块 8: 3018 个 token
块 9: 3018 个 token
块 10: 4201 个 token
块 11: 3043 个 token
块 12: 2438 个 token
块 13: 3295 个 token
块 14: 2578 个 token
块 15: 2423 个 token
块 16: 1386 个 token
块 17: 1482 个 token
块 18: 1615 个 token
块 19: 1454 个 token
将文档分割成 20 个块
块 0: 1468 个 token
块 1: 1946 个 token
块 2: 2020 个 token
块 3: 3384 个 token
块 4: 2458 个 token
块 5: 3535 个 token
块 6: 3059 个 token
块 7: 2027 个 token
块 8: 2417 个 token
块 9: 2772 个 token
块 10: 1913 个 token
块 11: 2674 个 token
块 12: 2131 个 token
块 13: 1409 个 token
块 14: 3256 个 token
块 15: 2827 个 token
块 16: 2547 个 token
块 17: 4187 个 token
块 18: 1527 个 token
块 19: 1246 个 token
将文档分割成 20 个块
块 0: 1272 个 token
块 1: 1646 个 token
块 2: 1643 个 token
块 3: 2279 个 token
块 4: 1451 个 token
块 5: 1635 个 token
块 6: 1983 个 token
块 7: 1337 个 token
块 8: 1820 个 token
块 9: 2269 个 token
块 10: 2894 个 token
块 11: 2176 个 token
块 12: 1401 个 token
块 13: 1882 个 token
块 14: 2114 个 token
块 15: 2240 个 token
块 16: 1900 个 token
块 17: 1550 个 token
块 18: 1713 个 token
块 19: 2035 个 token
将文档分割成 20 个块
块 0: 2694 个 token
块 1: 1808 个 token
块 2: 1874 个 token
块 3: 1328 个 token
块 4: 1552 个 token
块 5: 1436 个 token
块 6: 1367 个 token
块 7: 1333 个 token
块 8: 978 个 token
块 9: 1303 个 token
块 10: 1738 个 token
块 11: 1509 个 token
块 12: 1875 个 token
块 13: 1524 个 token
块 14: 1597 个 token
块 15: 1807 个 token
块 16: 2449 个 token
块 17: 2271 个 token
块 18: 1467 个 token
块 19: 1540 个 token
将文档分割成 20 个块
块 0: 1597 个 token
块 1: 1554 个 token
块 2: 1685 个 token
块 3: 1416 个 token
块 4: 1702 个 token
块 5: 1575 个 token
块 6: 1842 个 token
块 7: 1981 个 token
块 8: 1393 个 token
块 9: 1562 个 token
块 10: 1569 个 token
块 11: 1898 个 token
块 12: 3186 个 token
块 13: 2337 个 token
块 14: 1889 个 token
块 15: 1948 个 token
块 16: 1628 个 token
块 17: 3544 个 token
块 18: 2454 个 token
块 19: 1882 个 token

==== 在深度 1 处路由 ====
正在评估 180 个块的相关性
选定的块:5, 6, 7, 17, 18, 19, 20, 400, 401, 408, 410
更新的便笺簿:
深度 0 推理:
用户想知道提交强制发现动议的格式要求以及如何处理此类动议的签名。

根据对块的评估:

- 块 0、1、2、3、4、5、6、7、8 与相关性最高,因为它们涵盖了提交、动议、签名、送达以及 TTAB 程序中特别是动议和发现的一般要求。
- 这些块包含有关电子文件(通过 ESTTA)、纸质文件例外情况、签名要求、送达要求、提交格式(包括动议)、时间规则和专业人员职责的详细信息。
- 此外,还特别概述了强制发现动议的规则,包括所需的附件、时间和解决发现争议的良好信誉的认证。
- 块 11-19 主要涵盖审判后和上诉程序,相关性较低。

我将选择这些相关的块,以提供关于如何提交强制发现动议以及如何处理这些动议的签名的全面答案。

深度 1 推理:
用户的问题是关于提交强制发现动议的格式要求以及如何处理签名。相关信息可能涉及关于“动议”特别是“强制发现动议”、提交格式、签名要求和 TTAB 实践相关程序规则的部分。

根据提供的块的大量和深度,我确定了以下相关主题和解决这些主题的块:

1. 签名要求和动议及提交的可接受格式
- 关于提交(包括动议)签名的详细规则在块 5、6、7 中。
- 这些包括关于电子文件、ESTTA 的使用、必需的签名格式(包括带有符号方法“/sig/”的电子签名)的规则。

2. 提交格式和 ESTTA 的使用
- 提交要求、打印格式、大小、纸质提交和特殊例外情况可在块 7、8、9、10、11、12、13 中找到。
- 动议通常必须通过 ESTTA 提交,例外情况需要向主任提交申请并说明理由。

3. 强制动议和发现动议
- 与提交动议(如强制发现动议)、送达和时间相关的特定规则预计将在涵盖发现和动议的部分中找到。
- 发现和相关动议在从块 400 及更高版本开始的章节中介绍。

4. 送达和送达证明
- 动议如何送达以及送达证明和证明的讨论在块 17、18、19、20 中。
- 这些包括在参与方案件中的所有提交(通知异议或取消注册申请除外)必须送达给对方并提供送达证明的要求。

5. 强制发现动议详情
- 发现和动议程序、提交格式、时间、送达和相关制裁在 400 及更高版本(尽管此处未完全显示,但包含这些块是合理的)中得到了广泛涵盖。
- 这些包括披露、发现会议、发现请求的时间、响应、强制动议和制裁。

从以上内容来看,以下块最有可能提供所需信息:

- 块 5、6、7:签名规则和提交格式,包括动议。
- 块 17、18、19、20:提交的送达和送达证明。
- 块 400 至 410 及相关部分(401.01、401.02、401.03、408、410):发现规则,强制动议详情。

这些涵盖了动议的格式(包括强制发现动议)、签名规则、送达和送达证明以及发现程序和管理动议的规则。

与问题不太相关的块是关于异议、取消注册申请、答辩的常规程序规定,这些规定并未特别解决强制发现动议的提交或签名问题。

计划:选择上述相关块,并报告关于强制发现动议提交格式以及签名处理方式的关键程序要点。
将文档分割成 8 个块
块 0: 398 个 token
块 1: 256 个 token
块 2: 389 个 token
块 3: 356 个 token
块 4: 401 个 token
块 5: 277 个 token
块 6: 435 个 token
块 7: 265 个 token
将文档分割成 6 个块
块 0: 353 个 token
块 1: 393 个 token
块 2: 388 个 token
块 3: 398 个 token
块 4: 397 个 token
块 5: 247 个 token
将文档分割成 5 个块
块 0: 325 个 token
块 1: 389 个 token
块 2: 303 个 token
块 3: 344 个 token
块 4: 306 个 token
将文档分割成 8 个块
块 0: 396 个 token
块 1: 354 个 token
块 2: 361 个 token
块 3: 378 个 token
块 4: 388 个 token
块 5: 394 个 token
块 6: 361 个 token
块 7: 61 个 token
将文档分割成 7 个块
块 0: 396 个 token
块 1: 355 个 token
块 2: 377 个 token
块 3: 362 个 token
块 4: 326 个 token
块 5: 397 个 token
块 6: 69 个 token
将文档分割成 3 个块
块 0: 388 个 token
块 1: 373 个 token
块 2: 221 个 token
将文档分割成 8 个块
块 0: 360 个 token
块 1: 314 个 token
块 2: 369 个 token
块 3: 363 个 token
块 4: 361 个 token
块 5: 393 个 token
块 6: 361 个 token
块 7: 358 个 token

==== 在深度 2 处路由 ====
正在评估 45 个块的相关性
选定的块:0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36
更新的便笺簿:
深度 0 推理:
用户想知道提交强制发现动议的格式要求以及如何处理此类动议的签名。

根据对块的评估:

- 块 0、1、2、3、4、5、6、7、8 与相关性最高,因为它们涵盖了提交、动议、签名、送达以及 TTAB 程序中特别是动议和发现的一般要求。
- 这些块包含有关电子文件(通过 ESTTA)、纸质文件例外情况、签名要求、送达要求、提交格式(包括动议)、时间规则和专业人员职责的详细信息。
- 此外,还特别概述了强制发现动议的规则,包括所需的附件、时间和解决发现争议的良好信誉的认证。
- 块 11-19 主要涵盖审判后和上诉程序,相关性较低。

我将选择这些相关的块,以提供关于如何提交强制发现动议以及如何处理这些动议的签名的全面答案。

深度 1 推理:
用户的问题是关于提交强制发现动议的格式要求以及如何处理签名。相关信息可能涉及关于“动议”特别是“强制发现动议”、提交格式、签名要求和 TTAB 实践相关程序规则的部分。

根据提供的块的大量和深度,我确定了以下相关主题和解决这些主题的块:

1. 签名要求和动议及提交的可接受格式
- 关于提交(包括动议)签名的详细规则在块 5、6、7 中。
- 这些包括关于电子文件、ESTTA 的使用、必需的签名格式(包括带有符号方法“/sig/”的电子签名)的规则。

2. 提交格式和 ESTTA 的使用
- 提交要求、打印格式、大小、纸质提交和特殊例外情况可在块 7、8、9、10、11、12、13 中找到。
- 动议通常必须通过 ESTTA 提交,例外情况需要向主任提交申请并说明理由。

3. 强制动议和发现动议
- 与提交动议(如强制发现动议)、送达和时间相关的特定规则预计将在涵盖发现和动议的部分中找到。
- 发现和相关动议在从块 400 及更高版本开始的章节中介绍。

4. 送达和送达证明
- 动议如何送达以及送达证明和证明的讨论在块 17、18、19、20 中。
- 这些包括在参与方案件中的所有提交(通知异议或取消注册申请除外)必须送达给对方并提供送达证明的要求。

5. 强制发现动议详情
- 发现和动议程序、提交格式、时间、送达和相关制裁在 400 及更高版本(尽管此处未完全显示,但包含这些块是合理的)中得到了广泛涵盖。
- 这些包括披露、发现会议、发现请求的时间、响应、强制动议和制裁。

从以上内容来看,以下块最有可能提供所需信息:

- 块 5、6、7:签名规则和提交格式,包括动议。
- 块 17、18、19、20:提交的送达和送达证明。
- 块 400 至 410 及相关部分(401.01、401.02、401.03、408、410):发现规则,强制动议详情。

这些涵盖了动议的格式(包括强制发现动议)、签名规则、送达和送达证明以及发现程序和管理动议的规则。

与问题不太相关的块是关于异议、取消注册申请、答辩的常规程序规定,这些规定并未特别解决强制发现动议的提交或签名问题。

计划:选择上述相关块,并报告关于强制发现动议提交格式以及签名处理方式的关键程序要点。

深度 2 推理:
用户的问题是关于提交强制发现动议的格式以及签名处理方式。相关信息可能包含在关于动议、发现程序、提交格式、签名要求和服务规则的部分。

涵盖签名要求的块(5-12)提供了关于法律签名、电子签名、谁必须签名(律师或具有法律约束力的当事人)以及签名内容的详细规则。

块 0、4、7-10、15-18 讨论了提交(包括动议)的必需格式、通过 ESTTA 电子提交的强制要求以及纸质提交的例外情况。

块 23-35 涉及提交的送达,包括向所有当事人送达的要求、送达方式和送达证明。

最后,强制发现动议等发现相关动议及其提交细节应在 400 及更高版本(尽管此处未完全可见,但推理已将这些块视为相关)的块中。

因此,选择块 0、4、5、6、7、8、9、10、11、12、15、16、17、18、23、24、25、26、27、28、29、30、31、32、33、34、35、36 作为最相关的,以提供关于强制发现动议的提交格式和签名的全面答案。

在深度 2 返回 28 个相关块

==== 前 3 个检索到的段落 ====

段落 1(ID:0.0.5):
----------------------------------------
104 业务以书面形式进行
37 C.F.R. § 2.190(b) 电子商标文件。… 与商标审判和上诉委员会程序相关的文件必须通过 ESTTA 以电子方式提交给委员会。37 C.F.R. § 2.191 办公室根据书面记录采取行动。所有与办公室的业务都必须以书面形式进行。办公室的行动将完全基于书面记录。在存在分歧或疑问时,不考虑任何所谓的口头承诺、约定或谅解。除与委员会参与的发现会议(参见 TBMP § 401.01)和电话会议(参见 TBMP § 413.01 和 TBMP § 502.06)外,所有与委员会的业务都应以书面形式进行。37 C.F.R. § 2.191。当事人或其律师或其他授权代表亲自出席办公室是不必要的,除非是根据 37 C.F.R. § 2.120(j) 规定的庭前会议,或根据 37 C.F.R. § 2.129 的规定,如果当事人希望进行最终听证的口头辩论。委员会的决定将完全基于其书面记录。[注 1.] 在委员会程序中提交的文件必须通过 ESTT A 提交。37 C.F.R. § 2.190(b)。参见 TBMP § 110.01(a)。委员会程序以英语进行。如果一方打算依赖任何非英语的提交材料,则该方应同时提交这些材料的翻译件。如果未提交翻译件,则可能不考虑这些提交材料。[注 2.] 注释:

1. 参见。
----------------------------------------

段落 2(ID:0.0.5.4):
----------------------------------------
文件还应包括描述其性质的标题,例如,“异议通知”、“答辩”、“强制动议”、“反对被告简易判决动议的辩护状”或“依赖通知”。
作为委员会参与方程序的申请文件应提交给委员会,而不是商标业务部,并在其首页顶部注明申请序列号以及参与方程序的编号和标题。同样,根据商标法 § 7,15 U.S.C. § 1057,对委员会参与方程序中的注册进行修改、更正或放弃的请求,以及为该注册提交的任何新的授权委托书、国内代表指定或地址变更,应提交给委员会,而不是商标业务部,并在其首页顶部注明注册号以及参与方程序的编号和标题。[注 2.] 100-14 June   2024
TRADEMARK TRIAL AND APPEAL BOARD MANUAL OF PROCEDURE§ 105
注释:

1. 37 C.F.R. § 2.194。2. 37 C.F.R. § 2.194。106.02  提交签名
37 C.F.R. § 2.119(e) 在参与方程序中提交的每一份文件,以及提交异议的延期请求,都必须由提交方或其律师或其他授权代表签名,但未签名的提交文件如果提交给办公室的签名副本在办公室的通知期限内提交,则不会被拒绝考虑。37 C.F.R. § 11.14(e) 出庭。
----------------------------------------

段落 3(ID:0.0.5.5):
----------------------------------------
除本节 (a)、(b) 和 (c) 段所述人员外,其他任何人不得代表客户在商标事务中在办公室执业。(e)  除本章 § 2.11(a) 另有规定外,个人可以代表自己或以下人员在商标或其他非专利事务中出庭:
(1)  他/她是成员的事务所;
(2)  他/她是合伙人的合伙企业;或
(3)  他/她是公司或协会的官员,并且他/她被授权代表该组织。37 C.F.R. § 11.18 签名和为提交给办公室的信函提供的证明。(a)  对于在专利、商标和其他非专利事务中提交给办公室的所有文件,以及在纪律程序中提交给听证官的所有文件,除申请人或当事人必须签名的信函外,由执业人员提交给办公室的每一份信函都必须带有签名,由该执业人员亲自签名或插入,并符合 § 1.4(d)(1)、§ 1.4(d)(2) 或 § 2.193(a) 的规定。
----------------------------------------

GPT 4.1-mini 的结果显示了在文档中迭代提取相关组件的过程,便笺簿解释了其思考过程!在深度 1 处,模型识别出“关于提交(包括动议)签名的详细规则”和“ESTTA 的使用,必需的签名格式(包括带有符号方法‘/sig/’的电子签名)”是回答查询的关键组成部分。

到深度 2 时,便笺簿通过精确隔离包含电子签名(块 5-12)重要规定的块,同时保持对缺失内容的认识,并指出“发现相关动议……应在 400 及更高版本(尽管此处未完全可见……)的块中”,展示了复杂的判断力。

此过程表明 GPT 4.1 如何通过迭代深入挖掘相关内容并沿途解释其推理过程(使其更容易调试模型选择块的原因)来模仿法律分析师。

3.6 答案生成

现在,让我们使用 GPT-4.1 和检索到的段落生成答案。

我们在这里使用了一个巧妙的技巧,动态构建了一个文字列表(这强制模型只能选择我们提供的选项之一——在本例中是段落 ID)。我们能提供的选项数量有限制,因此如果您发现系统引用了超过 500 个文档,那么此解决方案可能不起作用。在这种情况下,您可以设置一个过滤器,最多可以引用 500 个潜在引用,或者要求模型在响应中引用确切的 ID,然后后处理响应以提取 ID,例如它可能说“... [文档 0.0.12]”,您可以使用一些正则表达式来提取引用。

from typing import List, Dict, Any
from pydantic import BaseModel, field_validator

class LegalAnswer(BaseModel):
    """法律问题的结构化响应格式"""
    answer: str
    citations: List[str]

    @field_validator('citations')
    def validate_citations(cls, citations, info):
        # 从 model_config 访问 valid_citations
        valid_citations = info.data.get('_valid_citations', [])
        if valid_citations:
            for citation in citations:
                if citation not in valid_citations:
                    raise ValueError(f"无效引用:{citation}。必须是以下之一:{valid_citations}")
        return citations

def generate_answer(question: str, paragraphs: List[Dict[str, Any]], 
                   scratchpad: str) -> LegalAnswer:
    """从检索到的段落生成答案。"""
    print("\n==== 生成答案 ====")

    # 提取有效的引用 ID
    valid_citations = [str(p.get("display_id", str(p["id"]))) for p in paragraphs]

    if not paragraphs:
        return LegalAnswer(
            answer="我无法在文档中找到相关信息来回答这个问题。",
            citations=[],
            _valid_citations=[]
        )

    # 为模型准备上下文
    context = ""
    for paragraph in paragraphs:
        display_id = paragraph.get("display_id", str(paragraph["id"]))
        context += f"段落 {display_id}:\n{paragraph['text']}\n\n"

    system_prompt = """您是一位法律研究助理,负责回答有关商标审判和上诉委员会程序手册(TBMP)的问题。

仅根据提供的段落回答问题。不要依赖任何基础知识或外部信息,也不要从段落中推断。
引用与答案相关的段落中的短语。这将帮助您更具体和准确。
在答案的每个陈述中包含段落 ID 的引用。有效的引用 ID 是:{valid_citations_str}
保持您的答案清晰、精确和专业。
"""
    valid_citations_str = ", ".join(valid_citations)

    # 使用结构化输调用模型
    response = client.responses.parse(
        model="gpt-4.1",
        input=[
            {"role": "system", "content": system_prompt.format(valid_citations_str=valid_citations_str)},
            {"role": "user", "content": f"问题:{question}\n\n便笺簿(导航推理):\n{scratchpad}\n\n段落:\n{context}"}
        ],
        text_format=LegalAnswer,
        temperature=0.3
    )

    # 在解析后添加验证信息
    response.output_parsed._valid_citations = valid_citations

    print(f"\n答案:{response.output_parsed.answer}")
    print(f"引用:{response.output_parsed.citations}")

    return response.output_parsed

# 生成答案
answer = generate_answer(question, navigation_result["paragraphs"], 
                       navigation_result["scratchpad"])
==== 生成答案 ====

答案:根据商标审判和上诉委员会(TTAB)的规定,强制发现动议必须通过 ESTTA 以电子方式提交,除非 ESTTA 因技术问题不可用或存在特殊情况,在这种情况下,可以允许纸质提交并附带书面说明(“与商标审判和上诉委员会程序相关的文件必须通过 ESTTA 以电子方式提交给委员会”;“规定要求所有提交均必须通过 ESTTA 以电子方式提交给委员会,但允许纸质提交的某些有限例外情况除外。任何允许的纸质提交都必须附带书面说明,表明 ESTTA 因技术问题而不可用,或存在特殊情况,并且在需要时,附带所需费用的主任申请” 0.0.5.0, 0.0.5.5.7.3)。

动议应包含描述其性质的标题,例如“强制动议”,并在首页顶部注明适当的程序编号和标题(“文件还应包括描述其性质的标题,例如‘强制动议’……并在首页顶部注明适当的申请序列号以及参与方程序的编号和标题” 0.0.5.4)。

每一份提交文件,包括强制发现动议,都必须由提交方或其律师或其他授权代表签名。对于通过 ESTTA 的电子文件,不需要传统的亲笔签名;而是使用电子签名。签名人必须亲自输入由签名人采用的字母、数字、空格和/或标点符号的组合,并将其放在两个斜杠(“/”)符号之间(例如,/John Smith/),并且签名人的姓名和头衔或职位必须出现在签名下方或旁边(“通过 ESTTA 提交的电子文件无需传统签名。根据 37 C.F.R. § 2.193(c) 的电子签名是电子文件所必需的。当事方或其代表输入一个被采纳为签名的‘符号’。委员会将接受任何字母、数字、空格和/或标点符号的组合作为有效签名,前提是它位于两个斜杠(‘/’)符号之间”;“在商标申请、注册或委员会程序中签署文件的个人的名字和姓氏,以及头衔或职位,必须立即显示在签名下方或旁边” 0.0.5.5.6.2, 0.0.5.5.6.0)。

如果文件由当事方的律师或其他授权代表代表当事方提交,则该文件必须带有该律师或代表的签名,除非该文件是要求当事人亲自签名的文件(0.0.5.5.6.3)。如果提交了未签名或签名不当的文件,如果根据委员会的通知在规定期限内提交了正确签名的副本,则不会拒绝考虑(0.0.5.5.6.4)。

总之:通过 ESTTA 以电子方式提交强制发现动议,使用上述电子签名,并确保包含签名人的姓名和头衔。如果需要纸质提交,请遵循纸质提交和签名的具体要求。
引用:['0.0.5.0', '0.0.5.4', '0.0.5.5.6.0', '0.0.5.5.6.2', '0.0.5.5.6.3', '0.0.5.5.6.4', '0.0.5.5.7.3']

GPT 4.1 有效地将引用整合到其响应中,同时保持清晰的信息流。每个程序性要求都链接到特定的权威参考(如“0.0.5.0”和“0.0.5.5.6.2”),从而生成了一个信息丰富且来源精确的响应。

它没有简单地在最后列出引用,而是使用括号注释将它们直接融入内容中,放在每个关键要求之后。这种方法将标准的规则陈述转化为一个有充分依据的法律分析,其中关于 ESTTA 提交程序、电子签名要求和纸质提交例外的陈述立即得到了相应监管引用的支持。

3.7 答案验证

首先,让我们看一下引用的段落:

cited_paragraphs = []
for paragraph in navigation_result["paragraphs"]:
    para_id = str(paragraph.get("display_id", str(paragraph["id"])))
    if para_id in answer.citations:
        cited_paragraphs.append(paragraph)


# 向受众显示引用的段落
print("\n==== 引用的段落 ====")
for i, paragraph in enumerate(cited_paragraphs):
    display_id = paragraph.get("display_id", str(paragraph["id"]))
    print(f"\n段落 {i+1}(ID:{display_id}):")
    print("-" * 40)
    print(paragraph["text"])
    print("-" * 40)
==== 引用的段落 ====

段落 1(ID:0.0.5):
----------------------------------------
104 业务以书面形式进行
37 C.F.R. § 2.190(b) 电子商标文件。… 与商标审判和上诉委员会程序相关的文件必须通过 ESTTA 以电子方式提交给委员会。37 C.F.R. § 2.191 办公室根据书面记录采取行动。所有与办公室的业务都必须以书面形式进行。办公室的行动将完全基于书面记录。在存在分歧或疑问时,不考虑任何所谓的口头承诺、约定或谅解。除与委员会参与的发现会议(参见 TBMP § 401.01)和电话会议(参见 TBMP § 413.01 和 TBMP § 502.06)外,所有与委员会的业务都应以书面形式进行。37 C.F.R. § 2.191。当事人或其律师或其他授权代表亲自出席办公室是不必要的,除非是根据 37 C.F.R. § 2.120(j) 规定的庭前会议,或根据 37 C.F.R. § 2.129 的规定,如果当事人希望进行最终听证的口头辩论。委员会的决定将完全基于其书面记录。[注 1.] 在委员会程序中提交的文件必须通过 ESTT A 提交。37 C.F.R. § 2.190(b)。参见 TBMP § 110.01(a)。委员会程序以英语进行。如果一方打算依赖任何非英语的提交材料,则该方应同时提交这些材料的翻译件。如果未提交翻译件,则可能不考虑这些提交材料。[注 2.] 注释:

1. 参见。
----------------------------------------

段落 2(ID:0.0.5.4):
----------------------------------------
文件还应包括描述其性质的标题,例如,“异议通知”、“答辩”、“强制动议”、“反对被告简易判决动议的辩护状”或“依赖通知”。
作为委员会参与方程序的申请文件应提交给委员会,而不是商标业务部,并在其首页顶部注明申请序列号以及参与方程序的编号和标题。同样,根据商标法 § 7,15 U.S.C. § 1057,对委员会参与方程序中的注册进行修改、更正或放弃的请求,以及为该注册提交的任何新的授权委托书、国内代表指定或地址变更,应提交给委员会,而不是商标业务部,并在其首页顶部注明注册号以及参与方程序的编号和标题。[注 2.] 100-14 June   2024
TRADEMARK TRIAL AND APPEAL BOARD MANUAL OF PROCEDURE§ 105
注释:

1. 37 C.F.R. § 2.194。2. 37 C.F.R. § 2.194。106.02  提交签名
37 C.F.R. § 2.119(e) 在参与方程序中提交的每一份文件,以及提交异议的延期请求,都必须由提交方或其律师或其他授权代表签名,但未签名的提交文件如果提交给办公室的签名副本在办公室的通知期限内提交,则不会被拒绝考虑。37 C.F.R. § 11.14(e) 出庭。
----------------------------------------

段落 3(ID:0.0.5.5.6.0):
----------------------------------------
办公室将接受符合本节 (c) 款要求的电子签名,用于纸质文件或通过 TEAS 或 ESTTA 提交的文件。(b)  原始签名副本。如果提交了原始签名副本,则提交人应保留原始文件作为真实性证据。如果出现真实性问题,办公室可以要求提交原始文件。(c)  电子签名要求。以电子方式签署文件的个人必须:
(1)  在电子提交文件的签名块中,在两个斜杠(“/”)符号之间亲自输入已采用为签名的任何字母、数字、空格和/或标点符号组合;或
(2)  根据主任指定的其他形式的电子签名签署核实声明。(d)  签名人必须已识别。在商标申请、注册或委员会程序中签署文件的个人的名字和姓氏,以及头衔或职位,必须立即显示在签名下方或旁边。(e)  签名人。在商标申请或注册相关的提交文件中,必须按照本节 (e)(1) 至 (9) 款的规定进行签名。(2)  响应、申请修改、明确放弃请求、最终行动复议请求和分割请求。对办公室行动的响应、申请修改、明确放弃请求、最终行动复议请求和分割请求必须由申请或注册的所有人,或具有法律约束力的所有人(例如
----------------------------------------

段落 4(ID:0.0.5.5.6.2):
----------------------------------------

* * * *
(i)  法律要求的认证文件。当法律要求认证文件时,副本或传真传输的认证是不可接受的。在委员会的参与方或单方程序中提交的每一份文件,以及提交异议的延期请求,都必须由提交方或其律师或其他授权代表(视情况而定)签名,并且必须识别签名人。[注 1.] 通过 ESTTA 提交的电子文件无需传统签名。电子签名根据 37 C.F.R. § 2.193(c) 是电子文件所必需的。当事方或其代表输入一个被采纳为签名的“符号”。委员会将接受任何字母、数字、空格和/或标点符号的组合作为有效签名,前提是它位于两个斜杠(“/”)符号之间。[注 2.] ESTTA 中的电子文件封面必须由提交方、当事方的律师或其他授权代表(视情况而定)签名。有关使用 ESTTA 提交文件的更多信息,请参见 TBMP § 110。当事人可以在委员会程序中自行代理,如果当事人居住在美国,则可以由律师代理当事人。[注 4.] 参见 TBMP § 114(关于“其他授权代表”的含义)和 TBMP § 527.02(关于 Fed. R. Civ. P. 11 制裁的动议)。当事人自行代理委员会程序时,当事人必须签署提交给委员会的任何文件。
----------------------------------------

段落 5(ID:0.0.5.5.6.3):
----------------------------------------
如果合伙企业当事人选择自行代理,则合伙企业应签署合伙企业提交的文件。如果公司或协会当事人选择自行代理,则该公司的官员或协会的官员应代表该公司或协会签名。如果共同申请人选择自行代理,则所有共同申请人必须签署提交给委员会的任何文件。[注 5.] 如果文件由当事方的律师或其他授权代表代表当事方提交,则该文件必须带有该律师或代表的签名,并由该律师或代表亲自签名或插入,除非它是要求当事人亲自签名的文件。签名文件的律师或其他授权代表,并将其提交给委员会代表当事人,应记住该文件的签名构成了对 37 C.F.R. § 11.18(b) 中规定的要素的认证,并且该规则的规定违反可能导致制裁或纪律处分。[注 6.] 参见 TBMP § 114.04(关于“其他授权代表”的含义)和 TBMP § 527.02(关于 Fed. R. Civ. P. 11 制裁的动议)。传输纸质文件以提交给委员会的人员可以签署封面信或传输信,办公室不要求当事人、律师或授权代表签署封面信或传输信。不适合一个人为另一个人签名,例如,“John Smith,代表 John Doe”或“John Doe,经 John Smith”。[注 7.]
----------------------------------------

段落 6(ID:0.0.5.5.6.4):
----------------------------------------
提交给委员会的文件应包括签名人的姓名(打印或键入形式)[注 8];签名人的身份描述(例如,如果提交方是个人,则为作为当事人的身份;如果提交方是公司,则为公司官员;或作为提交方的律师);以及签名人的业务地址和电话号码。在纸质或允许的纸质提交的罕见情况下,在提交文件中包含签名人的地址和电话号码至关重要,因为办公室收到的邮件在发送到办公室内部的最终目的地之前会被打开。因此,委员会很少看到在委员会程序中提交的文件的邮寄信封上的退货地址。根据 37 C.F.R. § 2.193(b),必须向委员会提交签名文件的副本,因为要求使用 ESTT A 提交文件。应保留原始文件作为真实性证据。如果出现已提交副本真实性的问题,办公室可以要求提交原始文件。[注 9.] 尽管要求提交给委员会的文件必须签名,但如果允许的纸质文件未签名,如果提交给委员会的签名副本在委员会通知的期限内提交,则不会拒绝考虑。[注 10.] 同样,无论是在 ESTT A 提交还是纸质提交的文件,如果签名不当,如果提交给委员会的正确签名副本在委员会通知的期限内提交,则不会拒绝考虑。
----------------------------------------

段落 7(ID:0.0.5.5.7.3):
----------------------------------------
长,并且不包含延伸到纸张边缘的标签或其他此类装置;
(3)  如果纸质提交包含分隔符,则分隔符不得带有任何突出的标签或其他装置,并且必须与提交材料使用相同尺寸和重量的纸张;
(4)  纸质提交不得钉装或装订;
(5)  纸质提交的所有页面都必须编号,并且展品应按照 § 2.123(g)(2) 的规定进行标识;
June   2024100-19
§ 106.03 GENERAL INFORMATION
(6)  与纸质提交相关的展品必须以纸质形式提交,并符合纸质提交的要求。(c)  为被视为机密,提交给商标审判和上诉委员会的文件,如果根据 § 2.125(f) 被视为全部或部分机密,则必须使用 ESTTA 中提供的“机密”选项提交,或者在适当的情况下,在单独的纸质封面下提交。提交文件及其封面都必须标有“机密”,并必须标识案件编号和当事人。必须同时提交一份供公众查看的提交文件副本,其中已编辑机密部分。规定要求所有提交均必须通过 ESTTA 以电子方式提交给委员会,但允许纸质提交的某些有限例外情况除外。任何允许的纸质提交都必须附带书面说明,表明 ESTTA 因技术问题而不可用,或存在特殊情况,并且在需要时,附带所需费用的主任申请。[注 1.]
----------------------------------------

“文字列表”技巧强制模型仅引用特定的段落 ID(如“0.0.5.4”)而不是编造自己的引用或突出显示随机文本——可以将其想象成创建了一个数字“目录”,GPT-4.1 只能从中选择。此解决方案可确保您获得可验证的引用路径,直接指向原始材料,从而解决了长上下文 RAG 中的一个重要问题。

最后,让我们使用 LLM 作为裁判的方法来验证答案。

from typing import List, Dict, Any, Literal
from pydantic import BaseModel

class VerificationResult(BaseModel):
    """验证结果格式"""
    is_accurate: bool
    explanation: str
    confidence: Literal["high", "medium", "low"]

def verify_answer(question: str, answer: LegalAnswer, 
                 cited_paragraphs: List[Dict[str, Any]]) -> VerificationResult:
    """
    验证答案是否基于引用的段落。

    参数:
        question:用户的问题
        answer:生成的答案
        cited_paragraphs:答案中引用的段落

    返回:
        包含准确性评估、解释和置信度级别的验证结果
    """
    print("\n==== 验证答案 ====")

    # 准备包含引用段落的上下文
    context = ""
    for paragraph in cited_paragraphs:
        display_id = paragraph.get("display_id", str(paragraph["id"]))
        context += f"段落 {display_id}:\n{paragraph['text']}\n\n"

    # 准备系统提示
    system_prompt = """您是一位法律信息的事实核查员。
您的工作是验证提供的答案:

1. 根据源段落是否准确
2. 是否正确使用引用

请严格批判,查找任何事实错误或未经证实的声明。
根据段落对问题的直接支持程度分配置信度级别:

- high:答案全面、准确,并直接由段落支持
- medium:答案大部分准确,但可能不完整或存在小问题
- low:答案存在重大差距、不准确或段落支持不足
"""

    response = client.responses.parse(
        model="o4-mini",
        input=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"""
问题:{question}

待验证的答案:
{answer.answer}

使用的引用:{', '.join(answer.citations)}

源段落:
{context}

该答案是否准确并得到源段落的正确支持?
根据完整性和准确性分配置信度级别(高、中或低)。
            """}
        ],
        text_format=VerificationResult
    )

    # 记录并返回验证结果
    print(f"\n准确性验证:{'通过' if response.output_parsed.is_accurate else '失败'}")
    print(f"置信度:{response.output_parsed.confidence}")
    print(f"解释:{response.output_parsed.explanation}")

    return response.output_parsed

# 仅使用引用的段落验证答案
verification = verify_answer(question, answer, cited_paragraphs)

# 显示最终结果及验证信息
print("\n==== 最终验证答案 ====")
print(f"验证:{'通过' if verification.is_accurate else '失败'} | 置信度:{verification.confidence}")
print("\n答案:")
print(answer.answer)
print("\n引用:")
for citation in answer.citations:
    print(f"- {citation}")
==== 验证答案 ====

准确性验证:通过
置信度:high
解释:答案正确地指出,强制发现动议必须通过 ESTTA 以电子方式提交(37 C.F.R. § 2.190(b) 和 2.193(b)),纸质提交仅在技术故障或特殊情况的有限例外情况下允许。它准确地描述了所需的标题和标题放置(TBMP § 105),并恰当地总结了电子文件的签名要求(37 C.F.R. § 2.193(c) 和 TBMP §§ 106.02、106.02(b)–(e)),包括使用斜杠包围的电子签名以及签名人姓名和头衔的标识。它还正确地指出了关于有缺陷签名的规则(37 C.F.R. § 2.119(e) 和 TBMP § 106.02)。引用与源段落一致。

==== 最终验证答案 ====
验证:通过 | 置信度:high

答案:
根据商标审判和上诉委员会(TTAB)的规定,强制发现动议必须通过 ESTTA 以电子方式提交,除非 ESTTA 因技术问题不可用或存在特殊情况,在这种情况下,可以允许纸质提交并附带书面说明(“与商标审判和上诉委员会程序相关的文件必须通过 ESTTA 以电子方式提交给委员会”;“规定要求所有提交均必须通过 ESTTA 以电子方式提交给委员会,但允许纸质提交的某些有限例外情况除外。任何允许的纸质提交都必须附带书面说明,表明 ESTTA 因技术问题而不可用,或存在特殊情况,并且在需要时,附带所需费用的主任申请” 0.0.5.0, 0.0.5.5.7.3)。

动议应包含描述其性质的标题,例如“强制动议”,并在首页顶部注明适当的程序编号和标题(“文件还应包括描述其性质的标题,例如‘强制动议’……并在首页顶部注明适当的申请序列号以及参与方程序的编号和标题” 0.0.5.4)。

每一份提交文件,包括强制发现动议,都必须由提交方或其律师或其他授权代表签名。对于通过 ESTTA 的电子文件,不需要传统的亲笔签名;而是使用电子签名。签名人必须亲自输入由签名人采用的字母、数字、空格和/或标点符号的组合,并将其放在两个斜杠(“/”)符号之间(例如,/John Smith/),并且签名人的姓名和头衔或职位必须出现在签名下方或旁边(“通过 ESTTA 提交的电子文件无需传统签名。根据 37 C.F.R. § 2.193(c) 的电子签名是电子文件所必需的。当事方或其代表输入一个被采纳为签名的‘符号’。委员会将接受任何字母、数字、空格和/或标点符号的组合作为有效签名,前提是它位于两个斜杠(‘/’)符号之间”;“在商标申请、注册或委员会程序中签署文件的个人的名字和姓氏,以及头衔或职位,必须立即显示在签名下方或旁边” 0.0.5.5.6.2, 0.0.5.5.6.0)。

如果文件由当事方的律师或其他授权代表代表当事方提交,则该文件必须带有该律师或代表的签名,除非该文件是要求当事人亲自签名的文件(0.0.5.5.6.3)。如果提交了未签名或签名不当的文件,如果根据委员会的通知在规定期限内提交了正确签名的副本,则不会拒绝考虑(0.0.5.5.6.4)。

总之:通过 ESTTA 以电子方式提交强制发现动议,使用上述电子签名,并确保包含签名人的姓名和头衔。如果需要纸质提交,请遵循纸质提交和签名的具体要求。

引用:

- 0.0.5.0
- 0.0.5.4
- 0.0.5.5.6.0
- 0.0.5.5.6.2
- 0.0.5.5.6.3
- 0.0.5.5.6.4
- 0.0.5.5.7.3

验证步骤会产生一个清晰、结构化的评估,其中引用了特定法规,并有条理地检查了答案的准确性和引用的正确使用。它不仅仅是说“正确”,而是通过解释答案为何正确来提供有用的上下文,让您有信心在向用户呈现答案时附带具体的引用。

4. 基础设施成本

让我们分解一下这种代理 RAG 方法的成本结构:

估算固定与可变成本

  • 估算固定(一次性)成本:
  • 传统 RAG:~$0.43(嵌入 + 元数据生成)
  • 代理 RAG:$0.00(无需预处理)

  • 估算可变(每次查询)成本:

  • 路由器模型(gpt-4.1-mini):
    • 初始路由(20 个块):~$0.10
    • 两个递归级别:~$0.20
  • 综合(gpt-4.1):~$0.05
  • 验证(o4-mini):~$0.01
  • 每次查询总计:~$0.36

虽然每次查询的成本高于传统 RAG,但这种方法提供了:

  • 对新文档的即时结果
  • 更精确的引用
  • 更好地处理释义和概念性问题
  • 无需基础设施维护开销

可以通过以下方式优化成本:

  • 缓存常见查询的结果
  • 限制模型调用的最大 token 数
  • 使用混合方法,先对文档进行预过滤

5. 与传统 RAG 的优势和权衡

优势

  • 零摄取延迟:无需预处理即可立即回答新文档中的问题。
  • 动态导航:通过关注有希望的部分来模仿人类阅读模式。
  • 跨部分推理:模型可以找到独立块检索可能遗漏的文档部分之间的联系,可能提高生成答案的准确性并节省优化检索管道的时间。

权衡

  • 每次查询成本更高:与基于嵌入的检索相比,每次查询需要更多的计算。
  • 延迟增加:分层导航的处理时间比简单的向量查找要长。
  • 可扩展性有限:对于预处理更有效的大型文档集合,可能难以处理。

6. 未来步骤

我们可以对所采取的方法进行一些修改:

  • 生成知识图谱:我们可以利用 GPT 4.1-mini 的大型上下文窗口来迭代生成详细的知识图谱,然后 GPT 4.1 可以遍历该图谱来回答问题。这样,无论问题如何,我们只需要“摄取”文档一次。
  • 改进的便笺簿工具:便笺簿工具可以提供更多选择,例如编辑或删除过去的记忆。这将允许模型选择最适合当前问题的选项。
  • 调整深度:我们可以调整分层导航的深度,以在成本和性能之间找到正确的平衡。某些用例需要句子级别的引用(如法律文件),而其他用例可能只需要段落级别的引用(如新闻文章)。

7. 要点

  1. 上下文窗口是超级能力:百万 token 的上下文窗口使得即时导航文档成为可能。
  2. 分层方法模仿人类阅读:代理路由就像人类浏览文档以查找相关部分一样。
  3. 便笺簿支持多步推理:维护推理记录可提高导航质量。
  4. 快速实现,无需数据库:整个系统都可以仅通过 API 调用构建,无需基础设施。
  5. 验证可提高可靠性:LLM 作为裁判模式可在错误到达用户之前捕获错误。

================================================================================

3B. 用例:制药研发的 AI 联合科学家

制药研发 AI 联合科学家

本节详细介绍了如何构建一个充当“联合科学家”的 AI 系统,以通过专注于优化特定约束下的药物合成工艺来加速制药研发中的实验设计。

🗂️ 摘要矩阵

此表总结了此特定 AI 联合科学家实现的核心技术选择及其基本原理。

| 层 | 选择 | 效用 |

选择 效用
构思 o4-mini(并行角色扮演代理) 快速且经济高效地生成多样化的假设和协议;角色扮演可增强创造力。
基础化 外部工具调用(chem_lookupcost_estimatoroutcome_db 等) 确保计划基于现实世界数据(化学性质、成本、过往结果)。
排名 o4-mini(成对锦标赛比较) 超越简单评分的细微评估;有效选择有前景的候选者。
批判/综合 o3(深度审查和综合) 提供严格的高级分析,识别风险,并确保科学有效性。
安全(可选) gpt-4.1-mini(定向检查) 在移交给人类之前增加一层额外的专业安全审查。
学习 o3 + 代码解释器(结果分析 → DB) 系统地捕获实验结果,从而实现随时间的持续改进。
核心技术 多代理协作与升级 利用不同模型(速度 vs. 深度)的优势来执行复杂的多步骤推理任务。

注意:模型标识符截至 2025 年 4 月准确,可能会发生变化。

1. 场景快照

  • 问题空间:优化制药研发中的复杂实验程序,例如在严格的约束条件下提高新化合物“XYZ-13”的合成产率。
  • 用户:参与药物发现和开发的科研人员和实验室技术员。
  • 典型提问:
    1. 建议 3 种不同的方案,通过测试不同的催化剂,将 XYZ-13 的产率提高 ≥15%,同时预算保持在 15,000 美元以内,并使用批准的试剂。
    2. 提出在 60°C 以下(由于过去的加热问题)优化 XYZ-13 产率的方案,在预算内探索不同的批准溶剂。
    3. 设计两种 XYZ-13 产率策略(目标为 ≥15%):a. 一种在 15,000 美元预算内最大化潜在产率,b. 一种优先考虑成本低于 10,000 美元。
  • 约束:
    • 预算:在规定的财务限额内运作(例如,每次实验系列 15,000 美元)。
    • 监管/安全:仅使用预先批准的化学品/试剂,并严格遵守安全规程。
    • 人类监督:最终的实验计划必须由人类专家在执行前进行审查和验证。

传统上,优化此类实验需要数周的手动规划、文献回顾、迭代实验和分析。这种 AI 联合科学家方法旨在通过自动化假设生成、方案设计和初步评估来显著缩短周期时间,使科学家能够专注于更高级别的策略和最终验证。它将科学家的角色从手动执行规划步骤转变为专家监督和与 AI 的协作。

2. 架构(多代理推理)

该系统采用多代理架构,模拟一个高性能的科学团队。充当不同角色的不同 AI 组件(例如,构思、批判和从结果中学习)使用各种模型和工具进行协作,以执行工作流程。

AI 联合科学家架构

2.1. 科学家输入与约束:

该过程从科学家定义目标、目标化合物和约束开始。

from openai import OpenAI
from agent_utils import Context, call_openai, log_json

# 示例初始输入
user_input = {
    "compound": "XYZ-13",
    "goal": "提高合成产率 15%",
    "budget": 15000,
    "time_h": 48,
    "previous": "之前的尝试在高温下失败;探索潜在的催化剂效应。"
}
ctx = Context(client=OpenAI(), **user_input)

2.2. 构思(o4-mini + 工具):

多个 o4-mini 实例,被赋予不同的角色(例如,假设代理方案代理资源代理),并行生成实验计划。分配不同的角色可以鼓励不同的观点,并在构思阶段同时涵盖问题的不同方面。

ROLE_FOCUS = {
    # 假设代理提示
    "hypothesis_agent": """您是一位药物假设专家。 
        请仅专注于分析化合物结构和研究目标以生成可测试的假设。 
        考虑作用机制、结合亲和力预测和潜在的脱靶效应。""",

    # 方案代理提示
    "protocol_agent"  : """您是一位实验室方案专家。 
        设计将有效测试所提供假设的实验程序。 
        专注于实验条件、对照和测量技术。""",

    # 资源代理提示
    "resource_agent"  : """您是一位实验室资源优化专家。 
        审查所提出的方案并优化效率。 
        识别降低试剂使用量、设备时间和总体成本的机会,同时保持科学有效性。""",
}

# 为构思创建结构化提示模板
IDEATION_PROMPT = """您是一位药物 {role} 专家。您的目标是为化合物 {compound} {goal}。
约束:

- 预算:${budget}
- 仅使用批准的试剂
- 在 {time_h} 小时内完成
- 先前尝试:{previous}
以描述您方案的结构化 JSON 进行响应。"""
import json, logging
from pathlib import Path
from typing import Dict, List, Any, Optional
from dataclasses import asdict
from functools import partial

MODEL_IDEATE   = "o4-mini-2025-04-16"  # 用于构思的 o4-mini 模型 - 平衡速度和质量

# 配置日志记录以帮助跟踪实验进度和调试
logging.basicConfig(level=logging.INFO, format="%(message)s")
logging.info(f"运行 ID {ctx.run_id}  化合物:{ctx.compound}")
logging.info(f"日志将存储在:{Path('logs') / ctx.run_id}")

def ideation(ctx: Context):
    logging.info("开始构思阶段...")
    ideas = []
    for role, focus in ROLE_FOCUS.items():
        logging.info(f"正在运行构思代理 ${role}")
        sys = IDEATION_PROMPT.format(role=role, focus=focus, **ctx.prompt_vars())
        usr = f"设计一个在 {ctx.budget} 美元预算内实现 {ctx.goal} 的方案。"
        idea = call_openai(ctx.client, MODEL_IDEATE, sys, usr, ctx)
        ideas.append(idea)
    log_json("ideation_done", ideas, ctx)
    return ideas
运行 ID 9835f69c  化合物:XYZ-13
日志将存储在:logs/9835f69c

构思代理可以使用外部工具,例如 literature_searchchem_lookup(化学数据库)、cost_estimatoroutcome_db(先前实验的结果)来为它们的建议提供数据基础。明确启用并提示模型使用外部工具可确保生成的方案是可行的、合规的并基于现有知识。模型根据任务决定何时以及调用哪个工具。

IDEATION_PROMPT += """
根据需要使用以下工具:

- 使用 `list_available_chemicals` 工具获取批准试剂列表。
- 使用 `chem_lookup` 工具验证试剂的性质。
- 使用 `cost_estimator` 工具根据试剂和建议的步骤计算近似成本。
- 检查 `outcome_db` 以获取 {compound} 的相关先前实验"""

ideas = ideation(ctx)
logging.info("构思完成!")
开始构思阶段...
正在运行构思代理 $hypothesis_agent
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)列出可用化学品
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)结果数据库:XYZ-13,产率,5
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)成本估算器:[{'name': '氯化钯', 'amount': 0.05, 'unit': 'g'}, {'name': '三苯基膦', 'amount': 0.1, 'unit': 'g'}, {'name': '碳酸钾', 'amount': 1, 'unit': 'g'}, {'name': '二甲基甲酰胺', 'amount': 50, 'unit': 'mL'}, {'name': '甲苯', 'amount': 50, 'unit': 'mL'}, {'name': '硼氢化钠', 'amount': 0.1, 'unit': 'g'}, {'name': '三乙胺', 'amount': 0.5, 'unit': 'mL'}], ['圆底烧瓶', '磁力搅拌器', '回流冷凝器'], 36
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
正在运行构思代理 $protocol_agent
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)结果数据库:XYZ-13,产率,5
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)列出可用化学品
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)文献检索:XYZ-13 合成 钯 三苯基膦 配体 产率 提高,无,3
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)成本估算器:[{'name': '醋酸钯', 'amount': 0.05, 'unit': 'g'}, {'name': '三苯基膦', 'amount': 0.1, 'unit': 'g'}, {'name': '碳酸钾', 'amount': 2, 'unit': 'g'}, {'name': '三乙胺', 'amount': 2, 'unit': 'mL'}, {'name': '二甲基甲酰胺', 'amount': 100, 'unit': 'mL'}], ['磁力搅拌器', '油浴', '惰性气体装置'], 48
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
正在运行构思代理 $resource_agent
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)结果数据库:XYZ-13,产率,5
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)列出可用化学品
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)成本估算器:[{'name': '醋酸钯', 'amount': 0.05, 'unit': 'g'}, {'name': '三苯基膦', 'amount': 0.1, 'unit': 'g'}, {'name': '碳酸钾', 'amount': 1, 'unit': 'g'}, {'name': '二甲基甲酰胺', 'amount': 5, 'unit': 'mL'}, {'name': '三乙胺', 'amount': 2, 'unit': 'mL'}], ['圆底烧瓶', '回流冷凝器', '加热套', '磁力搅拌器'], 36
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)化学查找:硼氢化钠,无
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
构思完成!

这些工具定义在 agent_utils.py 中。为了本解决方案的目的,工具调用在 tools.py 中进行了模拟。在实际用例中,这些工具将调用真实的 API。

2.3. 锦标赛排名(o4-mini / o3):

生成的方案根据预期效果、可行性、成本和新颖性等标准进行成对比较。要求模型单独对方案进行评分,不如一次提供两个方案并要求根据特定标准进行直接比较,这样通常能产生更可靠的相对排名。

这种 Elo 式排名可以识别出最有前景的候选者以进行更深入的审查。

TOURNAMENT_PROMPT = """
方案 A:[详细信息...]
方案 B:[详细信息...]

比较方案 A 和方案 B,为化合物 {compound} 实现 {goal},预算为 ${budget},使用批准的试剂。根据以下几点进行评分:

1. 实现 ≥ 15% 产率提高的可能性。
2. 实际可行性(试剂、时间)。
3. 估算成本效益(如有需要,请使用工具)。
4. 科学新颖性/风险。

返回 JSON {{"winner": "A"|"B", "justification": "..."}}。"""

# 这是一个模拟的锦标赛实现,仅比较前两个方案
# 实际实现将以锦标赛淘汰赛风格比较配对
def tournament(protocols: List[Dict[str, Any]], ctx: Context):
    logging.info("开始锦标赛阶段...")
    if len(protocols) == 1:
        return protocols[:1]
    a, b = protocols[0], protocols[1]
    sys = TOURNAMENT_PROMPT.format(**ctx.prompt_vars())
    usr = json.dumps({"A": a, "B": b}, indent=2)
    res = call_openai(ctx.client, MODEL_IDEATE, sys, usr, ctx)
    winner = a if res.get("winner", "A").upper() == "A" else b
    log_json("tournament", res, ctx)
    return [winner]

top_proto = tournament(ideas, ctx)[0]
logging.info("已选出锦标赛获胜者!")
开始锦标赛阶段...
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
已选出锦标赛获胜者!

在早期实验中,我们发现要求模型对方案进行 1-10 分评分会导致结果不一致且分数压缩。锦标赛方法通过强制进行相对判断来解决这个问题,这种判断被证明更可靠。这反映了人类专家的行为——科学家通常比分配绝对分数更容易比较两个选项。

2.4. 深度批判与综合(o3):

排名靠前的方案将传递给 o3 进行严格审查。o3 充当资深科学家,评估科学有效性、方法论、安全性、预算合规性,并提出改进建议或重写最终的、精炼的方案。它也可能调用工具进行验证。

# 使用更强大的模型进行严格审查的深度批判阶段
CRITIQUE_PROMPT = """您是一位资深研究员,正在审查为化合物 {compound} 提出的合成方案,目标是 {goal},预算为 ${budget},使用批准的试剂。请严格审查以下方案:

1. 识别科学缺陷或方法论弱点。
2. 评估安全风险和预算合规性(如有需要,请使用 `cost_estimator` 工具)。
3. 检查与先前 `outcome_db` 结果的一致性(如果相关)。
4. 提出具体的改进建议或重写部分(如有必要)。
5. 提供最终的“通过/不通过”建议。

返回 JSON {{"revised_protocol": ..., "critique": "...", "recommendation": "go|no-go"}}。

待审查方案:
[方案详情...]
"""

MODEL_CRITIQUE = "o3-2025-04-16"  # 用于深度批判的 o3 模型

def critique(protocol: Dict[str, Any], ctx: Context):
    logging.info("开始批判阶段...")
    sys = CRITIQUE_PROMPT.format(**ctx.prompt_vars())
    usr = json.dumps(protocol, indent=2)
    crit = call_openai(ctx.client, MODEL_CRITIQUE, sys, usr, ctx)
    log_json("critique", crit, ctx)
    return crit.get("revised_protocol", protocol)

critiqued = critique(top_proto, ctx)
logging.info("深度批判完成!")
开始批判阶段...
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)成本估算器:[{'name': '氯化钯', 'amount': 0.0045, 'unit': 'g'}, {'name': '三苯基膦', 'amount': 0.013, 'unit': 'g'}, {'name': '硼氢化钠', 'amount': 0.0038, 'unit': 'g'}, {'name': '碳酸钾', 'amount': 0.14, 'unit': 'g'}, {'name': '三乙胺', 'amount': 0.07, 'unit': 'mL'}, {'name': '二甲基甲酰胺', 'amount': 2, 'unit': 'mL'}, {'name': '甲苯', 'amount': 5, 'unit': 'mL'}], ['100 mL 圆底烧瓶', '磁力搅拌器', '回流冷凝器', '惰性气体管线'], 24
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)结果数据库:XYZ-13,无,5
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
深度批判完成!

我们特意使用不同的模型和角色来分隔构思和批判。让同一个模型同时生成和批判自己的工作,通常会导致自我辩护而不是客观评估。o3 模型扮演“资深科学家”的角色,持续识别出 o4-mini 在构思阶段错过的细微方法论弱点。

2.5. (可选)安全检查:

可以使用专门的模型,例如 gpt-4.1-mini,执行最终的安全检查(例如,危险试剂组合)。

# 使用定向模型进行可选的安全检查
SAFETY_PROMPT = """您是一位实验室安全专家。 
请识别此化合物 {compound} 的方案中的危险、不安全条件或合规性问题。 
如有需要,请使用 `chem_lookup` 工具。返回 JSON 评估。"""

MODEL_SAFETY   = "gpt-4.1-mini-2025-04-14"  # 用于安全检查的 gpt-4.1-mini 模型 - 针对指令遵循进行了优化

def safety(protocol: Dict[str, Any], ctx: Context):
    logging.info("开始安全评估...")
    sys = SAFETY_PROMPT.format(**ctx.prompt_vars())
    usr = json.dumps(protocol, indent=2)
    assessment = call_openai(ctx.client, MODEL_SAFETY, sys, usr, ctx)
    log_json("safety", assessment, ctx)
    return {"protocol": protocol, "safety": assessment}

secured = safety(critiqued, ctx)
logging.info("安全检查完成!")
开始安全评估...
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)化学查找:氯化钯,无
(工具)化学查找:三苯基膦,无
(工具)化学查找:硼氢化钠,无
(工具)化学查找:碳酸钾,无
(工具)化学查找:二甲基甲酰胺,无
(工具)化学查找:甲苯,无
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
安全检查完成!

2.6. 人工审查:

AI 生成的最终方案将通过界面呈现给人类科学家进行验证、潜在编辑和最终批准。

# 模拟执行和分析结果
ANALYSIS_PROMPT = """您是一位数据分析师。 
实验是否达到了 {goal}? 分析因素,提出改进建议,并返回结构化 JSON。
"""

def execute_and_analyse(pkt: Dict[str, Any], ctx: Context):
    logging.info("开始模拟执行和分析...")
    # 这些是实验室实验的模拟结果
    mock_results = {
        "yield_improvement": 12.5,
        "success": False,
        "actual_cost": ctx.budget * 0.85,
        "notes": "模拟执行"
    }
    sys = ANALYSIS_PROMPT.format(**ctx.prompt_vars())
    usr = json.dumps({"protocol": pkt, "results": mock_results}, indent=2)
    analysis = call_openai(ctx.client, MODEL_CRITIQUE, sys, usr, ctx)
    log_json("analysis", analysis, ctx)
    return analysis

# 仅当人类审阅者批准时才继续执行
if human_decision["approved"]:
    summary = execute_and_analyse(human_decision, ctx)
    logging.info("分析完成")
else:
    logging.info("方案被人类审阅者拒绝 - 跳过执行")
    summary = None

Path("output").mkdir(exist_ok=True)
out_path = Path("output") / f"{ctx.run_id}_summary.json"
out_path.write_text(json.dumps(summary, indent=2))
print(f"\n🎉 完成。摘要已写入 {out_path}")
模拟执行和分析...
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)文献检索:Pd(0) PPh3 偶联 产率 优化 EDTA 后处理 重结晶 损失,无,3
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
(工具)结果数据库:XYZ-13,产率,5
HTTP 请求:POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
分析完成



🎉 完成。摘要已写入 output/9835f69c_summary.json

3. 模型剧本

o4-minio3 之间进行选择取决于任务的复杂性和所需的深度。对于其他任务,gpt-4.1-mini 在成本和性能之间提供了平衡,而当需要更高的能力或细微差别时,则推荐功能更强大的 gpt4.1

| 任务 | 从...开始 | 何时升级... | 升级到 | 基本原理 |

任务 从...开始 何时升级... 升级到 基本原理
构思与方案生成 o4-mini 假设缺乏深度或需要复杂化学合成的创造力。 o3 o4-mini 可快速生成多样化的方案且成本效益高。当需要更细致的方法时,o3 可提供更深入的科学推理。
方案排名 o4-mini 比较需要更深入的科学评估或多因素权衡。 o3 使用 o4-mini 进行锦标赛式排名可有效识别有前景的候选者。当需要评估细微的科学有效性时,请升级。
深度批判与综合 o3 不适用 - 此关键任务已在使用最强大的模型。 不适用 o3 在严格的科学审查、识别方法论缺陷和综合改进方面表现出色。此任务固有地需要深度推理。
安全评估 gpt-4.1-mini 领域特定的危险需要更高的准确性或专业知识。 gpt-4.1 gpt-4.1-mini 为标准安全检查提供了成本和性能的良好平衡。当需要更高的准确性或更细致的推理来应对复杂安全风险时,请升级到 gpt4.1

关键见解:

此用例例证了一个强大的模式:使用更快、更便宜的模型(o4-mini)来获得广度和初步筛选,然后升级到更强大的模型(o3)来进行深度、关键审查和综合。这种分层方法在优化创造力/速度和严谨性/准确性方面效果显著,同时有效管理计算成本。与工具的集成对于将 AI 的推理基础化为可验证的现实世界数据至关重要。

4. 部署说明

将 AI 联合科学家从原型过渡到实验室使用需要仔细规划。

  • 成本控制:
    • 实现可配置的“模式”(例如 FastStandardThorough),这些模式可以调整 o4-mini 构思代理的数量、o3 批判的深度或可选检查的使用,以平衡结果质量与成本和延迟。
    • 跟踪每个阶段(构思、排名、批判)和每个工具调用的 token 使用情况,以进行精细的成本监控。
  • 可观测性:
    • 记录输入、输出、模型选择、工具调用/响应、延迟和每个阶段的 token 计数。
    • 监控锦标赛排名的性能以及 o3 批判的影响(例如,方案被大幅修改或拒绝的频率)。
    • 跟踪用户交互:人类科学家批准、编辑或拒绝的方案。
  • 安全与合规性:
    • 实现多层安全措施:提示中的约束、基于工具的检查(例如通过 chem_lookup 进行试剂兼容性检查)、可选的专用模型检查(gpt-4.1-mini)、自动化过滤器(例如针对已知危险组合)以及强制性人工审查。
    • 确保工具端点(例如内部数据库)满足安全要求。
  • 推出策略:
    • 从回顾性分析过往实验开始,然后进入影子模式(AI 与人类规划者一起提出方案),然后在广泛采用之前进行有限的实时用例并密切监控。

5. 要点

  1. 模型配对可产生协同效应o4-mini 可快速处理更多内容;o3 可带来精确性和深度。
  2. 工具集成将推理基础化为现实:化学成本和安全约束等现实世界数据为决策提供信息。
  3. 人类科学家仍然是核心:该系统通过消除繁重工作来赋能专家——而不是取代他们。

6. 有用的指南和资源

以下精选资源补充了 AI 联合科学家的设计和实现:

  • 组织代理:例程和交接 使用例程和交接来构建多代理工作流程,这与构思→排名→批判流程相关。

  • GPT-4.1 提示指南 用于改进批判和安全审查准确性的高级提示、工具使用和任务分解。

  • 多代理系统的结构化输出 使用模式验证来强制执行一致的 JSON 输出,以实现代理互操作性。

  • 代理 - OpenAI API 构建 OpenAI 工具多代理系统的综合指南,涵盖了组织、工具使用和最佳实践,这些是本系统架构的基础。

================================================================================

3C. 用例:保险索赔处理

许多企业都面临着数字化手写表格的任务。在本节中,我们将演示如何使用 OpenAI 来数字化和验证手写保险表格。虽然这是保险业的一个常见问题,但相同的技术可以应用于各种其他行业和表格,例如税务表格、发票等。

🗂️ 摘要矩阵

此表总结了此特定 OCR 实现针对保险用例的核心技术选择及其基本原理。

| 层 | 选择 | 效用 |

选择 效用
JSON 输出 结构化输出与 Pydantic 易于指定格式,比 JSON 模式 更好地遵循模式
OCR 和视觉 gpt-4.1 强大的 OCR 和视觉功能,结构化输出
推理 o4-mini 经济实惠但功能强大的推理,支持函数调用
表格验证 自定义函数调用 可以提供与自定义或内部数据库的交互

注意:价格和模型标识符截至 2025 年 4 月准确,可能会发生变化。

1. 场景快照

  • 用户:目标用户是保险服务和运营团队,他们需要从手写表格中提取数据。
  • 典型提问:每个表格都需要不同的结构,以及需要提取的不同字段。
  • 约束:
    • 准确性:需要高准确性以确保数据正确且完整。
    • 不确定性:系统必须处理数据中的不确定性,例如缺失数据、模糊数据和相同字段的不同格式。如果模型无法解决不确定性,系统需要一种请求人工审查的机制。
    • 性能与成本:虽然系统延迟不是关键,但需要高准确性,同时控制成本。我们的目标是将每处理 1000 页的成本控制在 20 美元以内。

2. 架构

解决方案的高级基本架构如下所示。

此任务很复杂,需要广泛的模型功能,包括视觉、函数调用、推理和结构化输出。虽然 o3 能够同时执行所有这些操作,但我们在实验中发现仅使用 o4-mini 不足以达到所需的性能。由于 o3 的相对成本较高,我们选择了一种两阶段方法。

  1. 第一阶段使用 GPT 4.1 的视觉功能执行。此阶段经过优化,可最大程度地提高文本提取的准确性,将不确定性留给推理阶段,并且不进行任何页面上未显示的假设。通过在第一阶段进行 OCR,我们不需要推理模型直接处理图像,这对于推理模型必须执行的所有其他任务来说可能具有挑战性。
  2. 第二阶段利用 o4-mini 的推理能力。我们使用 o4-mini 来验证 OCR 的准确性并将数据提取为结构化格式。重要的是,我们期望 o4-mini 作为二次质量门控——如果在此阶段 OCR 不完整,我们可以使用 o4-mini 来优化和验证原始结果。

为了具体演示这一点,让我们看一下保险表格的示例图像。

虽然表格本身相当简单,但缺少数据和模糊信息,这将使传统 OCR 系统难以正确填写。首先,请注意邮政编码和县已省略。其次,用户的电子邮件地址是模糊的——它可能是 jsmith1@gmail.comjsmithl@gmail.com。在接下来的部分中,我们将逐步介绍精心设计的解决方案如何处理这些歧义并返回正确的表格结果。

环境设置和库代码:

为了使我们的示例代码更清晰,我们将环境设置(例如 pip install 命令)和库函数分解到一个单独的代码块中。这将使我们更容易专注于每个解决方案步骤中的相关逻辑。

# 安装 Python 需求
%pip install -qU pydantic "openai>=1.76.0"

# 所有导入
import os
import json

from pydantic import BaseModel

# 创建 OpenAI 客户端
from openai import OpenAI

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "sk-dummykey"))
注意:您可能需要重新启动内核才能使用更新的软件包。
def run_conversation_loop(
    client,
    messages,
    tools,
    tool_handlers,
    response_format,
    model,
):
    """运行 OpenAI 响应完成循环,通过 tool_handlers 处理函数调用,直到解析最终响应。"""
    summaries = []
    while True:
        print(
            f"正在请求模型 '{model}' 的完成(消息数={len(messages)})"
        )
        response = client.responses.parse(
            model=model,
            input=messages,
            tools=tools,
            text_format=response_format,
            reasoning={"summary": "auto"},
        )
        summaries.append(response.output[0].summary)

        if not response.output_parsed:
            print("助手请求了工具调用,正在解析...")

            reasoning_msg, tool_call = response.output
            messages.append(reasoning_msg)
            messages.append({
                "id": tool_call.id,
                "call_id": tool_call.call_id,
                "type": tool_call.type,
                "name": tool_call.name,
                "arguments": tool_call.arguments,
            })

            if tool_call.name in tool_handlers:
                try:
                    args = json.loads(tool_call.arguments)
                except Exception as exc:
                    print(
                        "解析 %s 参数失败:%s", tool_call.name, exc
                    )
                    args = {}
                result = tool_handlers[tool_call.name](**args)
                messages.append(
                    {
                        "type": "function_call_output",
                        "call_id": tool_call.call_id,
                        "output": str(result),
                    }
                )
                print(f"工具调用 {tool_call.name} 完成,结果:{str(result)}")
            else:
                print("未处理的函数调用:%s", tool_call.name)

        if response.output_parsed is not None:
            print("已收到模型解析结果")
            return response, summaries

流程说明:第一阶段

  1. 图像:从用户智能手机拍摄的表格图像被传递给模型。OpenAI 的模型可以接受各种图像格式,但我们通常使用 PNG 格式来保持文本清晰并减少伪影。在本示例中,我们从公开可用的内容 URL 向模型传递图像。在生产环境中,您可能需要将图像作为签名 URL 传递给托管在您自己的云存储桶中的图像。
  2. 结构化输出模式:我们定义了一个 Pydantic 模型,用于设置输出数据的结构。该模型包含我们需要从表格中提取的所有字段,以及每个字段的适当类型。我们的模型分为几个子组件,每个组件本身就是一个 Pydantic 模型,并由父模型引用。
class PersonContact(BaseModel):
    name: str
    home_phone: str
    work_phone: str
    cell_phone: str
    email: str

class Address(BaseModel):
    street: str
    city: str
    state: str
    zip: str
    county: str

class DwellingDetails(BaseModel):
    coverage_a_limit: str
    companion_policy_expiration_date: str
    occupancy_of_dwelling: str
    type_of_policy: str
    unrepaired_structural_damage: bool
    construction_type: str
    roof_type: str
    foundation_type: str
    has_post_and_pier_or_post_and_beam_foundation: bool
    cripple_walls: bool
    number_of_stories: str
    living_space_over_garage: bool
    number_of_chimneys: str
    square_footage: str
    year_of_construction: str
    anchored_to_foundation: bool
    water_heater_secured: bool

class InsuranceFormData(BaseModel):
    applicant: PersonContact
    co_applicant: PersonContact
    risk_address: Address
    mailing_address_if_different_than_risk_address: Address
    participating_insurer: str
    companion_policy_number: str
    dwelling_details: DwellingDetails
    effective_date: str
    expiration_date: str
  1. 运行 OCR:使用 GPT-4.1 的视觉功能,我们运行管道的第一阶段,以结构化格式提取文档中的文本。此初始阶段旨在实现高准确性,同时将不确定性传递给第二阶段。我们的提示明确指示模型避免推断输入,而是尽可能精确地填写详细信息。对于图像输入,我们将图像输入详细信息设置为 auto,以推断适合图像的详细级别。我们在实验中发现 auto 工作效果很好,但如果您在 OCR 处理中遇到质量问题,可以考虑使用 high
OCR_PROMPT = """您是一位乐于助人的助手,在处理保险表格方面非常出色。

您将收到一份手填保险表格的图片。您的任务是按照给定的结构化格式对数据进行 OCR。
请尽可能精确地填写字段。如果可写字符可能存在歧义(例如 l 或 1,o 或 0),请在字段中包含所有可能性,并用“OR”分隔,尤其是在电子邮件地址方面。
"""

user_content = [
    {"type": "input_text", "text": "这是用户填写的表格照片:"},
    {
        "type": "input_image",
        "image_url": "https://drive.usercontent.google.com/download?id=1-tZ526AW3mX1qthvgi8spaaxxeqFG5_6",
        "detail": "auto",
    },
]

messages = [
    {"role": "system", "content": OCR_PROMPT},
    {"role": "user", "content": user_content},
]

response = client.responses.parse(
    model="gpt-4.1-2025-04-14",
    input=messages,
    text_format=InsuranceFormData,
    # 设置 temp 为 0 以便重现
    temperature=0,
)

s1_json_results = json.dumps(json.loads(response.output_parsed.model_dump_json()), indent=2)
print(s1_json_results)
{
  "applicant": {
    "name": "Smith, James L",
    "home_phone": "510 331 5555",
    "work_phone": "",
    "cell_phone": "510 212 5555",
    "email": "jsmithl@gmail.com OR jsmith1@gmail.com"
  },
  "co_applicant": {
    "name": "Roberts, Jesse T",
    "home_phone": "510 331 5555",
    "work_phone": "415 626 5555",
    "cell_phone": "",
    "email": "jrobertsjr@gmail.com"
  },
  "risk_address": {
    "street": "855 Brannan St",
    "city": "San Francisco",
    "state": "CA",
    "zip": "",
    "county": ""
  },
  "mailing_address_if_different_than_risk_address": {
    "street": "",
    "city": "",
    "state": "",
    "zip": "",
    "county": ""
  },
  "participating_insurer": "Acme Insurance Co",
  "companion_policy_number": "81265919",
  "dwelling_details": {
    "coverage_a_limit": "$900,000",
    "companion_policy_expiration_date": "5/31/27",
    "occupancy_of_dwelling": "Owner",
    "type_of_policy": "Homeowners",
    "unrepaired_structural_damage": false,
    "construction_type": "Frame",
    "roof_type": "Composition",
    "foundation_type": "Raised",
    "has_post_and_pier_or_post_and_beam_foundation": false,
    "cripple_walls": false,
    "number_of_stories": "Greater than 1 story",
    "living_space_over_garage": true,
    "number_of_chimneys": "2",
    "square_footage": "1200",
    "year_of_construction": "2005",
    "anchored_to_foundation": true,
    "water_heater_secured": true
  },
  "effective_date": "5/31/25",
  "expiration_date": "5/31/27"
}

请注意,输出缺少几个字段。在处理的下一阶段,我们将利用 OpenAI 的推理模型来推断缺失的字段。

流程说明:第二阶段

  1. 函数定义:我们定义了一组模型可以用来解决不确定性的自定义函数。在本例中,我们定义了一个可以验证电子邮件地址的函数,方法是检查电子邮件是否存在。这可以用来解决模型必须在多个可能值之间进行选择的模糊电子邮件地址字段。默认情况下,o4-mini 支持内置工具,如网络搜索,在本例中它将使用该工具来解析邮政编码和不完整的地址。
tools = [{
    "type": "function",
    "name": "validate_email",
    "description": "检查电子邮件地址是否有效且存在。",
    "parameters": {
        "type": "object",
        "properties": {
            "email": {
                "type": "string",
                "description": "要验证的电子邮件地址。"
            }
        },
        "required": [
            "email"
        ],
        "additionalProperties": False
    }
},
{
    "type": "function",
    "name": "search_web",
    "description": "执行网络搜索。",
    "parameters": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "要通过搜索引擎运行的搜索查询。"
            }
        },
        "required": [
            "query"
        ],
        "additionalProperties": False
    }
}]
  1. 提示:我们向模型提供一个提示,解释我们已通过 OCR 提取了文本,并要求模型执行推理和函数调用来填补缺失或模糊的字段。
PROMPT = """您是一位乐于助人的助手,在处理保险表格方面非常出色。

您将收到一份 OCR 后的文档的 JavaScript 表示形式。考虑哪些字段存在歧义,并推断如何填写它们。填写所有可能的缺失字段,这些字段可以从现有数据或网络搜索中推断出来。如果您无法填写某个字段,请推断原因。

如有必要,请使用提供的工具来澄清结果。如果 OCR 系统提供了两个可能性,请尽力确定哪个选项是正确的。
"""
messages = [
    {"role": "system", "content": PROMPT},
    {"role": "user", "content": s1_json_results},
]

# 出于演示目的,我们将硬编码正确的电子邮件答案。
def email_mock(*args, **kwargs):
    if kwargs["email"] == "jsmithl@gmail.com":
        return True
    return False

# 像 `o4-mini` 这样的推理模型很快将支持内置的网络搜索,但目前
# 我们通过一个简单的模拟函数来演示此功能。
def web_mock(*args, **kwargs):
    if "855 Brannan" in kwargs["query"]:
        return "855 Brannan St, San Francisco, 94107, San Francisco County"

    return ""

tool_handlers = {"validate_email": email_mock, "search_web": web_mock}

response, summaries = run_conversation_loop(
    client=client,
    messages=messages,
    tools=tools,
    tool_handlers=tool_handlers,
    response_format=InsuranceFormData,
    model="o4-mini-2025-04-16",
)

print(json.dumps(json.loads(response.output_parsed.model_dump_json()), indent=2))
正在请求模型 'o4-mini-2025-04-16' 的完成(消息数=2)
助手请求了工具调用,正在解析...
工具调用 validate_email 完成,结果:True
正在请求模型 'o4-mini-2025-04-16' 的完成(消息数=5)
助手请求了工具调用,正在解析...
工具调用 validate_email 完成,结果:False
正在请求模型 'o4-mini-2025-04-16' 的完成(消息数=8)
已收到模型解析结果
{
  "applicant": {
    "name": "Smith, James L",
    "home_phone": "510 331 5555",
    "work_phone": "",
    "cell_phone": "510 212 5555",
    "email": "jsmithl@gmail.com"
  },
  "co_applicant": {
    "name": "Roberts, Jesse T",
    "home_phone": "510 331 5555",
    "work_phone": "415 626 5555",
    "cell_phone": "",
    "email": "jrobertsjr@gmail.com"
  },
  "risk_address": {
    "street": "855 Brannan St",
    "city": "San Francisco",
    "state": "CA",
    "zip": "94107",
    "county": "San Francisco"
  },
  "mailing_address_if_different_than_risk_address": {
    "street": "855 Brannan St",
    "city": "San Francisco",
    "state": "CA",
    "zip": "94107",
    "county": "San Francisco"
  },
  "participating_insurer": "Acme Insurance Co",
  "companion_policy_number": "81265919",
  "dwelling_details": {
    "coverage_a_limit": "$900,000",
    "companion_policy_expiration_date": "5/31/27",
    "occupancy_of_dwelling": "Owner",
    "type_of_policy": "Homeowners",
    "unrepaired_structural_damage": false,
    "construction_type": "Frame",
    "roof_type": "Composition",
    "foundation_type": "Raised",
    "has_post_and_pier_or_post_and_beam_foundation": false,
    "cripple_walls": false,
    "number_of_stories": "Greater than 1 story",
    "living_space_over_garage": true,
    "number_of_chimneys": "2",
    "square_footage": "1200",
    "year_of_construction": "2005",
    "anchored_to_foundation": true,
    "water_heater_secured": true
  },
  "effective_date": "5/31/25",
  "expiration_date": "5/31/27"
}

您可以看到电子邮件地址已精炼为单个值,邮政编码和县已填入,并且通过使用风险地址填入了邮寄地址。模型还以结构化格式返回了结果(具有适当的类型,例如是/否问题的布尔值),这可以被下游系统轻松解析。

为了帮助我们理解和调试模型,我们还可以打印模型生成的链式思维推理摘要。这有助于揭示常见的失败模式、模型不清楚的点或不正确的上游详细信息。

在开发此解决方案时,链式思维摘要揭示了一些不正确的模式名称和类型化模式值。

for summary in summaries:
    for response in summary:
        print(response.text + '\n')
**确定保险表格详细信息**

我有一个部分填写的保险表格的 JSON 表示形式,还有一些缺失或模糊的字段需要处理。

对于电子邮件地址,我看到两个选项。我可以验证哪个是正确的,方法是分别用工具检查它们。

风险地址的邮政编码和县字段为空。根据地址“855 Brannan St, San Francisco, CA”,我可以确定正确的邮政编码是 94107,因为该区域对应于 South Beach。最后,由于邮寄地址为空,我假设它与风险地址相同。

**填写保险表格详细信息**

我认为最好将邮寄地址设置为与风险地址相同,或澄清空白地址意味着相同。由于这是填写缺失字段的明确说明,我将填写邮寄地址为风险地址,以避免混淆。

所有共同申请人字段均已提供,并且住宅详细信息已完整。还提供了生效日期和到期日期。我计划通过分别验证每个电子邮件选项来验证它们。让我们从验证第一个电子邮件开始。

3. 模型和功能剧本

选择正确的工具来完成工作是获得最佳结果的关键。一般来说,最好从满足您需求的最简单的解决方案开始,如果需要更多功能,则进行升级。

| 任务 | 从...开始 | 何时升级... | 升级到 | 基本原理 |

任务 从...开始 何时升级... 升级到 基本原理
OCR gpt-4.1 难以一目了然的复杂表格 o3 gpt-4.1 对于大多数 OCR 来说快速且经济高效。o-3 具有推理表格结构的能力。
结果优化 o4-mini 复杂的推理逻辑,需要大量函数调用。 o3 对于非常长的推理链,尤其是同时涉及函数调用和结构化输出时,效果更好。

4. 评估指标

跟踪关键指标以确保系统运行准确且符合预期。

关键指标

  • OCR 准确性:每个字符和每个单词的准确性。
  • 推断字段率:从现有数据或函数调用中正确推断出的未填写条目的比例。
  • 人工干预率:文档包含 UNKNOWN 并必须转交人工审查的频率。

我们建议构建一个带标签的保留数据集,其中包含表格及其预期响应。此数据集应能代表预期的部署环境,请参阅 OpenAI evals 指南以获取有关构建和评估系统的更详细信息。

5. 部署说明

从原型迁移到生产就绪系统需要关注运营细节(LLMOps)。

成本明细

我们将假设对于文档摄取,批量定价是一个可行的选择,因为其延迟容忍度很高(即,隔夜运行即可)。

第一阶段:OCR(光学字符识别)

模型:gpt-4.1

| 类型 | Token | 费率(每 100 万) | 成本 |

类型 Token 费率(每 100 万) 成本
输入 2,000 \$1.00 \$0.002
输出 1,500 \$4.00 \$0.006
1,000 页总计(第一阶段\) \$8.00

第二阶段:推理

模型:o4-mini

| 类型 | Token | 费率(每 100 万) | 成本 |

类型 Token 费率(每 100 万) 成本
输入 2,000 \$0.55 \$0.0011
输出 3,000 \$2.20 \$0.0066
1,000 页总计(第二阶段\) \$7.70

总计(每 1,000 页):\$15.70

将此成本与单阶段 o3 部署进行比较。假设 token 使用量和批量使用量相同,更强大的推理模型的额外成本将达到 70 美元/1000 页。

监控与部署

通过记录关键指标来监控您的系统:

  • llm_model_usedllm_input_tokensllm_output_tokensllm_latency_ms(每个模型)
  • total_query_latency_msestimated_query_cost(每个模型)
  • function_calls_per_documentnum_email_validation_calls
  • human_review_required

通过配置/环境变量固定部署中使用的特定模型版本标识符(例如 o4-mini-2025-04-16),以防止意外行为(由于模型更新)。

6. 有用的指南和资源

请参阅这些相关资源,以深入了解特定组件:

================================================================================

原型到生产

将原型过渡到生产需要仔细的规划和执行。此清单重点介绍了关键步骤,借鉴了我们的旗舰用例,以确保您的部署稳健、高效并实现业务目标。

🗂️ 摘要矩阵

| 清单区域 | 主要关注点/操作 | 重要性 |

清单区域 主要关注点/操作 重要性
定义成功标准 • 定义可衡量的 KPI 和 SLO(准确性、成本、延迟)。 • 确保目标可通过日志进行衡量。 提供明确的目标;证明价值。
记录模型基本原理 • 根据权衡仔细选择初始模型。 • 记录模型选择背后的“原因”。 证明选择的合理性;有助于未来的更新。
强大的评估与测试 • 使用黄金数据集构建自动化测试(“评估套件”)。 • 关注事实性、幻觉、工具错误。 • 测试工具的可靠性和边缘情况。 确保质量;在发布前防止回归。
可观测性与成本 • 实现关键日志记录以进行监控和调试。 • 设置成本护栏(token 限制、使用模式)。 实现调整;将支出控制在预算内。
安全与合规性 • 使用安全机制(审核 API、提示)。 • 执行特定领域的合规规则。 • 对高风险输出强制执行人工干预(HITL)。 确保负责任的操作;满足要求。
模型更新与版本控制 • 定义版本固定策略 • 实施新版本的 A/B 测试 • 创建回滚程序 在允许改进的同时保持稳定性。
  1. 定量定义成功标准:在进行重大开发之前,从“它能工作”转变为可衡量的目标。

    • 设定关键绩效指标(KPI)和 SLO:为业务价值(例如,RAG 准确性 > 95%、OCR 成本 < X 美元/页)和性能(例如,P95 延迟 < 1 秒,错误率)定义具体目标。
    • 确保可衡量性:确认所有 KPI 和 SLO 都可以直接从系统日志中衡量(例如,跟踪 total_tokenscritique_status)。
  2. 记录初始模型选择的基本原理:为将来参考,证明您的初始模型选择的合理性。

    • 审慎选择模型:使用模型介绍矩阵和用例为每个任务选择合适的模型(例如,o4-mini 用于速度/成本,gpt-4.1 用于准确性,o3 用于深度)。
    • 记录“原因”:在代码注释或设计文档中简要记录选择模型的原因(成本、延迟、能力权衡),以便未来的团队能够理解上下文。
  3. 实施强大的评估与测试:在发布更改之前验证质量并防止回归。

    • 构建自动化评估套件:使用“黄金数据集”(50-100 个多样化、专家验证的示例)创建可重复的测试流程。将测试重点放在事实性幻觉率工具错误率和特定任务指标上。
    • 可靠测试:严格测试集成工具的可靠性(成功率、错误处理)以及系统在负载和边缘情况(格式错误的数据、对抗性输入)下的行为。
  4. 建立可观测性与成本控制:监控性能并将支出控制在预算内。

    • 设置成本护栏:通过定义每个阶段的最大 token 限制并考虑操作模式(“快速”、“标准”、“全面”)来平衡成本和性能,以防止意外的成本增加。
    • 实施关键日志记录:通过每个处理阶段的结构化日志捕获关键操作数据,以实现调试和监控。
  5. 实施安全与合规性护栏:确保负责任的操作并满足要求。

    • 使用安全机制:使用审核 API、安全导向系统提示或哨兵模型等工具进行检查,尤其是在处理用户输入或敏感主题时。
    • 执行合规性:内置与您的特定行业和风险相关的检查(例如,法律约束、实验室安全)。
    • 要求人工干预(HITL):对低置信度输出、高风险场景或关键决策强制执行人工审查,确保工作流程清晰标记这些项目。
  6. 管理模型更新和版本控制:为模型随时间的演进做好准备。

    • 版本固定策略:决定是固定到特定模型版本以获得稳定性,还是自动采用新版本以获得改进。
    • A/B 测试框架:建立一个流程,在全面部署之前根据关键指标评估新模型版本。
    • 回滚计划:创建清晰的程序,在更新出现问题时恢复到以前的模型版本。
    • 监控版本性能:跟踪跨模型版本的指标,以识别性能趋势并为未来的选择决策提供信息。

================================================================================

适应决策树

模型选择决策树

向非技术利益相关者传达模型选择

在向业务利益相关者解释您的模型选择时,请重点关注以下关键点:

  1. 与业务成果保持一致:解释您的模型选择如何直接支持特定的业务目标(节省时间、降低成本、提高准确性)。

  2. 转换技术指标:将技术考虑因素转化为业务影响: -“此模型将处理时间从 5 秒减少到 0.7 秒,使我们能够以 7 倍的速度处理客户咨询” -“通过使用 mini 版本,我们可以在相同预算内处理 5 倍的文档”

  3. 突出权衡:为不同模型展示清晰的场景: -“选项 A(GPT-4.1):准确性最高但成本最高 - 非常适合面向客户的法律分析” -“选项 B(GPT-4.1 mini):准确性达到 90%,成本仅为 30% - 非常适合内部文档处理”

  4. 使用具体示例:演示模型之间输出的实际差异,以说明每个选项的价值主张。

================================================================================

附录

关键术语词汇表

| 术语 | 定义 |

术语 定义
上下文窗口 模型在单次请求中可以处理的最大 token 数
幻觉 模型生成看似合理但事实不正确或未经证实的內容
延迟 从向模型发送请求到收到响应之间的时间延迟
LLM 大型语言模型;在海量文本数据上训练的 AI 系统
提示工程 设计有效提示以从 AI 模型中获得所需输出的实践
RAG 检索增强生成;结合信息检索和文本生成
SOTA 最先进;代表给定领域最先进的阶段
Token 模型处理的基本文本单元(英文中约等于 0.75 个单词)

6.1 价格和效用表(2025 年 4 月)

| 模型 | 上下文窗口 | 输入价格(每 100 万 token) | 输出价格(每 100 万 token) | 最适合 |

模型 上下文窗口 输入价格(每 100 万 token) 输出价格(每 100 万 token) 最适合
GPT-4.1 1M \$2.00 \$8.00 长文档分析、代码审查
GPT-4.1 mini 1M \$0.40 \$1.60 生产代理、平衡的成本/性能
GPT-4.1 nano 1M \$0.10 \$0.40 高吞吐量、成本敏感型应用
GPT-4o 128K \$5.00 \$15.00 实时语音/视觉聊天
GPT-4o mini 128K \$0.15 \$0.60 视觉任务、快速分析
o3(低) 200K \$10.00* \$40.00* 大批量分类、目录丰富
o3(中) 200K \$10.00* \$40.00* 知识库问答
o3(高) 200K \$10.00* \$40.00* 多步推理、故障排除
o4-mini(低) 200K \$1.10* \$4.40* 视觉任务、快速分析
o4-mini(中) 200K \$1.10* \$4.40* 平衡的视觉+推理
o4-mini(高) 200K \$1.10* \$4.40* 深度推理,成本可控

* 注意:低/中/高设置会影响 token 使用量,而不是基本定价。较高的设置可能会使用更多的 token 进行深度推理,从而增加每次请求的成本和延迟。

6.2 提示模式快速参考表(Token 与延迟增量)

| 提示模式 | 描述 | Token 影响 | 延迟影响 | 最适合的模型 |

提示模式 描述 Token 影响 延迟影响 最适合的模型
自我批判 要求模型在最终确定前评估自己的答案 +20-30% token +15-25% 延迟 GPT-4.1, o3
思维链 (CoT) 明确指示“逐步思考” +40-80% token +30-50% 延迟 o3, o4-mini(高)
结构化输出 使用 JSON 模式或 Pydantic 模型进行一致格式化 +5-10% token +5-10% 延迟 所有模型
零 token 记忆 将上下文存储在外部数据库中,而不是对话中 -70-90% token -5-10% 延迟 GPT-4.1 系列
骨架填充 提供模板结构供模型完成 -10-20% token -5-15% 延迟 o4-mini, GPT-4.1 nano
自我一致性 生成多个答案并选择最一致的答案 +200-300% token +150-250% 延迟 o3(高)
角色扮演 为模型分配特定角色以获取专业知识 +5-15% token 中性 GPT-4o, o4-mini
锦标赛排名 成对比较选项,而不是单独评分 +50-100% token +30-60% 延迟 o3, o4-mini(高)
工具调用反射 提示模型在检测到不确定性时调用工具 +10-30% token +20-40% 延迟 o3, GPT-4.1

6.3 外部指南和文档链接

OpenAI 官方资源

RAG 和检索

专业用例

提示和模型选择

评估和部署

================================================================================

贡献者

本指南是 OpenAI 和 Tribe AI 合作的成果