Integrate LLM applications or inference services with ARMS

Updated at:
Copy as MD

The Python agent is a self-developed observability data collection agent for Python applications from Alibaba Cloud Observability products. Built on the OpenTelemetry standard, it provides automated instrumentation and supports tracing for LLM applications.

Background information

An LLM (large language model) application refers to any application built using a large language model. Trained on massive datasets and parameters, LLMs can answer questions in natural language similar to humans. They are widely used in natural language processing, text generation, and intelligent dialogue systems. As LLM applications become increasingly common, delivering efficient inference services has become a major challenge. Traditional service frameworks often encounter performance bottlenecks and inefficient memory management when handling concurrent requests. LLM inference frameworks—such as vLLM—are designed specifically to address these critical challenges.

LLM outputs are often difficult to predict accurately. Additionally, factors such as discrepancies between training and production results, data distribution drift causing performance degradation, stale data quality, and unreliable external dependencies can negatively impact overall LLM application and inference service performance. Therefore, it is crucial to detect drops in model output quality as soon as they occur.

ARMS supports automatic instrumentation of LLM applications through the Python agent. After integrating your LLM application with ARMS, you can view its trace visualization and intuitively analyze input/output details and token consumption across different operation types. For more information, see LLM trace analysis.

For a list of LLM inference and application frameworks supported by ARMS, see Python components and frameworks supported by ARMS Application Monitoring.

Install the Python agent

Choose an installation method based on your LLM application deployment environment:

Start your application with the Python agent

aliyun-instrument python llm_app.py
Note
  • Replace llm_app.py with your actual application file. If you do not yet have an LLM application to integrate, you can use the demo application provided in the Appendix.

  • The ARMS Python agent automatically detects your application type based on installed dependencies:

    • If you install any of the following dependencies, your application is identified as an LLM application:

      • openai

      • dashscope

      • llama_index

      • langchain

    • If you install any of the following dependencies, your application is identified as an LLM service:

      • vllm

      • sglang

    • To force a specific application type, set the APSARA_APM_APP_TYPE environment variable. Valid values are:

      • microservice: standard microservice application

      • app: LLM application

      • model: LLM service

Execution result

About one minute after startup, if your Python application appears on the ARMS console under LLM Application Monitoring > Application List and reports data, the integration was successful.

Configuration

Input/output content collection

Default: True (enabled by default).

When disabled: When users submit queries, only the size of fields such as input/output from models, tools, and knowledge bases is collected—not their actual content.

Currently effective plugins: Only Dify and LangChain support this setting.

To configure: Set the environment variable OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=False.

Split LLM applications

Default: False (application splitting is disabled by default).

When enabled: Reported data is split into LLM sub-applications. Each LLM application—such as a Dify Workflow, Agent, or Chat App—appears as a separate ARMS application.

Currently effective plugins: Only Dify supports this setting.

To configure: Set the environment variable PROFILER_GENAI_SPLITAPP_ENABLE=True.

Supported regions: Heyuan, Singapore.

Message content length limit

Default: 8K characters.

When enabled: Limits the length of each message content field (such as input/output messages). Content exceeding the specified character limit is truncated.

Currently effective plugins: This setting applies to all OpenTelemetry-compatible plugins (such as LangChain, DashScope, Dify, etc.).

To configure: If your agent version is >= 1.8.3, set the environment variable OTEL_INSTRUMENTATION_GENAI_MESSAGE_CONTENT_MAX_LENGTH=<integer_value>, replacing <integer_value> with your desired character limit as an integer.

Span attribute value length limit

Default: Not set (no limit by default).

When enabled: Limits the length of reported Span attribute values (such as gen_ai.agent.description). Values exceeding the specified character limit are truncated.

Currently effective plugins: This setting applies to all OpenTelemetry-compatible plugins (such as LangChain, DashScope, Dify, etc.).

To configure: Set the environment variable OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT=<integer_value>, replacing <integer_value> with your desired character limit as an integer.

Compress Spans

Default: Not set (compression is disabled by default).

When enabled: Multiple identical Spans generated at the same level are compressed into one, retaining only key information.

Currently effective plugins: This setting applies to all OpenTelemetry-compatible plugins (such as LangChain, DashScope, Dify, etc.).

Configure this by setting the environment variable APSARA_APM_INSTRUMENTER_SPAN_COMPRESS_ENABLED

=true.

Appendix

OpenAI Demo

llm_app.py

import openai
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/")
def call_openai():
    client = openai.OpenAI(api_key="sk-xxx")
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": "Write a haiku."}],
        max_tokens=20,
    )
    return {"data": f"{response}"}
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)    

requirements.txt

fastapi
uvicorn
openai >= 1.0.0

DashScope Demo

llm_app.py

from http import HTTPStatus
import dashscope
from dashscope import Generation
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/")
def call_dashscope():
    dashscope.api_key = 'YOUR-DASHSCOPE-API-KEY'
    responses = Generation.call(model=Generation.Models.qwen_turbo,
                                prompt='How is the weather today?')
    resp = ""
    if responses.status_code == HTTPStatus.OK:
        resp = f"Result is: {responses.output}"
    else:
        resp = f"Failed request_id: {responses.request_id}, status_code: {responses.status_code}, code: {responses.code}, message: {responses.message}"
    return {"data": f"{resp}"}
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

requirements.txt

fastapi
uvicorn
dashscope >= 1.0.0

LlamaIndex Demo

Place knowledge base documents (in PDF, TXT, DOC, or other text formats) in the data folder.

llm_app.py

import time
from fastapi import FastAPI
import uvicorn
import aiohttp
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import StorageContext
from llama_index.embeddings.dashscope import DashScopeEmbedding
import chromadb
import dashscope
import os
from dotenv import load_dotenv
from llama_index.core.llms import ChatMessage
from llama_index.core import VectorStoreIndex, get_response_synthesizer
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.llms.dashscope import DashScope, DashScopeGenerationModels
import random
load_dotenv()
os.environ["DASHSCOPE_API_KEY"] = 'sk-xxxxxx'
dashscope.api_key = 'sk-xxxxxxx'
api_key = 'sk-xxxxxxxx'
llm = DashScope(model_name=DashScopeGenerationModels.QWEN_MAX,api_key=api_key)
# create client and a new collection
chroma_client = chromadb.EphemeralClient()
chroma_collection = chroma_client.create_collection("chapters")
# define embedding function
embed_model = DashScopeEmbedding(model_name="text-embedding-v1", api_key=api_key)
# load documents
filename_fn = lambda filename: {"file_name": filename}
# automatically sets the metadata of each document according to filename_fn
documents = SimpleDirectoryReader(
    "./data/", file_metadata=filename_fn
).load_data()
# set up ChromaVectorStore and load in data
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(
    documents, storage_context=storage_context, embed_model=embed_model
)
retriever = VectorIndexRetriever(
    index=index,
    similarity_top_k=4,
    verbose=True
)
# configure response synthesizer
response_synthesizer = get_response_synthesizer(llm=llm, response_mode="refine")
# assemble query engine
query_engine = RetrieverQueryEngine(
    retriever=retriever,
    response_synthesizer=response_synthesizer,
)
SYSTEM_PROMPT = """
You are a general-knowledge chatbot for children. Your task is to answer user questions by combining the most relevant content found in the knowledge base. Do not answer subjective questions.
"""
# Initialize the conversation with a system message
messages = [ChatMessage(role="system", content=SYSTEM_PROMPT)]
app = FastAPI()
async def fetch(question):
    url = "https://www.aliyun.com"
    call_url = os.environ.get("LLM_INFRA_URL")
    if call_url is None or call_url == "":
        call_url = url
    else:
        call_url = f"{call_url}?question={question}"
    print(call_url)
    async with aiohttp.ClientSession() as session:
        async with session.get(call_url) as response:
            print(f"GET Status: {response.status}")
            data = await response.text()
            print(f"GET Response JSON: {data}")
            return data
@app.get("/heatbeat")
def heatbeat():
    return {"msg", "ok"}
cnt = 0
@app.get("/query")
async def call(question: str = None):
    global cnt
    cnt += 1
    if cnt == 20:
        cnt = 0
        raise BaseException("query is over limit,20 ", 401)
    # Add user message to the conversation history
    message = ChatMessage(role="user", content=question)
    # Convert messages into a string
    message_string = f"{message.role}:{message.content}"
    search = await fetch(question)
    print(f"search:{search}")
    resp = query_engine.query(message_string)
    print(resp)
    return {"data": f"{resp}".encode('utf-8').decode('utf-8')}
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

requirements.txt

fastapi
uvicorn
numpy==1.23.5
llama-index==0.10.62
llama-index-core==0.10.28
llama-index-embeddings-dashscope==0.1.3
llama-index-llms-dashscope==0.1.2
llama-index-vector-stores-chroma==0.1.6
aiohttp

LangChain Demo

llm_app.py

from fastapi import FastAPI
from langchain.llms.fake import FakeListLLM
import uvicorn
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
app = FastAPI()
llm = FakeListLLM(responses=["I'll callback later.", "You 'console' them!"])
template = """Question: {question}
Answer: Let's think step by step."""
prompt = PromptTemplate(template=template, input_variables=["question"])
llm_chain = LLMChain(prompt=prompt, llm=llm)
question = "What NFL team won the Super Bowl in the year Justin Beiber was born?"
@app.get("/")
def call_langchain():
    res = llm_chain.run(question)
    return {"data": res}
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

requirements.txt

fastapi
uvicorn
langchain
langchain_community

Dify Demo

You can quickly build a Dify application by following the instructions in Build a customized web-based AI Q&A assistant using Dify.