This document provides an explanation of the models ChainNode
and ChainEdge
, which are used to define a
Langchain processing chain for use in Ix.
A Langchain processing chain represents a sequence of actions or processes to be applied on a given input. This chain can involve various types of processes, including interactions with Language Models (LLMs).
In the context of Ix, chains are models using three entities: NodeType
, ChainNode
, and
ChainEdge
. These model define the types of components and structure of chains that use them.
Chains are stored in the database and can be loaded into a LangChain instance.
The graph model supports both defining arbitrary processing chains to run them, and also supports displaying and editing them visually in the user interface. The model supports asynchronicity, including parallel processing and delegation to other agents.
Ix provides custom features on top of LangChain that require custom integrations to load.
LLM Chains:
- LLMChain: wrapper around
langchain.LLMChain
to add Ix runtime. - LLMToolChain: chain that has
tools
available in the prompt. - LLMReply: Respond to chat stream with the result of an LLM prompt.
Artifacts:
- SaveArtifact: Save an artifact to the chat stream.
Routing / flow control:
- MapSubchain: Run subchain for each item in a list.
- ChooseTool: chain that chooses a tool with a subchain.
- Memory: Shared and local scoped memory for chains linked to Ix runtime.
- OpenAI Functions: OpenAI API functions for use in chains.
A NodeType
defines a LangChain component that may be used in a chain. Both property nodes and chain nodes are
supported. NodeType
instances describe the type of component, configuration options, and how they may be
connected to other nodes.
Note, that all LangChain components require a corresponding NodeType
. These definitions are required to provide
support for non-pydantic components including those that use a helper function or wrapper class for loading.
(e.g. Zapier LangChain component requires loading a client).
A ChainNode
represents a single node in the chain. Each node represents a processing step or operation that can be
applied. It stores the configuration necessary for the process, along with additional metadata like its name and
description.
A ChainNode
also maintains references to its parent Chain
allowing the chain to be traversed
and manipulated easily.
A ChainEdge
represents the connection or link between two ChainNode
instances in the chain. It defines the
source and target nodes of the connection, and contains additional information such as the root node of the chain it
belongs to, and an optional input map.
The ChainEdge
model is crucial for defining the structure and flow of the processing chain.
These models are used to dynamically create, modify, and traverse processing chains. Chains stored in the database may be queried and run.
# fetch chain, initialize task, and run chain
chain = Chain.objects.get(pk=CHAIN_ID)
task = fake_task(chain=chain)
callback_manager = IxCallbackManager(task)
langchain_chain = chain.load(callback_manager)
langchain_chain.run(user_input="Hello, world!")
Chains may be generated through the visual editor or a python code run as a management command or via shell_plus. JSON config import is not supported yet.
Here is a simple example of creating a chain that sends a greeting to the user. In this example, a simple chain that
greets the user is created. The chain consists of a single node that uses the hypothetical class GreetUserChain
to
send a greeting message to the user. The ChatOpenAI
language model
# Define the greeting operation
GREET_USER = {
"class_path": "ix.chains.llm.LLMChain",
"config": {
"llm": {
"class_path": "langchain.chat_models.openai.ChatOpenAI",
"config": {"request_timeout": 60, "temperature": 0.2, "verbose": True},
},
"messages": [
{
"role": "system",
"template": "Hello, User! How can I assist you today?",
}
],
},
}
# Create the chain
chain = Chain.objects.create(
pk=CHAIN_ID,
name="Greeting chain",
description="Chain used to greet the user",
)
# Create root node
root = ChainNode.objects.create_from_config(chain, GREET_USER, root=True)
In this next example, a chain with a sequence of actions is created. The chain consists of two nodes: one that uses
the previously defined GREET_USER
and another that asks the user for their name.
The ChainEdge
connecting the nodes is created automatically when adding a child node to an existing node. When
node_type
is set to "list", the ChainNode
will automatically create a ChainEdge
with the source_node
set to the parent node and the target_node
set to the newly created child node. The order of the child nodes is
determined by the order in which they are added, and recorded by the key
field of the ChainEdge
.
# Define the operation to ask the user's name
ASK_USER_NAME = {
"class_path": "ix.chains.llm.LLMChain",
"config": {
"llm": {
"class_path": "langchain.chat_models.openai.ChatOpenAI",
"config": {"request_timeout": 60, "temperature": 0.2, "verbose": True},
},
"messages": [
{
"role": "system",
"template": "What's your name?",
}
],
},
}
SEQUENCE = {
"class_path": "langchain.chains.SequentialChain",
"config": {
"chains": [
GREET_USER,
ASK_USER_NAME,
]
}
}
# Create the chain
chain = Chain.objects.create(
pk=CHAIN_ID,
name="Greeting and name asking chain",
description="Chain used to greet the user and ask their name",
)
# Create root node as a sequence
root = ChainNode.objects.create_from_config(chain, SEQUENCE, root=True)