Sunforger

Sunforger

First run-through of langchain + ollama: a few pitfalls notes

Version and Configuration#

Software Version:

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 version: 3.10.16

Requirements#

An episodic task needs to be performed. In each step, first, a solution is obtained using traditional methods. Then, a large language model is used to optimize the solution to observe the final effect.

Implementation#

File Structure

src/llm
├── agents.py
├── keys.toml
├── models.py
├── prompts
│   ├── __init__.py
│   ├── [xxxx] // your scenario name
│   │   ├── human.txt
│   │   └── system.txt
│   └── utils.py
└── tools.py

First, use it locally on Ollama, and after it works, consider integrating a more powerful model. For example, I initially used the qwen2:7b model.

# 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" 
        ) # Note: when passing parameters, the llm should be passed to the model parameter, otherwise a Validation Error will occur
    elif provider == "openai":
        return ChatOpenAI(...) # omitted
    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])

Prompt File, reference link configuration 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

Pitfalls#

1. Version#

Langchain is still evolving, and there are certain interface differences between earlier and current versions that need special attention. Timeliness is crucial when searching for information online.

2. Prompts#

When using the create_structured_chat_agent function to create an agent, it is important to ensure that the prompt must contain the three placeholders: tools, tool_names, agent_scratchpad, wrapped in {}.

Otherwise, it will raise an error ValueError: Prompt missing required variables: {'agent_scratchpad', 'tool_names', 'tools'}

During parsing, this prompt template will be treated as an f-string, so the contents within {} will be automatically replaced with variable values.

Update 25/6/24#

Using traditional .txt as prompts has poor scalability (as it does not support comments). It is recommended to use jinja2 templates to manage prompts.

This way, comments can be supported, and placeholders can be parsed.

The jinja template uses {{...}} for placeholders, which is different from the {...} used in langchain for prompt placeholders. These two cannot be mixed; otherwise, parsing errors will occur.

For the system prompt, it is usually provided when the agent is initialized and will not be changed afterward. If parameters need to be passed, it is recommended to do so through the jinja template.

from langchain_core.prompts.string import jinja2_formatter

s_template = jinja2_formatter(
  Path(path / "system.jinja").read_text(encoding="utf-8"),
  ... # pass in parameters corresponding to placeholders  
)
s_prompt = SystemMessagePromptTemplate.from_template(s_template)

For the human prompt, which is the agent's input, it is usually updated in real-time for each step, so it can be loaded using jinja or passed in through the .invoke() method. However, note that the former requires placeholders to be written in {{...}} form and imported similarly to the system prompt. The latter should use the {...} style as placeholders and be imported directly as a string (as shown below).

h_template = Path(path / "human.jinja").read_text(encoding="utf-8")
h_prompt = HumanMessagePromptTemplate.from_template(h_template)

3. Invocation#

When executing agent.invoke(), it is necessary to provide the content of the input field as well as the value for the intermediate_steps field.

Otherwise, it will raise an error KeyError: 'intermediate_steps'

Update#

You can use AgentExecutor to wrap the agent, allowing for more fine-tuned customization and the ability to return debugging information.

## 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"})

This allows for more precise customization without manually passing the intermediate_steps parameter.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.