Backend Swapping
If you want to do mixture of agents or something along those lines, you can dynamically swap between backends.Choices
Force the LLM to pick between multiple choices. Itβs implemented by implemented by computing the token-length normalized log probabilities of all choices and selecting the one with the highest probability (full credit goes to the sglang folks for this).Forking
Sometimes, you can reach a state in your program which is parallelizable.fork function.
Then, we can join them back into the parent ProgramState and continue generating from there.
Streaming
Enochian of course also supports streaming, so you can use it for a chat application frontend.Constrained decoding
For structured outputs, you can pass in either a regex, a JsonSchema, or a zod schema insideSamplingParams.
Zod types are the preferred way since it allows for type safety in the .get.
Tool Calling
You can provide the LLM some tools that it can optionally use per generation. You should use thecreateTools util which will allow for greater type inference when retrieving the result.
ToolUseResponse typed object:
createTools util is because the ToolUseResponse type cannot infer the literal
value of toolUsed and the response type without it. With the createTools util, hovering over
action in your IDE should show something like:
getWeather function and
enochian is able to infer that.
If you wanted to build a weather-getting agent, you could do a very simple βgame loopβ
like this:
Max Prompt Tokens
A downside of gradually building up conversation history is that your context length can grow much larger than youβd like. In applications where time to first token is critical, like copilot, itβs critical to stay under a certain threshold of tokens you are submitting to the LLM at each time. The.gen function provides a transform
callback that will take all the Messages added to the ProgramState previously, allow
you to edit the array and return a new trimmed down array that .gen will use.
maxPromptTokens threshold. Enochian provides a isUnderTokenThreshold(message: Message[], tokenThreshold: number, maxOutputTokens?: number)
util function to make this check really easy. The tokenThreshold is for the number of tokens in the modelβs context window you want to take up.
You can optionally provide a maxOutputTokens to reserve some number of tokens in the context window for your output tokens (since the number
of output tokens also counts towards your requests context length). If a maxOutputTokens is not passed in then zero tokens will be reserved for
the output and the model will generate until it hits the max length or an end-of-sequence token.
You can also provide metadata to .add and access that metadata inside the transform function to
trim tokens out that way.
transform presets for common patterns.
You can do ranking by embedding similarity:
Message and return the modified array. You can do trim from middle
on previous chat history, and then do embedding similarity ranking on the current
request:
Prefix Caching
Prefix caching is a common way to reduce computation needed for LLM requests and is also very relevant to the max prompt tokentransform function. If a common prefix
of a very frequently used prompt is cached (think long system prompts), you can skip
the entire computation of that prefix, effectively removing it from your max prompt
tokens restriction. For that reason, if you specify messages with the metadata
{ probablyPrefixCached: true }, then they will NOT be provided to the transform function.
Use this wisely!
You should probably always use
fromPrefixCache: true for system prompts and any
common prefixes in your prompts. Most providers automatically will do prefix caching
for you as its also advantageous for them to reduce the amount of computation they
have to do per request.