Prompt Templates, intro¶
CassIO powers a sophisticated set of bindings to seamlessly inject data from Cassandra tables into your LangChain prompt templates.
Basic usage¶
First, import the specialized Cassandra prompt template:
from langchain.prompts.database import CassandraReaderPromptTemplate
This cell simply obtains a Session
object, i.e. an active connection to your database. Replace with custom code if you are not using Astra DB.
from cqlsession import getCQLSession, getCQLKeyspace
cqlMode = 'astra_db' # 'astra_db'/'local'
session = getCQLSession(mode=cqlMode)
keyspace = getCQLKeyspace(mode=cqlMode)
ctemplate0 = """Please answer a question from a user.
Keep in mind that the user's age is {user_age} and they live in a city with
nickname {city_nickname}.
USER'S QUESTION: {user_question}
YOUR ANSWER:
"""
Natural binding with the DB¶
In the (string) template above, some variables are to be filled with a DB lookup.
The following instructions specifies the details of the binding: for instance,
the variable user_age
is to be found on table people
, specifically in column age
:
cassPrompt = CassandraReaderPromptTemplate(
session=session,
keyspace=keyspace,
field_mapper={
'user_age': ('people', 'age'),
'city_nickname': ('nickname_by_city', 'nickname'),
},
template=ctemplate0,
input_variables=["user_question"],
)
Note that in the command above you specify the primary key columns as input_variables
, and not the variable names found in the prompt string above.
When formatting the Prompt Template, you will have to specify the primary key values for the DB lookup -- the rest is done by the prompt template.
In this case there are two lookups from as many tables: the prompt template takes care of everything, provided you pass all the primary key columns required across tables.
Note: this operation essentially is a client-side join (a standard pattern with Cassandra).
print(cassPrompt.format(city='turin', name='beppe',
user_question='Is functional programming fun?'))
Please answer a question from a user. Keep in mind that the user's age is 2 and they live in a city with nickname CereaNeh. USER'S QUESTION: Is functional programming fun? YOUR ANSWER:
Arbitrary row functions¶
You can specify an arbitrary function to transform the database row into the returned field. The function gets a {column_name: value}
dictionary expressing the row and returns the value for the prompt template:
def nicknamer(row_dict):
return f"{row_dict['nickname']} (i.e. {row_dict['city']})"
field_mapper_f = {
'user_age': ('people', lambda row_dict: row_dict['age'] + 10000),
'city_nickname': ('nickname_by_city', nicknamer),
}
cassPromptF = CassandraReaderPromptTemplate(
session=session,
keyspace=keyspace,
field_mapper=field_mapper_f,
template=ctemplate0,
input_variables=["user_question"],
)
print(cassPromptF.format(city='milan', name='samanta',
user_question='Is there a square circle?'))
Please answer a question from a user. Keep in mind that the user's age is 10014 and they live in a city with nickname Taaac (i.e. milan). USER'S QUESTION: Is there a square circle? YOUR ANSWER:
Null and missing values¶
You can control how the prompt template should behave when a None
value is encountered or even when a table has no rows altogether for a given primary key.
First, you can pass a boolean parameter admit_nulls
to the prompt template.
Second, you can use the full four-element tuple format for the entries in the "field mapper". This would be (table_name, column_name_or_function, admit_nulls, default_value)
(whose admit_nulls
will override the overall default).
field_mapper_n = {
'user_age': ('people', 'age'),
'city_nickname': ('nickname_by_city', 'nickname', True, '(no nickname)'),
}
cassPromptN = CassandraReaderPromptTemplate(
session=session,
keyspace=keyspace,
field_mapper=field_mapper_n,
template=ctemplate0,
input_variables=["user_question"],
admit_nulls=False,
)
# Note: there is no "tokyo" in the nicknames table
print(cassPromptN.format(city='tokyo', name='hideo',
user_question='What are we having for lunch?'))
Please answer a question from a user. Keep in mind that the user's age is 144 and they live in a city with nickname (no nickname). USER'S QUESTION: What are we having for lunch? YOUR ANSWER:
try:
# Note: there are no rows with city='madrid' in the "people" table
print(cassPromptN.format(city='madrid', name='alberto',
user_question='What are we having for lunch?'))
except Exception as e:
print(f"Exception => {str(e)}")
Exception => Null data found for "user_age"
Partialing Prompt Templates¶
Cassandra-powered prompt templates support partialing. Suppose you have just enough information to bind the template to the DB-lookup values: you can leave the user_question
unspecified for later completion at "format-time":
cassPartialPrompt = cassPrompt.partial(city='lisbon', name='Pedro')
The partial prompt template will keep the provided inputs ready to execute the full lookup-and-format operation when needed:
print(cassPartialPrompt.format(user_question='Em verdade, o que quiseres?'))
Please answer a question from a user. Keep in mind that the user's age is 1 and they live in a city with nickname ACidade. USER'S QUESTION: Em verdade, o que quiseres? YOUR ANSWER:
You can partial on any choice of input variables, even mixing database-bound and regular inputs:
cassPartialPrompt2 = cassPrompt.partial(city='lisbon', user_question='Estou perto do Tejo?')
print(cassPartialPrompt2.format(name='Pedro'))
Please answer a question from a user. Keep in mind that the user's age is 1 and they live in a city with nickname ACidade. USER'S QUESTION: Estou perto do Tejo? YOUR ANSWER:
Chat Prompt Templates¶
The Cassandra-specific approach can be seamlessly integrated with LangChain's "chat prompt templates", which represent a (template-based) way to manage chat exchanges.
Start with a prompt, not much dissimilar from what you've seen so far:
systemTemplate = """
You are a chat assistant, helping a user of age {user_age} from a city
they refer to as {city_nickname}.
"""
cassSystemPrompt = CassandraReaderPromptTemplate(
session=session,
keyspace=keyspace,
template=systemTemplate,
input_variables=[],
field_mapper={
'user_age': ('people', 'age'),
'city_nickname': ('nickname_by_city', 'nickname'),
},
)
Next, you need specific abstractions to wrap this "system prompt" as part of a broader chat exchange:
from langchain.prompts import (
ChatPromptTemplate,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
)
systemMessagePrompt = SystemMessagePromptTemplate(prompt=cassSystemPrompt)
A sequence of messages¶
Once you wrap a single prompt template as a "system message prompt", go ahead and make it part of a longer chat conversation:
humanTemplate = "{text}"
humanMessagePrompt = HumanMessagePromptTemplate.from_template(humanTemplate)
cassChatPrompt = ChatPromptTemplate.from_messages(
[systemMessagePrompt, humanMessagePrompt]
)
Formatting¶
LangChain takes care of correctly propagating the formatting steps throughout the sequence of messages, including the Cassandra-backed template:
print(cassChatPrompt.format_prompt(
city='turin',
name='beppe',
text='Assistant, please help me!'
).to_string())
System: You are a chat assistant, helping a user of age 2 from a city they refer to as CereaNeh. Human: Assistant, please help me!
Partialing and Chat Prompt Templates¶
In some cases, you may want to partial with respect to the database lookup key(s) even within a chat prompt template:
cassChatPartialPrompt = cassChatPrompt.partial(
city='turin',
name='beppe'
)
print(cassChatPartialPrompt.format(text="Hahaha!"))
System: You are a chat assistant, helping a user of age 2 from a city they refer to as CereaNeh. Human: Hahaha!