252 lines
7.3 KiB
Python
252 lines
7.3 KiB
Python
"""Unittests for langchain.agents.chat package."""
|
|
|
|
from textwrap import dedent
|
|
from typing import Any
|
|
|
|
from langchain_core.agents import AgentAction, AgentFinish
|
|
from langchain_core.prompts.chat import (
|
|
ChatPromptTemplate,
|
|
HumanMessagePromptTemplate,
|
|
SystemMessagePromptTemplate,
|
|
)
|
|
from langchain_core.tools import Tool
|
|
|
|
from langchain_classic.agents.structured_chat.base import StructuredChatAgent
|
|
from langchain_classic.agents.structured_chat.output_parser import (
|
|
StructuredChatOutputParser,
|
|
)
|
|
|
|
output_parser = StructuredChatOutputParser()
|
|
|
|
|
|
def get_action_and_input(text: str) -> tuple[str, str]:
|
|
output = output_parser.parse(text)
|
|
if isinstance(output, AgentAction):
|
|
return output.tool, str(output.tool_input)
|
|
if isinstance(output, AgentFinish):
|
|
return output.return_values["output"], output.log
|
|
msg = "Unexpected output type" # type: ignore[unreachable]
|
|
raise ValueError(msg)
|
|
|
|
|
|
def test_parse_with_language() -> None:
|
|
llm_output = """I can use the `foo` tool to achieve the goal.
|
|
|
|
Action:
|
|
```json
|
|
{
|
|
"action": "foo",
|
|
"action_input": "bar"
|
|
}
|
|
```
|
|
"""
|
|
action, action_input = get_action_and_input(llm_output)
|
|
assert action == "foo"
|
|
assert action_input == "bar"
|
|
|
|
|
|
def test_parse_without_language() -> None:
|
|
llm_output = """I can use the `foo` tool to achieve the goal.
|
|
|
|
Action:
|
|
```
|
|
{
|
|
"action": "foo",
|
|
"action_input": "bar"
|
|
}
|
|
```
|
|
"""
|
|
action, action_input = get_action_and_input(llm_output)
|
|
assert action == "foo"
|
|
assert action_input == "bar"
|
|
|
|
|
|
def test_parse_with_language_and_spaces() -> None:
|
|
llm_output = """I can use the `foo` tool to achieve the goal.
|
|
|
|
Action:
|
|
```json
|
|
|
|
{
|
|
"action": "foo",
|
|
"action_input": "bar"
|
|
}
|
|
```
|
|
"""
|
|
action, action_input = get_action_and_input(llm_output)
|
|
assert action == "foo"
|
|
assert action_input == "bar"
|
|
|
|
|
|
def test_parse_without_language_without_a_new_line() -> None:
|
|
llm_output = """I can use the `foo` tool to achieve the goal.
|
|
|
|
Action:
|
|
```{"action": "foo", "action_input": "bar"}```
|
|
"""
|
|
action, action_input = get_action_and_input(llm_output)
|
|
assert action == "foo"
|
|
assert action_input == "bar"
|
|
|
|
|
|
def test_parse_with_language_without_a_new_line() -> None:
|
|
llm_output = """I can use the `foo` tool to achieve the goal.
|
|
|
|
Action:
|
|
```json{"action": "foo", "action_input": "bar"}```
|
|
"""
|
|
# TODO: How should this be handled?
|
|
output, log = get_action_and_input(llm_output)
|
|
assert output == llm_output
|
|
assert log == llm_output
|
|
|
|
|
|
def test_parse_case_matched_and_final_answer() -> None:
|
|
llm_output = """I can use the `foo` tool to achieve the goal.
|
|
|
|
Action:
|
|
```json
|
|
{
|
|
"action": "Final Answer",
|
|
"action_input": "This is the final answer"
|
|
}
|
|
```
|
|
"""
|
|
output, log = get_action_and_input(llm_output)
|
|
assert output == "This is the final answer"
|
|
assert log == llm_output
|
|
|
|
|
|
# TODO: add more tests.
|
|
# Test: StructuredChatAgent.create_prompt() method.
|
|
class TestCreatePrompt:
|
|
# Test: Output should be a ChatPromptTemplate with sys and human messages.
|
|
def test_create_prompt_output(self) -> None:
|
|
prompt = StructuredChatAgent.create_prompt(
|
|
[Tool(name="foo", description="Test tool FOO", func=lambda x: x)],
|
|
)
|
|
|
|
assert isinstance(prompt, ChatPromptTemplate)
|
|
assert len(prompt.messages) == 2
|
|
assert isinstance(prompt.messages[0], SystemMessagePromptTemplate)
|
|
assert isinstance(prompt.messages[1], HumanMessagePromptTemplate)
|
|
|
|
# Test: Format with a single tool.
|
|
def test_system_message_single_tool(self) -> None:
|
|
prompt: Any = StructuredChatAgent.create_prompt(
|
|
[Tool(name="foo", description="Test tool FOO", func=lambda x: x)],
|
|
)
|
|
actual = prompt.messages[0].prompt.format()
|
|
|
|
expected = dedent(
|
|
"""
|
|
Respond to the human as helpfully and accurately as possible. You have access to the following tools:
|
|
|
|
foo: Test tool FOO, args: {'tool_input': {'type': 'string'}}
|
|
|
|
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 foo
|
|
|
|
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:.
|
|
Thought:
|
|
""", # noqa: E501
|
|
).strip()
|
|
|
|
assert actual == expected
|
|
|
|
# Test: Format with multiple tools.
|
|
#
|
|
# Check:
|
|
#
|
|
# You have access to the following tools:
|
|
# ...
|
|
#
|
|
# and
|
|
#
|
|
# Valid "action" values: "Final Answer" or ...
|
|
#
|
|
def test_system_message_multiple_tools(self) -> None:
|
|
prompt: Any = StructuredChatAgent.create_prompt(
|
|
[
|
|
Tool(name="foo", description="Test tool FOO", func=lambda x: x),
|
|
Tool(name="bar", description="Test tool BAR", func=lambda x: x),
|
|
],
|
|
)
|
|
|
|
actual = prompt.messages[0].prompt.format()
|
|
|
|
expected = dedent(
|
|
"""
|
|
Respond to the human as helpfully and accurately as possible. You have access to the following tools:
|
|
|
|
foo: Test tool FOO, args: {'tool_input': {'type': 'string'}}
|
|
bar: Test tool BAR, args: {'tool_input': {'type': 'string'}}
|
|
|
|
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 foo, bar
|
|
|
|
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:.
|
|
Thought:
|
|
""", # noqa: E501
|
|
).strip()
|
|
|
|
assert actual == expected
|