8个LLM应用开发必备提示技巧

在过去两年中,我帮助组织构建并部署了数十个用于实际场景的LLM应用程序。这种经验让我获得了关于有效提示技术的宝贵见解。

8个LLM应用开发必备提示技巧

提示工程无疑是开发LLM原生应用中最关键的技能,因为精心设计的提示可以显著影响你的应用程序的性能和可靠性。在过去两年中,我帮助组织构建并部署了数十个用于实际场景的LLM应用程序。这种经验让我获得了关于有效提示技术的宝贵见解。

本文基于LLM三角原则,提供了八个实用技巧,以提升你的LLM提示工程水平。

“LLM原生应用程序是10%复杂的模型和90%的数据驱动工程工作。”

在本文中,我们将使用“落地页生成器”示例来展示如何将每个技巧应用于增强一个现实世界的LLM应用程序。

你也可以查看落地页生成器示例的完整脚本,以获得完整的视角。
注意: 这是一个简化的非生产示例,仅用于说明这些技巧。代码和提示故意保持简单,以便突出讨论的概念。

1、定义清晰的认知过程边界

首先定义每个代理或提示的目标。坚持每种认知过程类型对应一个代理,例如:构思落地页、选择组件或为特定部分生成内容。

明确的边界有助于保持LLM交互中的焦点和清晰度,符合LLM三角原则中的工程技巧顶点。

“我们流程中的每一步都是一个独立的过程,必须发生才能完成任务。”

例如,避免在同一提示中结合不同的认知过程,这可能会产生次优结果。相反,将这些过程分解为单独的、专注的代理:

def generate_landing_page_concept(input_data: LandingPageInput) -> LandingPageConcept:  
    """  
    基于输入数据生成落地页概念。  
    此函数专注于构思落地页的创造性过程。  
    """  
    pass  
  
def select_landing_page_components(concept: LandingPageConcept) -> List[LandingPageComponent]:  
    """  
    根据概念选择合适的组件。  
    此函数仅负责选择组件,  
    不生成其内容或布局。  
    """  
    pass  
  
def generate_component_content(component: LandingPageComponent, concept: LandingPageConcept) -> ComponentContent:  
    """  
    为特定的落地页组件生成内容。  
    此函数专注于根据组件类型和整体概念创建适当的内容。  
    """  
    pass

通过为每个代理定义明确的边界,我们可以确保工作流中的每一步都针对特定的心理任务。这将提高输出质量,并使其更容易调试和改进

2、明确输入/输出

定义明确的输入和输出结构,反映目标,并创建显式的数据模型。此做法涉及LLM三角原则工程技巧上下文数据顶点。

class LandingPageInput(BaseModel):  
    brand: str  
    product_desc: str  
    campaign_desc: str  
    cta_message: str  
    target_audience: str  
    unique_selling_points: List[str]  
  
class LandingPageConcept(BaseModel):  
    campaign_desc_reflection: str  
    campaign_motivation: str  
    campaign_narrative: str  
    campaign_title_types: List[str]  
    campaign_title: str  
    tone_and_style: List[str]

这些Pydantic模型定义了我们的输入和输出数据的结构,并定义了明确的边界和期望。

3、实施护栏

放置验证以确保LLM输出的质量和规范化。Pydantic非常适合实现这些护栏,我们可以利用其内置功能。

class LandingPageConcept(BaseModel):  
    campaign_narrative: str = Field(..., min_length=50)  # 内置验证  
    tone_and_style: List[str] = Field(..., min_items=2)  # 内置验证  
  
    # ...其余字段... #  
  
    @field_validator("campaign_narrative")  
    @classmethod  
    def validate_campaign_narrative(cls, v):  
        """验证叙述是否符合内容政策,使用另一个AI模型。"""  
        response = client.moderations.create(input=v)  
  
        if response.results[0].flagged:  
            raise ValueError("提供的文本违反了内容政策。")  
  
        return v

在这个例子中,通过定义两种类型的验证器来确保应用程序的质量:

  • 使用Pydantic的Field定义简单的验证,如至少包含2个风格属性,或者叙述至少50个字符。
  • 使用自定义的field_validator确保生成的叙述符合我们的内容审核政策(使用AI)。

4、与人类认知过程对齐

通过将复杂任务分解成遵循逻辑顺序的小步骤来结构化你的LLM工作流,从而模仿人类的认知过程。为此,请遵循LLM三角原则的*SOP(标准操作程序)*指导原则。

“没有SOP,即使是最强大的LLM也无法始终提供高质量的结果。”

4.1 捕获隐藏的隐性认知跳跃

在我们的示例中,我们期望模型返回LandingPageConcept作为结果。通过要求模型输出某些字段,我们引导LLM类似于人类营销人员或设计师创建落地页概念的方式。

class LandingPageConcept(BaseModel):  
    campaign_desc_reflection: str  # 鼓励分析活动描述  
    campaign_motivation: str       # 引导思考活动背后的“为什么”  
    campaign_narrative: str        # 引导创建落地页的故事  
    campaign_title_types: List[str]# 促进不同标题方法的头脑风暴  
    campaign_title: str            # 最终决定标题  
    tone_and_style: List[str]      # 定义落地页的整体感觉

LandingPageConcept结构鼓励LLM遵循类似人类推理的过程,反映了专家本能做出的微妙心理跳跃(隐性认知“跳跃”),正如我们在SOP中建模的一样。

4.2 将复杂过程分解为多个步骤/代理

对于复杂任务,将其分解为多个步骤,每个步骤由不同的LLM调用或“代理”处理:

async def generate_landing_page(input_data: LandingPageInput) -> LandingPageOutput:  
    # 第一步:构思活动  
    concept = await generate_concept(input_data)  
      
    # 第二步:选择适当的组件  
    selected_components = await select_components(concept)  
      
    # 第三步:为每个选定的组件生成内容  
    component_contents = {  
        component: await generate_component_content(input_data, concept, component)  
        for component in selected_components  
    }  
      
    # 第四步:组合最终HTML  
    html = await compose_html(concept, component_contents)  
      
    return LandingPageOutput(concept, selected_components, component_contents, html)
多代理过程代码

这种方法与人类解决复杂问题的方式一致——通过将其分解为更小的部分。

5、利用结构化数据(YAML)

YAML是一种流行的、易于阅读的数据序列化格式。它旨在易于人类阅读,同时便于机器解析——这使得它成为LLM使用的经典选择。

我发现YAML特别适用于LLM交互,并且在不同的模型上都能产生更好的效果。它将令牌处理集中在有价值的内容上,而不是语法。

YAML还更易于跨不同的LLM提供商维护,并允许你保持结构化的输出格式。

async def generate_component_content(input_data: LandingPageInput, concept: LandingPageConcept,component: LandingPageComponent) -> ComponentContent:  
    few_shots = {  
        LandingPageComponent.HERO: {  
            "input": LandingPageInput(  
                brand="Mustacher",  
                product_desc="奢华胡须膏用于梳理和造型",  
                # ... 其余输入数据 ...  
            ),  
            "concept": LandingPageConcept(  
                campaign_title="庆祝爸爸的独特魅力",  
                tone_and_style=["温暖", "略带幽默", "怀旧"]  
                # ... 其余概念 ...  
            ),  
            "output": ComponentContent(  
                motivation="英雄部分吸引注意力并传达核心价值主张。",  
                content={  
                    "headline": "致敬爸爸的魅力",  
                    "subheadline": "胡须护理的艺术",  
                    "cta_button": "立即购买"  
                }  
            )  
        },  
        # 根据需要添加更多组件示例  
    }  
  
    sys = "编写落地页组件内容。以YAML格式响应,包括动机和内容结构。"  
      
    messages = [{"role": "system", "content": sys}]  
    messages.extend([  
        message for example in few_shots.values() for message in [  
            {"role": "user", "content": to_yaml({"input": example["input"], "concept": example["concept"], "component": component.value})},  
            {"role": "assistant", "content": to_yaml(example["output"])}  
        ]  
    ])  
    messages.append({"role": "user", "content": to_yaml({"input": input_data, "concept": concept, "component": component.value})})  
  
    response = await client.chat.completions.create(model="gpt-4o", messages=messages)  
    raw_content = yaml.safe_load(sanitize_code_block(response.choices[0].message.content))  
    return ComponentContent(**raw_content)

请注意,我们是如何使用少量示例来“展示,而不是告诉”预期的YAML格式。这种方法比在提示中明确指示输出结构更有效。

6、精心设计上下文数据

仔细考虑如何向LLM建模和呈现数据。此技巧是LLM三角原则上下文数据顶点的核心。

“即使是最强大的模型也需要相关且结构良好的上下文数据才能大放异彩。”

不要丢弃所有模型上的数据。相反,告知模型与你定义的目标相关的数据片段。

async def select_components(concept: LandingPageConcept) -> List[LandingPageComponent]:  
    sys_template = jinja_env.from_string("""  
    你的任务是根据提供的概念选择最合适的落地页组件。  
    从以下组件中选择:  
    {% for component in components %}  
    - {{ component.value }}  
    {% endfor %}  
    你必须仅以有效的YAML列表形式回应所选组件。  
    """)  
  
    sys = sys_template.render(components=LandingPageComponent)  
  
    prompt = jinja_env.from_string("""  
    活动标题:“{{ concept.campaign_title }}"  
    活动叙述:“{{ concept.campaign_narrative }}"  
    风格属性:{{ concept.tone_and_style | join(', ') }}  
    """)  
  
    messages = [{"role": "system", "content": sys}] + few_shots + [  
        {"role": "user", "content": prompt.render(concept=concept)}]  
  
    response = await client.chat.completions.create(model="gpt-4", messages=messages)  
  
    selected_components = yaml.safe_load(response.choices[0].message.content)  
    return [LandingPageComponent(component) for component in selected_components]

在这个例子中,我们使用Jinja模板动态地组成我们的提示。这种方法优雅地为每个LLM交互创造了聚焦和相关的上下文。

“数据是LLM原生应用程序的动力引擎。战略性地设计上下文数据可以解锁它们的真正潜力。”

6.1 发挥少量学习的优势

少量学习是提示工程中必不可少的技术。为LLM提供相关的示例可以显著提高其任务理解能力。

请注意,在我们下面讨论的两种方法中,我们都重用了Pydantic模型作为少量示例——这个技巧确保了示例与实际任务之间的一致性!遗憾的是,我是通过实践才学到这一点的。

示例少量学习

请参阅第5节中的few_shots字典。在这种方法中:

示例作为单独的用户和助手消息添加到messages列表中,然后是实际的用户输入。

messages.extend([  
    message for example in few_shots for message in [  
        {"role": "user", "content": to_yaml(example["input"])},  
        {"role": "assistant", "content": to_yaml(example["output"])}  
    ]  
])  
# 然后我们可以添加用户提示  
messages.append({"role": "user", "content": to_yaml(input_data)})

通过将示例作为messages放置,我们与指令模型的训练方法对齐。这使模型在处理用户输入之前可以看到多个“示例交互”,帮助它理解预期的输入-输出模式。

随着应用程序的增长,你可以添加更多的少量示例以覆盖更多用例。对于更高级的应用,考虑实现动态少量选择,其中基于当前输入选择最相关的示例。

任务特定的少量学习

此方法使用与当前任务直接相关的示例在提示中。例如,此提示模板用于生成额外的卖点:

生成{{ num_points }}个更多的卖点,按照以下风格:  
{% for point in existing_points %}  
- {{ point }}  
{% endfor %}

这种方法通过在提示中直接包含示例,而不是作为单独的消息,为特定内容生成任务提供了有针对性的指导。

7、KISS——简单为王

虽然像“思维树”或“思维图”这样的花哨提示工程技术在研究中非常有趣,但我在实践中发现它们相当不切实际,通常是过度设计。对于实际应用,重点在于设计适当的LLM架构(即工作流工程)。

这同样适用于你在LLM应用程序中使用的代理。了解标准代理和自主代理之间的区别至关重要:

代理:“通过做XYZ将我从A带到B。”

自主代理:“通过做某事将我从A带到B,我不关心怎么做。”

虽然自主代理提供了灵活性和更快的开发速度,但它们也可能引入不可预测性和调试挑战。谨慎使用自主代理——只有当好处明显超过潜在的控制损失和增加的复杂性时才使用。

8、迭代,迭代,再迭代!

持续实验是改善你的LLM原生应用程序的关键。不要被实验的想法吓倒——它们可以像调整提示一样简单。正如在《构建LLM应用程序:清晰的逐步指南》中所述,建立一个基准并跟踪改进是非常重要的。

在“人工智能”领域,LLM原生应用程序需要一种研究和实验的心态。

另一个很棒的技巧是尝试你的提示在一个比你计划在生产中使用的模型更弱的模型上运行(例如开源8B模型)——在一个较小的模型上表现“良好”的提示在较大的模型上会表现得更好。

9、结束语

这八个技巧为你提供了在LLM原生应用程序中进行有效提示工程的坚实基础。通过在提示中应用这些技巧,你将能够创建更可靠、高效和可扩展的LLM原生应用程序。

记住,目标不是创建最复杂的系统,而是构建能在现实世界中工作的系统。继续实验、学习和构建——可能性是无穷无尽的。


原文链接:8 Practical Prompt Engineering Tips for Better LLM Apps

汇智网翻译整理,转载请标明出处