Packages
langchain
0.8.12
0.9.2
0.9.1
0.9.0
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.4.0-rc.3
0.4.0-rc.2
0.4.0-rc.1
0.4.0-rc.0
0.3.3
0.3.2
0.3.1
0.3.0
0.3.0-rc.2
0.3.0-rc.1
0.3.0-rc.0
0.2.0
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Elixir implementation of a LangChain style framework that lets Elixir projects integrate with and leverage LLMs.
Current section
Files
Jump to
Current section
Files
lib/chains/data_extraction_chain.ex
defmodule LangChain.Chains.DataExtractionChain do
@moduledoc """
Defines an LLMChain for performing data extraction from a body of text.
Provide the schema for desired information to be parsed into. It is treated as
though there are 0 to many instances of the data structure being described so
information is returned as an array.
The result is always a list. If the LLM returns a single map instead of an
array, it is automatically wrapped in a list so callers can rely on a
consistent return type.
Originally based on:
- https://github.com/langchain-ai/langchainjs/blob/main/langchain/src/chains/openai_functions/extraction.ts#L43
## Example
# JSONSchema definition of data we want to capture or extract.
schema_parameters = %{
type: "object",
properties: %{
person_name: %{type: "string"},
person_age: %{type: "number"},
person_hair_color: %{type: "string"},
dog_name: %{type: "string"},
dog_breed: %{type: "string"}
},
required: []
}
# Model setup
{:ok, chat} = ChatOpenAI.new(%{temperature: 0})
# run the chain on the text information
data_prompt =
"Alex is 5 feet tall. Claudia is 4 feet taller than Alex and jumps higher than him.
Claudia is a brunette and Alex is blonde. Alex's dog Frosty is a labrador and likes to play hide and seek."
{:ok, result} = LangChain.Chains.DataExtractionChain.run(chat, schema_parameters, data_prompt)
# Example result
[
%{
"dog_breed" => "labrador",
"dog_name" => "Frosty",
"person_age" => nil,
"person_hair_color" => "blonde",
"person_name" => "Alex"
},
%{
"dog_breed" => nil,
"dog_name" => nil,
"person_age" => nil,
"person_hair_color" => "brunette",
"person_name" => "Claudia"
}
]
If the LLM returns a single map (e.g. when only one entity is found), it is
wrapped in a list automatically:
# Single-entity result normalised to a list
[
%{
"person_name" => "Alex",
"person_age" => nil,
...
}
]
The `schema_parameters` in the previous example can also be expressed using a
list of `LangChain.FunctionParam` structs. An equivalent version looks like
this:
alias LangChain.FunctionParam
schema_parameters = [
FunctionParam.new!(%{name: "person_name", type: :string}),
FunctionParam.new!(%{name: "person_age", type: :number}),
FunctionParam.new!(%{name: "person_hair_color", type: :string}),
FunctionParam.new!(%{name: "dog_name", type: :string}),
FunctionParam.new!(%{name: "dog_breed", type: :string})
]
|> FunctionParam.to_parameters_schema()
"""
use Ecto.Schema
require Logger
alias LangChain.PromptTemplate
alias LangChain.Message
alias LangChain.Message.ToolCall
alias LangChain.LangChainError
alias LangChain.Chains.LLMChain
alias LangChain.ChatModels.ChatOpenAI
@function_name "information_extraction"
@extraction_template ~s"Extract and save the relevant entities mentioned in the following passage together with their properties. Use the value `null` when missing in the passage.
Passage:
<%= @input %>"
@doc """
Coerces the extraction tool's `info` argument to a list of rows.
Models sometimes return one JSON object instead of a one-element array; `run/4`
uses this so callers always get `{:ok, list}`.
"""
@spec normalize_extraction_info(term()) :: {:ok, [any()]} | {:error, LangChainError.t()}
def normalize_extraction_info(info) when is_list(info), do: {:ok, info}
def normalize_extraction_info(info) when is_map(info), do: {:ok, [info]}
def normalize_extraction_info(other) do
{:error,
LangChainError.exception("Extracted data must be a list or map, got: #{inspect(other)}")}
end
@doc """
Run the data extraction chain.
"""
@spec run(ChatOpenAI.t(), json_schema :: map(), prompt :: [any()], opts :: Keyword.t()) ::
{:ok, result :: [any()]} | {:error, LangChainError.t()}
def run(llm, json_schema, prompt, opts \\ []) do
verbose = Keyword.get(opts, :verbose, false)
try do
messages =
[
Message.new_system!(
"You are a helpful assistant that extracts structured data from text passages. Only use the functions you have been provided with. Extract the data in a single tool use."
),
PromptTemplate.new!(%{role: :user, text: @extraction_template})
]
|> PromptTemplate.to_messages!(%{input: prompt})
{:ok, chain} = LLMChain.new(%{llm: llm, verbose: verbose})
chain
|> LLMChain.add_tools(build_extract_function(json_schema))
|> LLMChain.add_messages(messages)
|> LLMChain.run()
|> case do
{:ok,
%LLMChain{
last_message: %Message{
role: :assistant,
tool_calls: [
%ToolCall{
name: @function_name,
arguments: %{"info" => info}
}
]
}
}} ->
normalize_extraction_info(info)
other ->
{:error, LangChainError.exception("Unexpected response. #{inspect(other)}")}
end
rescue
exception ->
Logger.warning(
"Caught unexpected exception in DataExtractionChain. Error: #{inspect(exception)}"
)
{:error,
LangChainError.exception(
"Unexpected error in DataExtractionChain. Check logs for details."
)}
end
end
@doc """
Build the function to expose to the LLM that can be called for data
extraction.
"""
@spec build_extract_function(json_schema :: map()) :: LangChain.Function.t() | no_return()
def build_extract_function(json_schema) do
LangChain.Function.new!(%{
name: @function_name,
description: "Extracts the relevant information from the passage.",
function: fn args, _context ->
# NOTE: The function is not executed here because we won't be returning
# this to the LLM. The LLMChain does not run the function, but stops at
# the request for it.
{:ok, args}
end,
parameters_schema: %{
type: "object",
properties: %{
info: %{
type: "array",
items: json_schema
}
},
required: ["info"]
}
})
end
end