LLM调用中的角色详解

大型语言模型(LLMs)通过使对话感觉直观、响应迅速且越来越智能,彻底改变了我们与机器的互动方式。它们现在为从基本聊天界面到复杂AI代理的各种应用提供动力,这些代理可以规划、推理并在任务中采取行动。

这种智能的实现不仅仅是因为模型的参数。它还在于我们如何构建交互。为了释放LLMs的全部潜力,特别是在多轮或工具增强的设置中,模型必须了解谁在说话,他们扮演的角色是什么,以及对话中已经发生了什么。

这就是角色的作用,如systemuserassistant,它们定义了每条消息背后的上下文和意图。在更高级的代理系统中,还有额外的角色如tool_usetool_resultplanner,帮助组织推理和决策。这些角色指导模型的行为,确保上下文得到保留,并使超越简单文本生成的操作成为可能。

无论您是构建友好的聊天机器人还是完全自主的代理,理解和使用基于角色的格式对于构建可靠且有效的LLM应用程序都是关键。

1、理解LLM对话中的角色

在使用基于聊天的应用程序或代理系统的LLMs时,角色有助于结构化对话。每条消息都有一个角色,告诉模型谁在说话以及消息的类型是什么。这有助于模型决定如何回应并跟踪对话。

基本角色是systemuserassistant。这些涵盖了大多数日常用例。在更高级的设置中,比如当构建AI代理时,会添加额外的角色来处理工具、推理步骤或函数调用。现在让我们看看每个角色如何融入整体流程,从简单的对话到代理级别的功能。

1.1 系统角色:设定行为

system角色在对话开始前给模型一般性的指令。它设定了模型在整个聊天中应该如何表现的上下文。

示例:

  • “你是一个乐于助人的助手,可以帮助计划旅行。”
  • “你像莎士比亚一样说话。”

这个消息通常在开始时发送一次,并在整个对话中保持活跃。它适用于定义语气、个性或您希望模型遵循的特定规则。

1.2 用户角色:人类输入

user角色是人们输入消息的地方。这些是模型回应的问题或命令。

示例:

  • “日本有什么有趣的地方可以参观?”
  • “解释神经网络是如何工作的。”

用户的每条新消息都进入这个角色。这是推动互动前进的部分。

1.3 助手角色:模型的回应

assistant角色是模型回复的地方。根据系统提示和最新的用户消息,模型在此角色中生成回应。

示例:

  • “你可能会喜欢去东京体验文化,去京都参观寺庙,去冲绳享受海滩。”
  • “神经网络是一种受人类大脑启发的机器学习模型…”

这是用户看到的模型输出部分。

1.4 代理的额外角色:工具和推理

在更高级的情况下,尤其是在构建基于代理的系统时,有额外的角色帮助模型做超出单纯文本回复的事情。这些包括调用工具、显示结果或逐步进行计划。

示例:

  • OpenAI: 使用function_call角色让模型调用外部工具
  • Claude: 使用tool_usetool_result来显示何时使用工具及其返回的结果
  • LLaMA 3: 使用特殊标签如<|python_tag|>来运行代码

这些额外的角色帮助模型超越对话。它们允许模型获取实时数据,逐步做出决策,并更像代理一样执行任务。

1.5 这些角色为何重要

systemuserassistant角色共同构成了LLM用来理解和回应的完整消息历史。如果这些角色没有正确使用,对话可能会迅速失去上下文,偏离主题或变得不可预测。

正确使用角色有助于您构建一致、清晰且能够处理更复杂任务的LLM应用程序。以下是它们为何重要的原因:

  • 上下文追踪: 角色帮助模型理解谁说了什么以及按什么顺序。这使得对话自然流畅,允许模型参考之前的消息,并在长时间的聊天中避免混淆。
  • 控制行为: system角色设定了模型的整体语气、规则或个性。这保持了助手与您产品声音的一致性,并避免了显得不合适的回应。
  • 清晰的任务执行: 通过分离系统指令、用户提示和助手回复,模型可以更好地理解正在请求的内容以及如何回应。它消除了歧义并提高了回答的质量。

这些角色也是更高级功能的基础,如工具使用、计划步骤或多轮推理。如果您正在构建代理或工具增强的系统,这个结构是使这些工作流成为可能的关键。

2、理解代理中的角色

首先,让我们了解代理实际上是什么。术语“代理”经常被随意使用,其定义可以根据上下文有所不同。一种有用的方法来自Anthropic的帖子 Building Effective Agents,它区分了工作流和代理。

工作流遵循固定的执行路径。而代理则根据当前情况动态决定下一步做什么。这种灵活性使代理能够在开放环境中运作并处理许多可能路径的任务。

代理的核心组件

大多数现代代理围绕三个核心组件构建:记忆、工具和计划。

在基于代理的系统中角色的工作方式

随着LLMs与记忆、工具和计划机制集成,角色成为架构中的关键部分。它们帮助结构化交互,并使代理能够有效地推理、行动和跟踪进度。

组织内部步骤

代理通常使用特定的角色表示每个内部动作。例如,一个计划步骤可能以assistant角色表示,一个工具调用以tool_use角色表示,而输出以tool_result角色表示。这有助于在多步骤推理和工具执行中保持清晰度。

支持逐步推理

Chain-of-Thought、ReAct和Tree-of-Thoughts等技术依赖于为每个推理阶段分配一个角色。这使得过程可解释、可调试和模块化。

处理工具使用

当代理调用工具时,它会创建一个包含工具名称和输入的tool_use消息。工具的响应被捕获在tool_result消息中。这种结构确保了工具使用清晰分离且易于追踪。

计划和反馈循环

许多代理遵循计划、行动、观察和修订的循环。使用角色表示每个阶段有助于干净地管理这些循环,并使扩展或调整代理逻辑更容易。

跟踪记忆和上下文

角色有助于管理短期记忆(如之前的消息和工具调用)和长期记忆(如存储的文档或知识)。用明确的角色标记每条消息确保代理能有效引用过去的步骤。

多代理协作

在具有多个代理的系统中,角色可以定义每个代理的功能——例如“规划者”、“研究者”或“执行者”。这有助于避免歧义并确保组件之间的协调。

基于代理的系统中的角色不仅仅是格式约定。它们定义了推理、工具使用、记忆管理和协作如何发生。使用得当,它们使代理更加可靠、可解释,并能够处理复杂的任务。

记忆

LLMs是无状态的。除非显式提供上下文,否则它们不会保留过去交互的记忆。在聊天应用中,这意味着通常需要在每次请求时管理并重新发送完整的消息历史。

一些平台还支持提示缓存,允许频繁重复的输入(如长系统消息)被重复使用而无需重新处理。这减少了延迟和成本。

工具

工具允许代理与外部系统交互,例如通过调用API、搜索网络或运行本地代码。这些通常是通过模式或函数签名定义的。

良好的文档工具提高了准确性。工具的名称、描述和输入模式应像模型作为开发人员使用它们一样编写。清晰的文档导致更好的使用。

计划

代理需要对任务进行推理并确定下一步。计划可以是使用内置的链式思维推理那么简单,也可以是维护随新信息更新的显式计划那么复杂。

有效的计划还包括在需要时从失败尝试中恢复并修改方法的能力。

3、在LLM和代理系统中使用角色的示例

让我们通过一些实际的例子来演示基于角色的提示工程的实现。我们将从使用Clarifai的OpenAI兼容API的基本对话角色开始,然后扩展到工具调用功能,最后探索Google的Agent Development Kit (ADK)如何简化高级、基于角色的代理的开发。

3.1 基本对话角色:系统和用户

即使是简单的聊天机器人也能从结构化的角色中受益。system角色建立了模型的人格或基本规则,而user角色传递了人类输入。下面是使用Clarifai的OpenAI兼容API定义这些角色在消息历史中的例子,并引导模型的行为。

代码示例:设置人格和用户输入

import os
from openai import OpenAI

# Initialize the OpenAI-compatible client for Clarifai
# Ensure CLARIFAI_PAT is set as an environment variable
client = OpenAI(    
    base_url="https://api.clarifai.com/v2/ext/openai/v1",
    api_key=os.environ["CLARIFAI_PAT"]    
)

# Define messages using system and user roles
messages_basic = [
    {"role": "system", "content": "You are a helpful travel assistant. Always suggest sustainable travel options."},
    {"role": "user", "content": "I want to plan a trip to Bali. What are some eco-friendly activities?"}
]

print("--- Basic Conversational Interaction ---")
try:
    response_basic = client.chat.completions.create(    
        model="https://clarifai.com/anthropic/completion/models/claude-sonnet-4",
        messages=messages_basic,
        max_completion_tokens=200,
        temperature=0.7,    
    )
    print(f"Assistant: {response_basic.choices[0].message.content}")
except Exception as e:
    print(f"An error occurred: {e}")

在这个例子中,系统角色明确指示模型作为“乐于助人的旅行助手”行事,并优先考虑“可持续旅行选项”。用户角色然后提供具体的查询。这种基础的角色使用确保模型的回应从第一轮就开始符合期望的行为。

3.2 高级角色:启用工具使用以实现代理行为

在基本对话角色的基础上,代理系统引入了额外的角色以支持与外部工具的交互。这允许LLMs在需要时获取实时数据、运行计算或调用API。模型决定何时调用工具,而您的应用程序将工具的输出返回给模型,帮助它生成完整且知情的回应。

代码示例:LLM工具调用和结果处理

import os
from openai import OpenAI
import json

# Initialize the OpenAI-compatible client for Clarifai
client = OpenAI(    
    base_url="https://api.clarifai.com/v2/ext/openai/v1",
    api_key=os.environ["CLARIFAI_PAT"]    
)

# Define the external tool (function) that the LLM can call.
# This structure informs the LLM about the tool's purpose and expected inputs.
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Returns the current temperature for a given location. Use this for all weather-related queries.",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "City and country, e.g., 'Bogotá, Colombia'"
                    }
                },
                "required": ["location"],
                "additionalProperties": False # Ensures no extra parameters are passed
            }
        }
    }
]

# Simulate a backend function that would actually fetch weather data
# IMPORTANT: This is a MOCK tool function. In a real application,
# this would be an actual API call to a weather service.
def mock_get_weather_api(location: str) -> str:
    """Simulates an API call to get weather data."""
    print(f"\n--- (Simulating API call for: {location} - using MOCK data) ---")
    weather_data = {
        "New York": "72°F and partly cloudy.",
        "London": "15°C and drizzly.",
        "Tokyo": "18°C with light rain."
    }
    # Return mock data as a JSON string
    return json.dumps({"weather_report": weather_data.get(location, "Weather data not available for this location.")})

# --- Agentic Interaction Flow ---

# 1. Initial user query: LLM decides to call a tool
messages_tool_call = [
    {"role": "system", "content": "You are a helpful assistant with access to a weather tool."},
    {"role": "user", "content": "What's the weather like in New York today?"}
]

print("\n--- Step 1: LLM decides to use a tool ---")
response_1 = client.chat.completions.create(    
    model="https://clarifai.com/anthropic/completion/models/claude-sonnet-4",
    messages=messages_tool_call,
    tools=tools, # Provide the tool definitions
    tool_choice='auto', # Allow the LLM to decide if it needs a tool
    max_completion_tokens=100,
)

tool_calls = response_1.choices[0].message.tool_calls
if tool_calls:
    print(f"LLM proposed tool call (Role: assistant with tool_calls): {tool_calls[0].function.name} with arguments {tool_calls[0].function.arguments}")
    
    # 2. Execute the tool and capture its result
    # This is where your application would call the actual external API/function.
    function_name = tool_calls[0].function.name
    function_args = json.loads(tool_calls[0].function.arguments) # Arguments are JSON string
    
    if function_name == "get_weather":
        # Call the MOCK weather API function
        tool_output = mock_get_weather_api(location=function_args.get("location"))
    else:
        tool_output = json.dumps({"error": "Unknown tool called."})

    # 3. Add the tool_call and its result back to the messages history
    messages_tool_call.append(response_1.choices[0].message) # Add the LLM's tool_call message
    messages_tool_call.append({
        "role": "tool", # This role carries the output of the tool back to the LLM
        "tool_call_id": tool_calls[0].id, # Link back to the specific tool call
        "content": tool_output # The actual result from the tool
    })

    # 4. Second call: LLM uses the tool result to formulate the final answer
    print("\n--- Step 2: LLM formulates answer using tool result ---")
    response_2 = client.chat.completions.create(    
        model="https://clarifai.com/anthropic/completion/models/claude-sonnet-4",
        messages=messages_tool_call, # Pass the updated conversation history
        max_completion_tokens=200,
    )
    print(f"Final Agent Response (Role: assistant): {response_2.choices[0].message.content}")
else:
    print("LLM did not propose a tool call for this query.")
    print(f"Initial LLM Response (Role: assistant): {response_1.choices[0].message.content}")

这个例子展示了完整的代理循环:

  • user 发起交互,询问天气情况。
  • LLM,在 system 角色(定义它为“有访问天气工具的帮助助手”)和提供的 tools 的指导下,认识到需要使用外部函数。它以 assistant 角色回应,但不是文本,而是提供一个 tool_calls 对象,表明它打算调用 get_weather 函数。
  • 您的应用程序拦截来自 assistant 响应的 tool_call。然后执行 mock_get_weather_api 函数(返回预定义的模拟天气数据以供演示),检索 tool_output
  • tool_output 被附加到消息历史记录中,带有 role: "tool"(在某些API实现中为 tool_result),明确表示此消息包含工具执行的结果。此消息也链接回原始的 tool_call_id
  • 最后,更新后的消息历史记录(包括初始的 systemuser 消息、assistanttool_calltooltool_output)被送回给LLM。有了工具的结果现在在对话上下文中,LLM可以生成直接、知情的回答给用户,再次以 assistant 角色呈现。这种多轮交互,由这些特定且不同的角色驱动,是代理行为的本质。

4、通过Google ADK简化代理构建

虽然直接的API调用提供了细粒度的控制,但代理开发套件和框架提供了更高层次的抽象,以简化构建和管理复杂代理。它们通常将多步推理、工具编排和内存管理封装在一个更直观的框架中。例如,Google的ADK允许您通过清晰的指令和集成的工具定义代理,自动处理底层基于角色的消息传递。

代码示例:使用Google ADK和Clarifai LLM构建代理

import os
import asyncio
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types # For defining message types

# Initialize Clarifai-hosted model using LiteLlm for ADK compatibility
# LiteLlm enables ADK to work with various OpenAI-compatible endpoints.
clarifai_model = LiteLlm(
    model="openai/deepseek-ai/deepseek-chat/models/DeepSeek-R1-Distill-Qwen-7B", # Example model
    base_url="https://api.clarifai.com/v2/ext/openai/v1",
    api_key=os.environ["CLARIFAI_PAT"] # Ensure this environment variable is set
)

# Define a mock tool function that the agent can use.
# ADK automatically converts this into a callable tool for the LLM.
def get_weather(city: str) -> dict:
    """
    Retrieves current weather data for a specified city.
    Useful for answering questions about local weather conditions.
    """
    print(f"[Tool Call Log] get_weather called for: {city}")
    # IMPORTANT: This is a MOCK implementation. In a production system,
    # this function would make a real API call to a weather service.
    city_key = city.lower().replace(" ", "")

    mock_data = {
        "newyork": {"status": "success", "report": "Sunny, 25°C in New York."},
        "london": {"status": "success", "report": "Cloudy, 15°C in London."},
        "tokyo": {"status": "success", "report": "Light rain, 18°C in Tokyo."},
    }

    return mock_data.get(city_key, {
        "status": "error",
        "error_message": f"Sorry, I don't have weather information for '{city}'."
    })

# Define the agent itself, integrating the model and tool.
# The 'instruction' here acts as the agent's core system prompt.
weather_agent = Agent(
    name="WeatherAssistant",
    description="Provides weather updates using Clarifai-hosted LLM and custom tools.",
    instruction="You are a helpful weather assistant. Use the `get_weather` tool to fetch city weather. "
                "Return detailed weather reports when available, or clear error messages if not.",
    model=clarifai_model,
    tools=[get_weather], # Register the tool with the agent
)

async def run_adk_agent():
    app_name = "weather_agent_app"
    session_service = InMemorySessionService()

    # Create a session for the user's interaction
    await session_service.create_session(
        app_name=app_name,
        user_id="demo_user",
        session_id="weather_session"
    )

    runner = Runner(
        agent=weather_agent,
        app_name=app_name,
        session_service=session_service
    )

    print("\n--- Google ADK Agent Interaction ---")
    print("Ask about the weather (e.g., 'What's the weather in Tokyo?') or type 'exit' to quit.")

    while True:
        user_input = input("You: ")
        if user_input.strip().lower() == "exit":
            break

        # Send the user's message to the agent
        # ADK handles the underlying role formatting and tool orchestration
        response_events = runner.run(
            user_id="demo_user",
            session_id="weather_session",
            new_message=types.Content(role="user", parts=[types.Part(text=user_input)])
        )

        # Process the events to find the final response
        final_response = "No response from agent."
        for event in response_events:
            if event.is_final_response() and event.content and event.content.parts:
                final_response = event.content.parts[0].text
                break
            # You could also log other event types (e.g., tool calls, thoughts) for debugging

        print(f"Agent: {final_response}")

if __name__ == "__main__":
    asyncio.run(run_adk_agent())

上述Google ADK示例展示了框架如何简化代理开发:

  • LiteLlm: 这个类允许ADK无缝集成Clarifai的OpenAI兼容端点,使您的代理在不同LLM提供商之间灵活。
  • Agent定义: Agent类本身是您定义代理核心身份的地方。instruction参数作为主要系统级提示,指导代理的行为和目的。tools参数注册您的Python函数作为LLM的可调用工具。
  • RunnerSessionService: ADK的Runner协调交互,管理对话流程,在需要时调用工具,并处理与LLM的来回消息(包括基于角色的格式)。InMemorySessionService管理对话历史记录(memory),确保代理在回合间有上下文。
  • 简化的交互: 从用户的角度(以及您的应用程序逻辑来看),您只需将user消息发送给runner,ADK会在后台处理所有复杂的角色管理、工具调用和结果处理,最终返回一个最终响应。这突出了框架如何抽象掉底层提示工程细节,让您专注于代理的整体逻辑和能力。

5、结束语

角色是有效使用LLM的关键。它们帮助模型保持稳定,保持上下文,并在涉及工具或多步骤推理时可靠地响应。

角色不仅帮助模型保持稳定,保持上下文,而且在涉及工具或多步骤推理时可靠地响应。我们从核心角色开始:system用于指令,user用于输入,assistant用于响应。使用Clarifai的OpenAI兼容API,我们展示了如何明确定义这些角色可以保持交互的稳定性和目的性。

我们还介绍了代理框架和工具使用如何协同工作,从模型决定何时调用工具,到你的代码执行它,通过tool角色返回结果,然后模型使用该输出进行响应。像Google ADK这样的工具包会自动处理大部分内容,在后台管理角色和编排。


原文链接:Agentic Prompt Engineering: A Deep Dive into LLM Roles and Role-Based Formatting

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