Convertor-based templates¶
Prompt templates that automatically bind to a database are built on top of a specialized kind of prompt template in LangChain called ConvertorPromptTemplate
.
This kind of prompt has one important ability: at its core is a "convertor function", that transforms some of the input variables passed to the prompt's format
method into another set of output variables with other values:
convertor: {input1: in_value1, ...} => {output1: out_value1, ...}
In other words, some of the named arguments to pass to the format
method are not ending up directly in the template string's placeholders, rather they are used by the convertor to obtain the final variables for the template string.
The intended usage is as follows: the convertor, bound to a database, is set to perform a lookup based on the tables' primary keys to return the desired column values, so that one can pass the lookup keys to format
and have a prompt magically filled with the data columns from the DB corresponding to that primary key.
Here is a diagram for the case of the CassandraReaderPromptTemplate
(note: the prompt and the variables have been simplified from the case shown in the notebook for the sake of clarity):
A simple example with a custom convertor function will show this mechanism in action and provide guidance should you need to implement your own convertor-based prompt template.
Building a convertor prompt template¶
Build a mock "account balance checker" that associates a current balance to each user (a key-value store), but that also keeps a counter of how many times a certain balance is accessed.
class Balancer():
def __init__(self, accounts):
self.accounts = accounts
self._accesses = {}
def read(self, user):
if user in self.accounts:
self._accesses[user] = self._accesses.get(user, 0) + 1
return self.accounts[user]
def accesses(self):
return self._accesses
bal0 = Balancer({'Rita': 100, 'Otto': 40, 'Anna': 150})
bal0.read('Rita')
bal0.read('Rita')
bal0.read('Anna')
print(bal0.accesses())
{'Rita': 2, 'Anna': 1}
Ok, you have seen how this works.
Now, create another balancer for use in the template:
myBalance = Balancer({'Katie': 1000, 'Gil': 800, 'Hugh': 1200})
Template creation¶
from langchain.prompts.database.convertor_prompt_template import ConvertorPromptTemplate
The template string¶
balancePrompt = ("Hello. Your balance is {balance}. "
"You have {unreads} unread message(s).")
The getter and the convertor-prompt-template¶
The "getter", i.e. the convertor, has a dict-in, dict-out signature and in most cases (but not necessarily) retains a reference to a global object (in this case it's myBalance
, but it could be a DB connection or anything):
def bal_getter(args_dict):
user_name = args_dict['user_name']
return {
'balance': myBalance.read(user_name),
}
When creating the template, note that you have to specify the inputs and outputs of the convertor as well. That is to allow proper management of the prompt template under the various manipulations LangChain can do to prompts.
myCustomTemplate = ConvertorPromptTemplate(
template=balancePrompt,
input_variables=['unreads'],
convertor=bal_getter,
convertor_input_variables=['user_name'],
convertor_output_variables=['balance'],
)
Formatting¶
At this point, formatting the template is a matter of passing the inputs to the convertor (alongside any direct-to-prompt-string variable the prompt may have).
myCustomTemplate.format(user_name='Gil', unreads=123)
'Hello. Your balance is 800. You have 123 unread message(s).'
myCustomTemplate.format(user_name='Gil', unreads=456)
'Hello. Your balance is 800. You have 456 unread message(s).'
myCustomTemplate.format(user_name='Katie', unreads=789)
'Hello. Your balance is 1000. You have 789 unread message(s).'
A quick check reveals that the global dependency of the convertor has indeed been used throughout the calls to method read
of myBalance
:
myBalance.accesses()
{'Gil': 2, 'Katie': 1}