Logs are essential to monitor and improve your LLM app in production. Literal AI provides flexible and composable SDKs to log your LLM app at different levels of granularity.
Literal AI approaches LLM logging at three levels:
Generation: Log of a single LLM call. (Generations are Steps.)
Step: Log of a regular function execution, which is usually an intermediate step in an LLM system. Possible types are: tool, embedding, retrieval, rerank, undefined, etc. Steps can be considered as Spans.
Run: Trace of an Agent/Chain run, including its intermediate steps. Can contain one or multiple generations.
Thread: A collection of Runs that are part of a single conversation.
You can log a generation only (typically for extraction use cases), or log a run only (typically for task automation), or combine them in threads (typically for chatbots).
See installation to get your API key and instantiate the SDK
Generations are logged by integrations with LLM providers. They capture the prompt, completion, settings, and token latency.Here is an example with OpenAI:
You can leverage multimodal capabilities on Literal AI in two ways:
Simple logging on API calls to Multimodal LLM APIs, like gpt-4o
Save multimodal files as Attachments. Image, videos, audio and other files are shown as Attachment in the Literal AI platform, which can be accessed and downloaded via a Step.
A Run represents a trace of an Agent or Chain execution, capturing all intermediate steps and actions.Runs can be logged manually using decorators or through framework integrations such as Llama Index or LangChain.
Here’s how you can log a Run with intermediate steps using Python and TypeScript:
@literalai_client.step(type="tool")def get_temperature(city: str): return "10C"@literalai_client.rundef my_agent(user_input: str): # Reusing the OpenAI call from the previous example call_openai(user_input) # Naive tool example get_temperature("paris") return "Success"my_agent("Hello world")# Wait for all steps to be sent. This is NOT needed in production code.literalai_client.flush()
async function getTemperature(city: string) { return literalAiClient .step({ type: 'tool', name: 'Get Temperature', input: { city }, }) .wrap(() => { return { content: '10C' }; });}async function myAgent(userInput: string) { return literalAiClient .run({ name: 'My Agent', input: { userInput } }) .wrap(async () => { // Reusing the OpenAI call from the previous example await callOpenAI(userInput); // Naive tool example await getTemperature("Paris"); return { content: 'Success' }; });}myAgent("Hello world");
You can attach files to a Run or any of its intermediate steps, which is particularly useful for multimodal use cases.
@literalai_client.step(type="tool")def load_document(): with open ("./some.pdf", "rb") as file: literalai_client.api.create_attachment( name="pdf_document", content=file.read() ) return "doc loaded"
You can interact with an example Thread in the platform here.It is up to the application to keep track of the thread ID and pass it to the Literal AI client.
Every run logged with the same thread ID will be part of the same conversation.Here is an example:
import uuiddef process_message(thread_id: str, user_input: str): with literalai_client.thread(thread_id=thread_id) as thread: # Reusing the Agent from the previous example my_agent(user_input)thread_id = str(uuid.uuid4())# Calling the agent a first timeprocess_message(thread_id=thread_id, user_input="foo")# Calling the agent a second time with the same thread IDprocess_message(thread_id=thread_id, user_input="bar")# Wait for all steps to be sent. This is NOT needed in production code.literalai_client.flush()
import { v4 as uuidv4 } from 'uuid';async function processMessage(threadId: string, userInput: string) { await literalAiClient .thread({ id: threadId}) .wrap(async () => { await myAgent(userInput); });}const threadId = uuidv4();// Calling the agent a first timeprocessMessage(threadId, "foo");// Calling the agent a second time with the same thread IDprocessMessage(threadId, "bar");
You can learn more about the Thread API in the following references:
You can bind a Thread to a User to track their activity: quite handy for chatbots and conversational AIs!Simply provide a unique User identifier, such as an email.
# If the user `john.doe@example.com` does not exist, it's automatically created.def process_message(thread_id: str, user_input: str): with literalai_client.thread(thread_id=thread_id, participant_id="john.doe@example.com") as thread: # Reusing the Agent from the previous example my_agent(user_input)
// If the user `john.doe@example.com` does not exist, it's automatically created.async function processMessage(threadId: string, userInput: string) { await literalAiClient .thread({ participantId: "john.doe@example.com", id: threadId}) .wrap(async () => { await myAgent(userInput); });}
You can create a User at any time with the create_user API.
If your User already exists, you may update its metadata with the update_user API.The Literal AI client method thread() takes a participant_id (participantId in TypeScript) argument which accepts any of:
User.id: the unique ID of your User — it’s a UUID
User.identifier: the unique identifier of your User — it can be an email, a username, etc.
Careful with collisions when letting users pick their own identifier!
Literal AI supports logging to different environments, which allows you to separate your development, staging, and production data: dev, staging, prod.
This is particularly useful for managing your LLM application lifecycle.
To specify an environment when initializing the LiteralClient, you can use the environment parameter:
Literal AI supports pairing your logs to a release, a release is a version of your deployed code to help you identify new issues and regressions.This is particularly useful for managing your LLM application once in production.
The value can be arbitrary, but we recommend Semantic Versioning, Calendar Versioning, or the Git commit SHA.To specify a release when initializing the LiteralClient, you can use the release parameter:
Scores allow you to evaluate the LLM system performance at three levels: LLM generations, Agent Runs and Conversation Threads.
Scores can be human generated (human feedback, like a thump up or down), or AI generated (hallucination evaluation for instance).They can be visualized on the dashboard charts and used as filters.