defmodule LangChain.PromptTemplate do
@moduledoc """
a PromptTemplate is just a normal string template,
you can pass it a set of values and it will interpolate them.
You can also partially evaluate the template by calling the partial/2 function
input_variables will contain the list of variables that still need to be specified to
complete the template.
"""
@derive Jason.Encoder
defstruct [
# template is a string
template: "",
# input_variables is a list of variables that need to be specified for that template
input_variables: [],
# partial_variables is a map of variables that have already been specified and can be applied to the template
partial_variables: %{},
# src is the source of the message, currently one of :user, :system, :ai, :generic
src: :user
]
@doc """
converts to eex and then interpolates the values+partial_variables.
(eex wants values and partial_variables to be specified as a map with atomic keys)
"""
def format(template, values) do
# env is a keyword list merging values and partials and convert
env =
Map.merge(values, template.partial_variables)
|> Map.to_list()
|> Enum.map(fn {k, v} ->
{k, v}
if is_function(v) do
case v.() do
{:ok, res} -> {k, res}
_ -> {k, "[template function #{k} failed to render]"}
end
else
{k, v}
end
end)
outcome = template.template |> EEx.eval_string(env)
{:ok, outcome}
end
@doc """
partially apply the variables in 'partial' to the partial_variables and
remove them from the input_variables
"""
def partial(template, partial) do
keys = Map.keys(partial)
input_variables_without_partial = template.input_variables -- keys
partial_variables = Map.merge(template.partial_variables, partial)
{:ok,
%LangChain.PromptTemplate{
template: template.template,
input_variables: input_variables_without_partial,
partial_variables: partial_variables
}}
end
# defp serialize_prompt_message(prompt_message) do
# # Implement the serialization logic for the specific prompt message type here.
# # You might need to pattern match or use a separate function for each message type.
# end
end