你看到的 temperature: float | None = None 是 LangChain 中 LLM(大语言模型)初始化时的一个常见参数定义,其注释说明了它的作用------控制生成文本的随机性。
📖 Temperature 是什么?
temperature 是一个调节模型输出概率分布"尖锐程度"的超参数。它直接影响模型在每一步选择下一个 token 时的随机性:
- 低 temperature(如 0.1 ~ 0.3) :概率分布变得更尖锐,模型倾向于选择概率最高的 token,输出更加确定、保守、连贯,适合需要精确答案的任务(如事实问答、代码生成、翻译)。
- 高 temperature(如 0.7 ~ 1.0) :概率分布更平滑,模型有更大机会选择概率较低的 token,输出更加多样、创意、不可预测,适合故事创作、头脑风暴等需要新颖性的场景。
- 极低 temperature(趋近 0):几乎总是选择最高概率的 token,输出接近贪婪解码(greedy decoding),但可能过于呆板。
- 极高 temperature(> 1.0):可能导致输出变得混乱、无意义,因为概率分布过于平坦,随机性过强。
⚙️ 在 LangChain 中如何使用?
在初始化 LLM 实例时,可以通过 temperature 参数设置:
python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-4",
temperature=0.3, # 较低的随机性,适合精准任务
)
如果设置为 None(默认),模型会使用其自身的默认值(通常为 1.0 或 0.7,取决于具体模型)。
🧠 与其他参数的协同
top_p(核采样) :与 temperature 共同控制随机性,但机制不同。top_p限制采样范围(只从累积概率达到 p 的 token 中采样),而 temperature 调整概率分布的形态。实践中建议只调整其中一个,避免冲突。frequency_penalty/presence_penalty:控制重复性,与 temperature 独立作用。
💡 面试常见追问
| 问题 | 回答要点 |
|---|---|
| temperature 和 top_p 有什么区别? | temperature 改变概率分布的"陡峭程度",top_p 截断低概率 token 的采样空间。两者可组合使用,但通常只调一个。 |
| 什么时候用低 temperature? | 需要高精度、低随机性时,如数学计算、代码生成、信息提取。 |
| 什么时候用高 temperature? | 需要多样性、创意时,如诗歌生成、开放式对话、故事续写。 |
| temperature 设置为 0 有什么风险? | 输出完全确定,可能陷入重复或模式化,且无法利用模型的随机性来规避局部最优。 |
| 如何根据任务选择 temperature? | 原则:任务越客观、答案越唯一,temperature 越低;任务越主观、答案越开放,temperature 越高。通常从 0.7 开始调整,根据输出质量微调。 |
理解 temperature 是调优 LLM 输出质量的基础,也是面试中常考的知识点。
当系统中集成的工具数量增多时,大模型(LLM)在工具选择、参数生成和结果整合上容易出现"失真"------例如错误选择工具、遗漏必要参数、或输出与工具结果矛盾的内容。这是一个典型的 Agent 扩展性问题。以下是系统性的解决方案,结合 LangGraph 等框架的实践。
1. 工具描述优化(第一道防线)
问题根源:模型依赖工具的名称和描述来判断何时使用。模糊或相似的描述会导致选择错误。
解决方案:
- 清晰命名 :避免通用名称(如
tool1),使用动词+名词(如search_tickets、get_weather)。 - 详细描述:在描述中包含关键使用场景、参数含义和返回格式。可加入示例。
- 使用别名 :LangChain 的
@tool装饰器支持name和description参数。
python
@tool(name="get_weather", description="查询指定城市当天的实时天气,参数city为城市名(中文),返回温度、湿度、天气状况。")
def get_weather(city: str) -> str:
...
2. 动态工具选择(减少上下文冗余)
问题根源:将所有工具的描述都塞进系统提示,占用大量 token,增加模型"视野"负担。
解决方案:
- 基于意图的路由:在 Agent 上层增加一个轻量级分类器(或使用 LLM 做意图识别),根据用户输入决定加载哪些工具。
- 动态注入 :在 LangGraph 中,通过条件边(
add_conditional_edges)将不同意图导向不同的子图,每个子图只绑定相关工具。
python
def route_by_intent(state):
# 判断用户意图,返回子图名称
if "天气" in state["messages"][-1].content:
return "weather_subgraph"
else:
return "default_subgraph"
3. 工具调用参数校验与后处理
问题根源:模型可能生成不合理的参数值,导致工具返回错误或无用结果。
解决方案:
- 参数 Schema 强化:使用 Pydantic 模型定义工具输入,并在工具执行前校验。
- 工具执行后结果验证:检查工具返回是否有效,若无效则重试或回退。
- 给模型提供反馈 :如果工具调用失败,将错误信息以
ToolMessage返回给模型,让模型重新生成调用。
python
class WeatherInput(BaseModel):
city: str = Field(description="城市名,如北京")
unit: Literal["celsius", "fahrenheit"] = "celsius"
@tool(args_schema=WeatherInput)
def get_weather(city: str, unit: str) -> str:
...
4. 多轮对话+人工介入(Human-in-the-Loop)
问题根源:模型一次性决策可能错误,需要外部纠正。
解决方案:
- 对关键工具(如删除、支付)使用
interrupt或interrupt_before机制,在执行前请求用户确认。 - 允许用户纠错,如"不是这个城市,是上海"。
5. 工具分组与层级化 Agent
问题根源:大量工具平铺在同一个 Agent 中,模型难以区分。
解决方案:
- 采用 多 Agent 协作:顶层 Supervisor Agent 负责分配任务,底层 Worker Agent 各自拥有专属工具集。
- 或使用 LangGraph 的 子图(Subgraph),每个子图处理一类任务。
6. 模型选择与推理策略
问题根源:基础模型对工具调用支持弱(如某些开源模型)。
解决方案:
- 选择对函数调用支持更强的模型(如
gpt-4o、claude-3-opus、qwen-max)。 - 在系统提示中明确要求模型"必须先调用工具再回答"或"只能根据工具结果回答"。
- 启用 forced tool call :在
bind_tools中设置tool_choice="required"或指定具体工具名。
python
llm_with_tools = llm.bind_tools(tools, tool_choice="required")
7. 反馈回路与自我修正
问题根源:模型无法感知自身错误。
解决方案:
- 工具执行后,将结果和用户后续反馈一起作为新消息输入,形成多轮纠错。
- 在 LangGraph 中设计循环,当检测到工具结果不一致时,重新进入 Agent 节点再次推理。
8. 工具调用日志与监控
解决方案:
- 集成 LangSmith 或自定义日志,记录每次工具调用的输入输出,分析模型失真的模式。
- 根据日志调整工具描述或参数 Schema。
综合最佳实践(基于 LangGraph)
#mermaid-svg-Lc7B3MWnhR9NhRY3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .error-icon{fill:#552222;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .marker.cross{stroke:#333333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Lc7B3MWnhR9NhRY3 p{margin:0;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .cluster-label text{fill:#333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .cluster-label span{color:#333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .cluster-label span p{background-color:transparent;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .label text,#mermaid-svg-Lc7B3MWnhR9NhRY3 span{fill:#333;color:#333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .node rect,#mermaid-svg-Lc7B3MWnhR9NhRY3 .node circle,#mermaid-svg-Lc7B3MWnhR9NhRY3 .node ellipse,#mermaid-svg-Lc7B3MWnhR9NhRY3 .node polygon,#mermaid-svg-Lc7B3MWnhR9NhRY3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .rough-node .label text,#mermaid-svg-Lc7B3MWnhR9NhRY3 .node .label text,#mermaid-svg-Lc7B3MWnhR9NhRY3 .image-shape .label,#mermaid-svg-Lc7B3MWnhR9NhRY3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .rough-node .label,#mermaid-svg-Lc7B3MWnhR9NhRY3 .node .label,#mermaid-svg-Lc7B3MWnhR9NhRY3 .image-shape .label,#mermaid-svg-Lc7B3MWnhR9NhRY3 .icon-shape .label{text-align:center;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .node.clickable{cursor:pointer;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .arrowheadPath{fill:#333333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Lc7B3MWnhR9NhRY3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Lc7B3MWnhR9NhRY3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Lc7B3MWnhR9NhRY3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .cluster text{fill:#333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .cluster span{color:#333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Lc7B3MWnhR9NhRY3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .icon-shape,#mermaid-svg-Lc7B3MWnhR9NhRY3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .icon-shape p,#mermaid-svg-Lc7B3MWnhR9NhRY3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .icon-shape .label rect,#mermaid-svg-Lc7B3MWnhR9NhRY3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Lc7B3MWnhR9NhRY3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Lc7B3MWnhR9NhRY3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Lc7B3MWnhR9NhRY3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 天气
车票
其他
成功
失败
用户输入
意图识别节点
路由条件
天气子图: 只有天气工具
车票子图: 只有车票工具
通用Agent: 全部工具
执行工具
结果验证与反馈
输出
重试或人工介入
总结
解决"工具多导致模型失真"的核心思路是:减少单次决策的复杂性。具体手段包括:
- 优化工具描述
- 动态路由分组
- 参数验证与后处理
- 多 Agent 分工
- 强制工具调用
- 人工干预兜底
通过这些方法,可以显著提升多工具场景下 Agent 的回答准确性和可靠性。
"动态路由分组"是解决多工具场景下模型"选择困难症"的核心架构模式。它的核心思想是:不把所有的工具都塞给一个 Agent,而是根据用户的意图,将请求动态地分配给只挂载了相关工具的子 Agent(或子图)。
这就像一个大公司的总机(Router),接到电话后,根据来电意图转接到对应的专业部门(如"车票部门"、"天气部门"),而不是让一个前台文员去处理所有业务。
🧠 为什么它能解决"工具多导致失真"?
- 减少上下文噪音:LLM 的上下文窗口有限。当 50 个工具描述挤在一起时,模型容易忽视关键工具的细节。分组后,每次只把 3~5 个相关工具的描述放入 Prompt,大大降低了模型理解难度。
- 降低选择干扰 :心理学上"选择越多,错误越多"。将相似工具(如
get-ticket和search-ticket)归类,避免模型因名称相似而选错。 - 专用模型优化:每个子 Agent 可以拥有独立的系统提示词(如"你只负责车票查询,不要回答其他问题"),进一步强化特定任务的执行力。
⚙️ 在 LangGraph 中如何实现"动态路由分组"?
在 LangGraph 中,动态路由通过 条件边(Conditional Edges) 和 子图(Subgraphs) 或 专用节点 配合实现。
1. 定义路由函数(Router / 总机)
这个函数读取当前 State(主要是用户最新消息),判断意图,并返回下一个节点的名称。
python
def route_by_intent(state: MessagesState) -> Literal["ticket_agent", "weather_agent", "chart_agent", "general_agent"]:
"""动态路由:分析用户输入,分配到对应的子 Agent"""
last_msg = state["messages"][-1].content
# 简单关键词匹配(也可用轻量级 LLM 分类器)
if any(k in last_msg for k in ["票", "高铁", "动车", "火车", "12306"]):
return "ticket_agent"
elif any(k in last_msg for k in ["天气", "温度", "下雨", "预报"]):
return "weather_agent"
elif any(k in last_msg for k in ["图表", "柱状图", "折线图", "饼图"]):
return "chart_agent"
else:
return "general_agent" # 兜底的通用 Agent
2. 构建专用子图(Subgraphs / 专业部门)
每个子图只加载自己的专属工具集,并拥有特定的系统提示。
python
# ----- 子图 1:车票专家 (只挂载 12306 相关工具)-----
ticket_builder = StateGraph(MessagesState)
ticket_tools = [tool for tool in all_tools if "ticket" in tool.name or "12306" in tool.name]
# 可以绑定强制 tool_choice
ticket_llm = llm.bind_tools(ticket_tools, tool_choice="get-tickets")
# ... 添加节点和边 ...
ticket_graph = ticket_builder.compile()
# ----- 子图 2:图表专家 (只挂载 AntV 图表工具)-----
chart_builder = StateGraph(MessagesState)
chart_tools = [tool for tool in all_tools if "chart" in tool.name or "bar" in tool.name]
chart_llm = llm.bind_tools(chart_tools) # 不强制,让模型自主决定
# ... 添加节点和边 ...
chart_graph = chart_builder.compile()
3. 在主图中挂载子图
将子图作为节点添加到主工作流中,并利用条件边连接路由函数。
python
# 主图构建
main_builder = StateGraph(MessagesState)
# 将子图作为节点添加
main_builder.add_node("ticket_agent", ticket_graph) # 子图节点
main_builder.add_node("weather_agent", weather_graph)
main_builder.add_node("chart_agent", chart_graph)
main_builder.add_node("general_agent", general_agent_node) # 普通节点
# 设置入口并添加条件边
main_builder.set_entry_point("router")
main_builder.add_node("router", route_by_intent) # 或者直接作为条件边
# 关键:条件边决定去哪个子图
main_builder.add_conditional_edges(
"router", # 起始节点
route_by_intent, # 路由函数
{
"ticket_agent": "ticket_agent",
"weather_agent": "weather_agent",
"chart_agent": "chart_agent",
"general_agent": "general_agent"
}
)
# 所有子图执行完成后,回到主流程结束
main_builder.add_edge("ticket_agent", END)
main_builder.add_edge("weather_agent", END)
# ...
📈 执行流程图解
渲染错误: Mermaid 渲染失败: Parse error on line 4: ...ntent} C -->|包含"车票"| D[车票子图
只挂载 ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'STR'
💡 高级技巧与注意事项
-
意图分类器的选择:
- 关键词匹配:简单快速,适合固定场景。
- 轻量级 LLM 分类 :使用
gpt-3.5-turbo或qwen-turbo做零样本分类,准确率更高,适合复杂语义。 - Embedding 相似度:将用户输入与预定义的意图模板计算相似度,适合多语言场景。
-
子图间状态共享:
- 子图可以共享主图的 State(如
user_id、conversation_history)。 - 可以通过子图的输入/输出映射(
input_mapper/output_mapper)控制状态传递。
- 子图可以共享主图的 State(如
-
兜底策略:
- 如果路由函数置信度低,默认进入"通用 Agent"(只挂载少量工具),避免因错误路由导致无法回答。
-
性能优化:
- 子图可以独立编译和缓存,主图加载时只需引用子图对象。
- 路由函数本身应轻量化,避免调用大模型,以免增加延迟。
通过动态路由分组,你的系统从"一个万能 Agent"进化为"多个专业 Agent + 智能总机",不仅解决了工具过多导致的失真,还提升了系统的可维护性和扩展性。