1.1大模型配置面试题

你看到的 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_ticketsget_weather)。
  • 详细描述:在描述中包含关键使用场景、参数含义和返回格式。可加入示例。
  • 使用别名 :LangChain 的 @tool 装饰器支持 namedescription 参数。
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)

问题根源:模型一次性决策可能错误,需要外部纠正。

解决方案

  • 对关键工具(如删除、支付)使用 interruptinterrupt_before 机制,在执行前请求用户确认。
  • 允许用户纠错,如"不是这个城市,是上海"。

5. 工具分组与层级化 Agent

问题根源:大量工具平铺在同一个 Agent 中,模型难以区分。

解决方案

  • 采用 多 Agent 协作:顶层 Supervisor Agent 负责分配任务,底层 Worker Agent 各自拥有专属工具集。
  • 或使用 LangGraph 的 子图(Subgraph),每个子图处理一类任务。

6. 模型选择与推理策略

问题根源:基础模型对工具调用支持弱(如某些开源模型)。

解决方案

  • 选择对函数调用支持更强的模型(如 gpt-4oclaude-3-opusqwen-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),接到电话后,根据来电意图转接到对应的专业部门(如"车票部门"、"天气部门"),而不是让一个前台文员去处理所有业务。


🧠 为什么它能解决"工具多导致失真"?

  1. 减少上下文噪音:LLM 的上下文窗口有限。当 50 个工具描述挤在一起时,模型容易忽视关键工具的细节。分组后,每次只把 3~5 个相关工具的描述放入 Prompt,大大降低了模型理解难度。
  2. 降低选择干扰 :心理学上"选择越多,错误越多"。将相似工具(如 get-ticketsearch-ticket)归类,避免模型因名称相似而选错。
  3. 专用模型优化:每个子 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'


💡 高级技巧与注意事项

  1. 意图分类器的选择

    • 关键词匹配:简单快速,适合固定场景。
    • 轻量级 LLM 分类 :使用 gpt-3.5-turboqwen-turbo 做零样本分类,准确率更高,适合复杂语义。
    • Embedding 相似度:将用户输入与预定义的意图模板计算相似度,适合多语言场景。
  2. 子图间状态共享

    • 子图可以共享主图的 State(如 user_idconversation_history)。
    • 可以通过子图的输入/输出映射(input_mapper / output_mapper)控制状态传递。
  3. 兜底策略

    • 如果路由函数置信度低,默认进入"通用 Agent"(只挂载少量工具),避免因错误路由导致无法回答。
  4. 性能优化

    • 子图可以独立编译和缓存,主图加载时只需引用子图对象。
    • 路由函数本身应轻量化,避免调用大模型,以免增加延迟。

通过动态路由分组,你的系统从"一个万能 Agent"进化为"多个专业 Agent + 智能总机",不仅解决了工具过多导致的失真,还提升了系统的可维护性和扩展性。