Basics#
As Langworks builds on top of Pypeworks, many of Pypeworks’ basic concepts are also relevant to Langworks. To this foundation Langworks adds several new concepts specific to working with LLMs.
Query#
A Query
is a specialised Pypeworks Node providing an interface to a LLM. Instead of
taking a function, a query takes a prompt:
Query(
query = "Explain like I am five what the Python library Langworks is used for."
)
Unlike a typical prompt, these prompts are templatable using Langwork’s built-in template language, based on the Jinja template language. This allows for access to any arguments passed to the query:
Query(
query = "Explain like I am five what {{ input }} is used for."
)
In addition a query may provide guidance on how the LLM should handle the prompt, using Langwork’s built-in dynamic template language:
Query(
query = (
"What weighs more: {{ input[0] }} or {{ input[1] }}? Think step-by-step before stating"
" your final answer, either '{{ input[0] }}' or '{{ input[1] }}', delimited by triple"
" asterisks (i.e. ***{{ input[0] }}*** or ***{{ input[1] }}***)."
),
guidance = (
'Let\'s think step-by-step'
'{% gen params = Params(stop = ["***"], include_stop = True) %}'
'{% choice [input[0], input[1]], params = Params(temperature = 0)}'
'***'
)
)
Input passed by argument may be complemented by input passed by context. To do so, assign a lookup table to the query’s context argument, as you would typically do in Jinja:
Query(
query = (
"Tell me more about {{input}} in relation to {{topic}}."
),
context = dict(
topic = "humans"
)
)
Messages and history#
Interaction with a conversational LLM may consist of a back and forth of messages. Sometimes it is
desirable to prefill such conversation to steer further interaction, i.e. to define a system prompt,
or to restore an earlier conversation. This may be done by assigning a
langworks.messages.Thread
to Query’s history argument:
Query(
history = [
{
"content": "You are a helpful assistant.",
"role": "system"
}
]
)
Langwork#
Just as a Query
may be likened to a Pypeworks Node, a Langwork
may be
likened to a pipework. In fact, a langwork may be used just like a pipework, composing nodes and
queries into a directed acyclic graph. Herein nodes and queries may be seamlessly intertwined:
langwork = (
Langwork(
# Nodes / queries
selector = Query(
query = "What is most well-known {{input}}?"
),
wikifier = Query(
query = (
"Can you give me a brief Wikipedia-like article in Markdown describing the"
" {{input}} of your choice?"
)
),
extractor = Node(
lambda *args, context = {}, history = []: history[-1]
)
# Connections
connections = [
Connection("enter" , "selector"),
Connection("selector" , "wikifier"),
Connection("wikifier" , "extractor"),
Connection("extractor" , "exit")
]
)
)
for(cat in ["dog", "cat"]):
print(langwork(cat))
Langworks expands upon this by providing various utilities to ease working with LLMs. Langworks may enforce common prompt histories and template contexts, as well as specify a common middleware to interface with LLMs.
Middleware#
Middleware abstracts away the code needed to interface with specific LLM providers. Within Langworks
all middleware must satisfy a common interface, making it ease to interchange LLM providers. In
fact, as both Query
and Langwork
provide hooks for middleware, LLM providers
may be easily mixed:
from langworks.middleware.vllm import (
SamplingParams,
vLLM
)
langwork = (
Langwork(
# Config
middleware = vLLM(
url = "http://127.0.0.1:4000/v1",
model = "meta-llama/Meta-Llama-3-8B-Instruct",
params = SamplingParams(temperature = 0.3)
),
# Queries
gen_plan = Query(
query = "Give a step-by-step explanation how {{input}} may be implemented."
# Uses middleware attached to langwork.
),
extract_plan = Node(
lambda *args, context = {}, history = []: history[-1]
),
gen_python = Query(
query = (
"Write me a function in Python that implements the computation detailed below:"
"\n\n"
"{{input}}"
),
# Uses a different middleware to access a model specialised in code generation.
middleware = vLLM(
url = "http://127.0.0.1:4001/v1",
model = "mistralai/Codestral-22B-v0.1",
params = SamplingParams(temperature = 0.3)
)
),
extract_code = Node(
lambda *args, context = {}, history = []: history[-1]
),
# Connections
connections = [
Connection("enter" , "gen_plan"),
Connection("gen_plan" , "extract_plan"),
Connection("extract_plan" , "gen_python"),
Connection("gen_python" , "extract_code"),
Connection("extract_code" , "exit")
]
)
)
for(challenge in ["quick sort", "A* path finding"]):
print(langwork(challenge))