Skip to content

Commit

Permalink
[Feat] Accept non-dict if only 1 prompt input variable (#19156)
Browse files Browse the repository at this point in the history
For prompt templates with only 1 variable (common in e.g.,
MessageGraph), it's convenient to wrap the incoming object in the
variable before formatting.


The downside of this, of course, would be that some number of
invocations will successfully format when the user may have intended to
format it properly before
  • Loading branch information
hinthornw committed Mar 20, 2024
1 parent d9396bd commit 68298cd
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 8 deletions.
13 changes: 9 additions & 4 deletions libs/core/langchain_core/prompts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,15 @@ def get_input_schema(

def _format_prompt_with_error_handling(self, inner_input: Dict) -> PromptValue:
if not isinstance(inner_input, dict):
raise TypeError(
f"Expected mapping type as input to {self.__class__.__name__}. "
f"Received {type(inner_input)}."
)
if len(self.input_variables) == 1:
var_name = self.input_variables[0]
inner_input = {var_name: inner_input}

else:
raise TypeError(
f"Expected mapping type as input to {self.__class__.__name__}. "
f"Received {type(inner_input)}."
)
missing = set(self.input_variables).difference(inner_input)
if missing:
raise KeyError(
Expand Down
48 changes: 44 additions & 4 deletions libs/core/langchain_core/prompts/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,11 +574,51 @@ class ChatPromptTemplate(BaseChatPromptTemplate):
("human", "{user_input}"),
])
messages = template.format_messages(
name="Bob",
user_input="What is your name?"
prompt_value = template.invoke(
{
"name": "Bob",
"user_input": "What is your name?"
}
)
"""
# Output:
# ChatPromptValue(
# messages=[
# SystemMessage(content='You are a helpful AI bot. Your name is Bob.'),
# HumanMessage(content='Hello, how are you doing?'),
# AIMessage(content="I'm doing well, thanks!"),
# HumanMessage(content='What is your name?')
# ]
#)
Single-variable template:
If your prompt has only a single input variable (i.e., 1 instance of "{variable_nams}"),
and you invoke the template with a non-dict object, the prompt template will
inject the provided argument into that variable location.
.. code-block:: python
from langchain_core.prompts import ChatPromptTemplate
template = ChatPromptTemplate.from_messages([
("system", "You are a helpful AI bot. Your name is Carl."),
("human", "{user_input}"),
])
prompt_value = template.invoke("Hello, there!")
# Equivalent to
# prompt_value = template.invoke({"user_input": "Hello, there!"})
# Output:
# ChatPromptValue(
# messages=[
# SystemMessage(content='You are a helpful AI bot. Your name is Carl.'),
# HumanMessage(content='Hello, there!'),
# ]
# )
""" # noqa: E501

input_variables: List[str]
"""List of input variables in template messages. Used for validation."""
Expand Down
13 changes: 13 additions & 0 deletions libs/core/tests/unit_tests/prompts/test_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,16 @@ def test_chat_prompt_message_placeholder_partial() -> None:
assert prompt.format_messages() == []
prompt = prompt.partial(history=[("system", "foo")])
assert prompt.format_messages() == [SystemMessage(content="foo")]


def test_messages_prompt_accepts_list() -> None:
prompt = ChatPromptTemplate.from_messages([MessagesPlaceholder("history")])
value = prompt.invoke([("user", "Hi there")]) # type: ignore
assert value.to_messages() == [HumanMessage(content="Hi there")]

# Assert still raises a nice error
prompt = ChatPromptTemplate.from_messages(
[("system", "You are a {foo}"), MessagesPlaceholder("history")]
)
with pytest.raises(TypeError):
prompt.invoke([("user", "Hi there")]) # type: ignore

0 comments on commit 68298cd

Please sign in to comment.