版本與配置#
軟體版本:
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 參數。