微信扫码
与创始人交个朋友
我要投稿
提示词工程无疑是开发 LLM 原生应用程序中最关键的技能之一,因为精心设计的提示词可以显著提升应用程序的性能和可靠性。
在过去的两年里,我一直在帮助组织为实际应用场景构建和部署数十个 LLM 应用程序。这段经历让我对有效的提示词技术有了宝贵的见解。
本文基于LLM 三角原则,提出了八大实用提示词工程技巧,以提升你的 LLM 提示词能力。
“LLM 原生应用程序中,10% 是复杂模型,90% 是基于数据驱动的实验性工程工作。”
在本文中,我们将使用“着陆页生成器”示例来展示如何应用每个技巧来提升真实世界中的 LLM 应用程序。
你也可以查看着陆页生成器示例的完整脚本以全面了解。注意: 这是一个简化的、非生产环境的示例,旨在说明这些技巧。代码和提示词故意简单化,以突出讨论的概念。
首先为每个代理或提示词定义目标。坚持每个代理只处理一种认知过程类型,例如:构思着陆页、选择组件或为特定部分生成内容。
保持清晰的边界有助于在 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
通过为每个代理定义清晰的边界,我们可以确保工作流程中的每一步都针对特定的思维任务进行定制。这将提高输出的质量,并使调试和优化变得更加容易。
定义明确的输入和输出结构,以反映目标并创建明确的数据模型。这一做法涉及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 模型定义了我们输入和输出数据的结构,并为代理设定了明确的边界和期望。
设置验证机制以确保 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
在此示例中,通过定义两种类型的验证器来确保应用程序的质量:
**Field**
定义简单的验证规则,如语气/风格属性至少有2个,叙述内容至少有50个字符。**field_validator**
来确保生成的叙述符合我们的内容审核政策(使用 AI)。通过将复杂任务分解为逻辑顺序的较小步骤来构建 LLM 工作流程,以模仿人类的认知过程。要做到这一点,请遵循LLM 三角原则的 SOP(标准操作程序) 指导原则。
“没有 SOP,即使是最强大的 LLM 也无法始终如一地交付高质量的结果。”
在我们的示例中,我们希望模型返回 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 中建模的那样。
对于复杂任务,将过程分解为多个步骤,每个步骤由单独的 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 = await compose_html(concept, component_contents)
return LandingPageOutput(concept, selected_components, component_contents, html)
多代理过程代码的示意图。这种多代理方法与人类解决复杂问题的方式一致——通过将其分解为较小的部分。
YAML 是一种流行的人类友好的数据序列化格式。它设计为易于人类阅读,同时仍然易于机器解析——这使得它非常适合 LLM 使用。
我发现 YAML 对于 LLM 交互特别有效,并且在不同模型中产生了更好的结果。它将 token 处理集中在有价值的内容上,而不是语法上。
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="Luxurious mustache cream for grooming and styling",
),
"concept": LandingPageConcept(
campaign_title="Celebrate Dad's Dash of Distinction",
tone_and_style=["Warm", "Slightly humorous", "Nostalgic"]
),
"output": ComponentContent(
motivation="The hero section captures attention and communicates the core value proposition.",
content={
"headline": "Honor Dad's Distinction",
"subheadline": "The Art of Mustache Care",
"cta_button": "Shop Now"
}
)
},
}
sys = "Craft landing page component content. Respond in YAML with motivation and content structure as shown."
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 格式。这种方法比在提示词中明确说明输出结构更为有效。
仔细考虑如何对数据进行建模和展示给 LLM。这个技巧是LLM 三角原则的上下文数据顶点的核心。
“即使是最强大的模型也需要相关且结构良好的上下文数据才能发挥光彩。”
不要抛弃你对模型的所有数据。相反,向模型提供与您定义的目标相关的信息。
async def select_components(concept: LandingPageConcept) -> List[LandingPageComponent]:
sys_template = jinja_env.from_string("""
Your task is to select the most appropriate components for a landing page based on the provided concept.
Choose from the following components:
{% for component in components %}
- {{ component.value }}
{% endfor %}
You MUST respond ONLY in a valid YAML list of selected components.
""")
sys = sys_template.render(components=LandingPageComponent)
prompt = jinja_env.from_string("""
Campaign title: "{{ concept.campaign_title }}"
Campaign narrative: "{{ concept.campaign_narrative }}"
Tone and style attributes: {{ 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 原生应用程序的引擎提供动力。上下文数据的战略设计解锁了它们的真正潜力。”
少样本学习是提示词工程中必不可少的技术。为 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
放置,我们与指令模型的训练方法保持一致。它允许模型在处理用户输入之前查看多个“示例交互”——帮助它理解预期的输入输出模式。
随着应用程序的发展,你可以添加更多的少样本学习示例来涵盖更多的用例。对于更高级的应用程序,可以考虑实现动态少样本学习选择,其中根据当前输入选择最相关的示例。
此方法使用与当前任务直接相关的示例在提示词本身中。例如,此提示词模板用于生成额外的独特卖点:
Generate {{ num_points }} more unique selling points for our {{ brand }} {{ product_desc }}, following this style:
{% for point in existing_points %}
- {{ point }}
{% endfor %}
通过将示例直接包含在提示词中而不是作为单独的消息,这为特定内容生成任务提供了有针对性的指导。
虽然像“思维树”或“思维图”这样的复杂提示词工程技术很吸引人,尤其是在研究方面,但我发现它们在生产中实际上相当不切实际,往往过于复杂。对于真实的应用程序,重点是设计适当的 LLM 架构(即工作流工程)。
这也适用于在 LLM 应用程序中使用代理。理解标准代理和自主代理之间的区别至关重要:
代理: “通过执行 XYZ 将我从 A → B。”
自主代理:“
通过某种方式将我从 A → B,我不在乎怎么做。”
虽然自主代理提供了灵活性和更快的开发速度,但它们也可能引入不可预测性和调试挑战。谨慎使用自主代理——仅当好处明显超过可能的控制损失和复杂性增加时使用。
持续的实验对于改进 LLM 原生应用程序至关重要。不要被实验的想法吓倒——它们可以小到调整提示词。正如《构建 LLM 应用程序:清晰的分步指南》中所概述的那样,建立基准并根据基准跟踪改进非常重要。
就像“AI”中的其他一切一样,LLM 原生应用程序需要研究和实验心态。
另一个很棒的技巧是将你的提示词尝试在比你打算在生产中使用的更弱的模型上(如开源的 8B 模型)——在较小的模型上表现“还可以”的提示词在较大模型上表现会更好。
这八大技巧为 LLM 原生应用程序中的有效提示词工程提供了坚实的基础。通过在提示词中应用这些技巧,你将能够创建更可靠、高效和可扩展的 LLM 原生应用程序。
记住,目标不是创建最复杂的系统,而是构建在现实世界中可行的东西。继续实验、学习和构建——可能性是无穷无尽的。
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-09-18
2024-07-18
2024-07-02
2024-07-10
2024-07-09
2024-07-10
2024-07-15
2024-07-14
2024-08-14
2024-07-26
2024-11-13
2024-10-31
2024-10-29
2024-10-16
2024-09-19
2024-08-28
2024-08-24
2024-08-11