本教程将介绍如何基于用户反馈优化分类器。分类器非常适合优化,因为收集期望的输出通常非常简单,这使得基于用户反馈创建少样本示例变得容易。这正是我们在此示例中要做的事情。

目标

在此示例中,我们将构建一个根据标题对 GitHub issue 进行分类的机器人。它将接收一个标题并将其分类为多个不同类别之一。然后,我们将开始收集用户反馈,并使用这些反馈来塑造分类器的性能。

开始使用

首先,我们将进行设置,以便将所有跟踪发送到特定项目。我们可以通过设置环境变量来实现这一点:
import os
os.environ["LANGSMITH_PROJECT"] = "classifier"
然后,我们可以创建初始应用程序。这将是一个非常简单的函数,它接收 GitHub issue 标题并尝试为其打标签。
import openai
from langsmith import traceable, Client
import uuid

client = openai.Client()

available_topics = [
    "bug",
    "improvement",
    "new_feature",
    "documentation",
    "integration",
]

prompt_template = """Classify the type of the issue as one of {topics}.
Issue: {text}"""

@traceable(
    run_type="chain",
    name="Classifier",
)
def topic_classifier(
    topic: str):
    return client.chat.completions.create(
        model="gpt-4o-mini",
        temperature=0,
        messages=[
            {
                "role": "user",
                "content": prompt_template.format(
                    topics=','.join(available_topics),
                    text=topic,
                )
            }
        ],
    ).choices[0].message.content
然后,我们可以开始与它交互。在交互时,我们将提前生成 LangSmith 运行 ID 并将其传递给此函数。我们这样做是为了稍后可以附加反馈。 以下是我们如何调用应用程序:
run_id = uuid.uuid4()
topic_classifier(
    "fix bug in LCEL",
    langsmith_extra={"run_id": run_id})
以下是我们如何在此之后附加反馈。我们可以以两种形式收集反馈。 首先,我们可以收集”正面”反馈——这是针对模型做对的示例。
ls_client = Client()
run_id = uuid.uuid4()
topic_classifier(
    "fix bug in LCEL",
    langsmith_extra={"run_id": run_id})
ls_client.create_feedback(
    run_id,
    key="user-score",
    score=1.0,
)
接下来,我们可以专注于收集与生成的”修正”相对应的反馈。在此示例中,模型会将其分类为 bug,而我真正希望将其分类为文档。
ls_client = Client()
run_id = uuid.uuid4()
topic_classifier(
    "fix bug in documentation",
    langsmith_extra={"run_id": run_id})
ls_client.create_feedback(
    run_id,
    key="correction",
    correction="documentation")

设置自动化

现在,我们可以设置自动化,将带有某种形式反馈的示例移动到数据集中。我们将设置两个自动化,一个用于正面反馈,另一个用于负面反馈。 第一个将获取所有带有正面反馈的运行,并自动将它们添加到数据集中。这背后的逻辑是,任何带有正面反馈的运行都可以在未来的迭代中用作好示例。让我们创建一个名为 classifier-github-issues 的数据集来添加此数据。 Optimization Negative 第二个将获取所有带有修正的运行,并使用 webhook 将它们添加到数据集中。创建此 webhook 时,我们将选择”使用修正”选项。此选项将使从运行创建数据集时,使用修正而不是使用运行的输出作为数据点的真实输出。 Optimization Positive

更新应用程序

现在,我们可以更新代码以下载我们正在发送运行的数据集。下载后,我们可以创建一个包含示例的字符串。然后,我们可以将此字符串作为提示的一部分!
### NEW CODE ###
# Initialize the LangSmith Client so we can use to get the dataset
ls_client = Client()

# Create a function that will take in a list of examples and format them into a string
def create_example_string(examples):
    final_strings = []
    for e in examples:
        final_strings.append(f"Input: {e.inputs['topic']}\n> {e.outputs['output']}")
    return "\n\n".join(final_strings)
### NEW CODE ###

client = openai.Client()

available_topics = [
    "bug",
    "improvement",
    "new_feature",
    "documentation",
    "integration",
]

prompt_template = """Classify the type of the issue as one of {topics}.

Here are some examples:
{examples}

Begin!
Issue: {text}
>"""

@traceable(
    run_type="chain",
    name="Classifier",
)
def topic_classifier(
    topic: str):
    # We can now pull down the examples from the dataset
    # We do this inside the function so it always get the most up-to-date examples,
    # But this can be done outside and cached for speed if desired
    examples = list(ls_client.list_examples(dataset_name="classifier-github-issues"))  # <- New Code
    example_string = create_example_string(examples)
    return client.chat.completions.create(
        model="gpt-4o-mini",
        temperature=0,
        messages=[
            {
                "role": "user",
                "content": prompt_template.format(
                    topics=','.join(available_topics),
                    text=topic,
                    examples=example_string,
                )
            }
        ],
    ).choices[0].message.content
如果现在使用与之前类似的输入运行应用程序,我们可以看到它正确地学习到任何与文档相关的内容(即使是 bug)都应该被分类为 documentation
ls_client = Client()
run_id = uuid.uuid4()
topic_classifier(
    "address bug in documentation",
    langsmith_extra={"run_id": run_id})

对示例进行语义搜索

我们可以做的另一件事是仅使用语义上最相似的示例。当您开始构建大量示例时,这很有用。 为了做到这一点,我们可以首先定义一个示例来找到 k 个最相似的示例:
import numpy as np

def find_similar(examples, topic, k=5):
    inputs = [e.inputs['topic'] for e in examples] + [topic]
    vectors = client.embeddings.create(input=inputs, model="text-embedding-3-small")
    vectors = [e.embedding for e in vectors.data]
    vectors = np.array(vectors)
    args = np.argsort(-vectors.dot(vectors[-1])[:-1])[:5]
    examples = [examples[i] for i in args]
    return examples
然后,我们可以在应用程序中使用它
ls_client = Client()

def create_example_string(examples):
    final_strings = []
    for e in examples:
        final_strings.append(f"Input: {e.inputs['topic']}\n> {e.outputs['output']}")
    return "\n\n".join(final_strings)

client = openai.Client()

available_topics = [
    "bug",
    "improvement",
    "new_feature",
    "documentation",
    "integration",
]

prompt_template = """Classify the type of the issue as one of {topics}.

Here are some examples:
{examples}

Begin!
Issue: {text}
>"""

@traceable(
    run_type="chain",
    name="Classifier",
)
def topic_classifier(
    topic: str):
    examples = list(ls_client.list_examples(dataset_name="classifier-github-issues"))
    examples = find_similar(examples, topic)
    example_string = create_example_string(examples)
    return client.chat.completions.create(
        model="gpt-4o-mini",
        temperature=0,
        messages=[
            {
                "role": "user",
                "content": prompt_template.format(
                    topics=','.join(available_topics),
                    text=topic,
                    examples=example_string,
                )
            }
        ],
    ).choices[0].message.content

Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.