Python探针是阿里云可观测产品自研的Python语言的可观测采集探针,其基于OpenTelemetry标准实现了自动化埋点能力,支持追踪LLM应用程序。
背景信息
LLM(Large Language Model)应用是指基于大语言模型所开发的各种应用,大语言模型通过大量的数据和参数训练,能够回答类似人类自然语言的问题,因此在自然语言处理、文本生成和智能对话等领域有广泛应用。同时在大语言模型应用日益普及的今天,如何高效地提供推理服务成为一个重要挑战。传统的服务框架在处理并发请求时往往会遇到性能瓶颈、内存管理效率低下等问题。大语言模型推理服务框架(如vLLM等)正是为解决这些关键挑战而生。
由于LLM的输出结果往往很难准确预测,同时面临训练和生产效果可能出现偏差、数据分布漂移导致性能下降、数据质量不保鲜、依赖外部数据不可靠等若干因素的不可控情况,这往往会影响LLM应用与推理服务的整体表现,当模型输出质量下降时能够及时识别就显得非常重要。
ARMS支持对LLM应用通过Python探针自动埋点,将LLM应用接入ARMS后,您即可查看LLM应用的调用链视图,更直观地分析不同操作类型的输入输出、Token消耗等信息。更多信息,请参见LLM调用链分析。
ARMS支持的LLM (大语言模型)推理服务框架和应用框架,请参见ARMS 应用监控支持的 Python 组件和框架。
安装 Python 探针
根据LLM应用部署环境选择合适的安装方式:
通过 Python 探针启动应用
aliyun-instrument python llm_app.py
请将llm_app.py替换为实际应用,如果您暂时没有可接入的LLM应用,您也可以使用附录提供的应用Demo。
ARMS Python探针会根据您安装的依赖对您的应用类型进行自动识别:
如果您安装了以下依赖之一,应用将被识别为大语言模型应用:
openai
dashscope
llama_index
langchain
如果您安装了以下依赖之一,应用将被识别为大语言模型服务:
vllm
sglang
如果您需要强制指定Python应用的类型,您可以设置APSARA_APM_APP_TYPE环境变量,该环境变量的取值如下:
microservice:普通微服务应用
app:大语言模型应用
model:大语言模型服务
执行结果
约一分钟后,若Python应用出现在ARMS控制台的 页面中且有数据上报,则说明接入成功。
配置
输入/输出内容采集
默认值:True,默认开启采集。
关闭后的效果:用户query时,模型、工具、知识库的input/output等详情字段只采集字段大小,不采集字段内容。
当前生效插件:仅Dify支持此配置。
配置方式:设置环境变量OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=False
。
大模型应用拆分
默认值: False,默认不做应用拆分。
开启后的效果:上报的数据拆分到LLM子应用,每个大模型应用(如Dify Workflow/Agent/Chat App)对应一个ARMS应用。
当前生效插件:仅Dify支持此配置。
配置方式:设置环境变量PROFILER_GENAI_SPLITAPP_ENABLE=True
。
支持的地域:河源、新加坡。
内容长度采集限制
默认值:不设置,默认没有限制。
开启后的效果:限制上报的Span属性值的长度,超过指定字符长度的属性值将会被截断。
当前生效插件:该配置适用于所有支持 OpenTelemetry 的插件(如LangChain/DashScope/Dify等)。
配置方式:设置环境变量export OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT=<integer_value>
。请将 <integer_value>
替换为希望限制的字符长度大小整数值。
附录
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='今天天气好吗?')
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
在data目录下存放知识库文档(pdf、txt、doc等文本格式)。
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 = """
你是一个儿童通识类聊天机器人,你的任务是根据用户输入的问题,结合知识库中找到的最相关的内容,然后根据内容生成回答。注意不回答主观问题。
"""
# 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
您可以参考基于Dify构建网页定制化AI问答助手文档快速搭建一个Dify应用。