版本与配置#
软件版本:
langchain 0.3.25 pypi_0 pypi
langchain-community 0.3.25 pypi_0 pypi
langchain-core 0.3.65 pypi_0 pypi
langchain-ollama 0.3.3 pypi_0 pypi
langchain-openai 0.3.24 pypi_0 pypi
langchain-text-splitters 0.3.8 pypi_0 pypi
python
版本:3.10.16
需求#
需要进行一个 episodic 的任务。在每一个 step 里,首先以传统方法求出一个方案。然后再过大语言模型对方案进行优化,以观察最后的效果。
实现#
文件架构
src/llm
├── agents.py
├── keys.toml
├── models.py
├── prompts
│ ├── __init__.py
│ ├── [xxxx] // your scenario name
│ │ ├── human.txt
│ │ └── system.txt
│ └── utils.py
└── tools.py
先在本地 Ollama 上使用一下,通了之后再去考虑对接强大一点的模型。比如我这面首先使用了 qwen2:7b
这个模型
# test/test_llm.py
from langchain.agents import create_structured_chat_agent
from src.llm.models import get_model
from src.llm.prompts.utils import load_prompts
def test_llm():
model = get_model("ollama", "qwen2:7b")
prompt = load_prompts(scenario="routing")
agent = create_structured_chat_agent(model, [], prompt)
result = agent.invoke({"input": "hello", "intermediate_steps": []})
print(result)
# models.py
from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI
def get_model(provider: str, model: str, temperature: float = 0.3):
if provider == "ollama":
return ChatOllama(
model=model, temperature=temperature, base_url="http://localhost:11434"
) # 传参注意,llm 要传到 model 参数里去,否则会报 Validation Error
elif provider == "openai":
return ChatOpenAI(...) # 省略
else:
raise ValueError("unsupported provider")
# prompts/utils.py
from pathlib import Path
from langchain.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
SystemMessagePromptTemplate,
)
def load_prompts(scenario: str):
path = Path(__file__).parent / scenario
with open(path / "system.txt", "r", encoding="utf-8") as f:
s_template = f.read()
s_prompt = SystemMessagePromptTemplate.from_template(s_template)
with open(path / "human.txt", "r", encoding="utf-8") as f:
h_template = f.read()
h_prompt = HumanMessagePromptTemplate.from_template(h_template)
return ChatPromptTemplate.from_messages([s_prompt, h_prompt])
提示词文件,参考链接配制 https://smith.langchain.com/hub/hwchase17/structured-chat-agent
# human.txt
{input}
{agent_scratchpad}
(reminder to respond in a JSON blob no matter what)
# system.txt
Respond to the human as helpfully and accurately as possible. You have access to the following tools:
{tools}
Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input).
Valid "action" values: "Final Answer" or {tool_names}
Provide only ONE action per $JSON_BLOB, as shown:
{{
"action": $TOOL_NAME,
"action_input": $INPUT
}}
Follow this format:
Question: input question to answer
Thought: consider previous and subsequent steps
Action:
$JSON_BLOB
Observation: action result
... (repeat Thought/Action/Observation N times)
Thought: I know what to respond
Action:
{{
"action": "Final Answer",
"action_input": "Final response to human"
}}
Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:```$JSON_BLOB```then Observation
坑点#
1. 版本#
langchain 还在不断迭代,早先版本和现今版本有一定的接口差异,需要特别注意。在网络查询资料时,时效性很重要。
2. 提示词#
在使用 create_structured_chat_agent
函数创建 agent 的时候,要注意提示词内必须包含 tools
, tools_name
, agent_scratchpad
这三个占位符,并用 {}
包裹起来。
否则报错
ValueError: Prompt missing required variables: {'agent_scratchpad', 'tool_names', 'tools'}
在解析时,这个提示词模板会被认为是
f-string
所以会自动将{}
中的内容替换成变量值
更新 25/6/24#
用传统 .txt
作为提示词,扩展性太差了(因为不支持注释)。选用 jinja2
模板来管理提示词。
这样既能支持注释,又可以解析占位符。
jinja
模板使用 {{...}}
来做占位符,这与 langchain
中使用 {...}
作为提示词占位符不同。这两个不能混用,否则会发生解析错误。
对于 system prompt
,通常 agent 初始化的时候就要给出,然后后续不会更改。如果需要传参,建议通过 jinja
模板传入。
from langchain_core.prompts.string import jinja2_formatter
s_template = jinja2_formatter(
Path(path / "system.jinja").read_text(encoding="utf-8"),
... # 传入占位符对应的参数
)
s_prompt = SystemMessagePromptTemplate.from_template(s_template)
对于 human prompt
也就是 agent 的输入,通常是每一步实时更新的,所以,可以采用 jinja
载入,也可以通过 .invoke()
方法传入。但需要注意,前者需要将占位符写成 {{...}}
形式,然后以类似 system prompt
的方式导入 human prompt
。而后者需要注意,应该以 langchain
风格的 {...}
作为占位符,然后直接以 string 的方式导入(如下所示)。
h_template = Path(path / "human.jinja").read_text(encoding="utf-8")
h_prompt = HumanMessagePromptTemplate.from_template(h_template)
3. 调用#
在执行 agent.invoke()
的时候。需要给出 input
字段内容的同时,还需要给出 intermediate_steps
字段的值。
否则报错
KeyError: 'intermediate_steps'
更新#
可以使用 AgentExecutor 将 agent 包装一下,这样就可以有更精细的定制,并且能够返回调试信息。
## original code
agent = create_structured_chat_agent(model, [], prompt)
result = agent.invoke({"input": "hello", "intermediate_steps": []})
## updated code
agent = create_structured_chat_agent(model, [], prompt)
executor = AgentExecutor.from_agent_and_tools(
agent=agent, tools=[], verbose=True, return_intermediate_steps=True
)
result = executor.invoke({"input": "hello"})
在允许更精细定制化的同时,也无需手动传入 intermediate_steps 参数。