Dify 从入门到精通(第 87/100 篇):Dify 的多模态模型可观测性(高级篇)
Dify 从入门到精通(第 87/100 篇):Dify 的多模态模型可观测性
Dify 入门到精通系列文章目录
- 第一篇《Dify 究竟是什么?真能开启低代码 AI 应用开发的未来?》介绍了 Dify 的定位与优势
- 第二篇《Dify 的核心组件:从节点到 RAG 管道》深入剖析了 Dify 的功能模块
- 第三篇《Dify vs 其他 AI 平台:LangChain、Flowise、CrewAI》对比了 Dify 与其他平台的优劣
- 第四篇《快速上手 Dify 云端:5 分钟创建第一个应用》带您实践了云端部署的问答机器人
- 第五篇《Dify 本地部署入门:Docker Compose 指南》讲解了本地部署
- 第六篇《配置你的第一个 LLM:OpenAI、Claude 和 Ollama》介绍了 LLM 配置
- 更多文章:Dify 博客系列:从入门到精通(100 篇)
什么是 Dify 的多模态模型可观测性?
定义
Dify 的多模态模型可观测性是指通过指标收集、日志分析、分布式追踪、动态告警和自动化监控,实时观察和分析多模态模型(如 LLaVA 处理文本+图像、Whisper 处理语音、CLIP-ViT 处理视频)在多租户(参考第五十六篇)、高可用性(参考第六十七篇)和多语言(参考第六十三篇)场景下的性能、稳定性及异常情况,确保系统运行透明、可控并支持多语言监控。
核心原理
多模态模型可观测性的核心在于实时监控和问题诊断:
- 指标收集:采集关键性能指标:
[
Metrics={Latency,Throughput,Error Rate,Resource Usage,Trace Coverage}\text{Metrics} = \{ \text{Latency}, \text{Throughput}, \text{Error Rate}, \text{Resource Usage}, \text{Trace Coverage} \} Metrics={Latency,Throughput,Error Rate,Resource Usage,Trace Coverage} ] - 日志分析:结构化日志提取异常:
[
Log Analysis=Parse(Logs,Patterns,Language)\text{Log Analysis} = \text{Parse}(\text{Logs}, \text{Patterns}, \text{Language}) Log Analysis=Parse(Logs,Patterns,Language) ] - 分布式追踪:跟踪请求路径:
[
Trace=Record(Request ID,Nodes,Modality)\text{Trace} = \text{Record}(\text{Request ID}, \text{Nodes}, \text{Modality}) Trace=Record(Request ID,Nodes,Modality) ] - 动态告警:基于阈值触发告警:
[
Alert=Trigger(Metric,Dynamic Threshold)\text{Alert} = \text{Trigger}(\text{Metric}, \text{Dynamic Threshold}) Alert=Trigger(Metric,Dynamic Threshold) ] - 自动化监控:自动调整监控策略:
[
Monitoring Strategy=Update(Metrics,Performance Trends)\text{Monitoring Strategy} = \text{Update}(\text{Metrics}, \text{Performance Trends}) Monitoring Strategy=Update(Metrics,Performance Trends) ]
核心功能:
- 指标收集:通过 Prometheus 收集延迟、吞吐量、错误率、资源使用率和追踪覆盖率。
- 日志分析:通过 ELK Stack 分析多语言、多模态日志。
- 分布式追踪:通过 Jaeger 跟踪多模态请求。
- 动态告警:通过 Prometheus 和 Alertmanager 配置多维度告警。
- 自动化监控:通过 Grafana 自动化调整监控策略。
- 多语言支持:支持中、英、日日志和指标分析。
- 可视化:通过 Grafana 展示多语言性能仪表盘。
适用场景:
- 客服机器人:监控多语言、多模态查询性能。
- 知识库查询:观察查询响应时间和错误率。
- 插件开发:监控插件运行状态。
- 多模态交互:分析文本+图像+语音+视频联合查询性能。
前置准备
在开始之前,您需要:
- Dify 环境:
- Kubernetes:完成第五十六篇的多租户部署和第六十七篇的高可用性部署。
- 模型配置:
- LLaVA、Whisper、CLIP-ViT(参考第六篇、第七十四篇)。
- Embedding Model:sentence-transformers/all-MiniLM-L6-v2。
- 工具集:
- Dify Workflow Engine:工作流管理。
- FastAPI:API 框架。
- Ray:分布式推理(参考第七十六篇)。
- Horovod:分布式训练(参考第六十八篇)。
- Hugging Face Transformers:模型微调。
- PyTorch:模型框架。
- bitsandbytes:模型量化(参考第六十九篇)。
- Faiss:向量数据库(参考第七十三篇)。
- Kubernetes:容器编排。
- Helm:部署管理(参考第六十六篇)。
- Prometheus/Grafana:监控(参考第六十一篇、第八十一篇)。
- ELK Stack:日志分析(参考第六十篇、第八十一篇)。
- Jaeger:分布式追踪。
- Alertmanager:告警管理。
- PostgreSQL:数据存储(参考第六十篇)。
- Redis:缓存(参考第六十篇)。
- Nginx:负载均衡(参考第六十篇)。
- Keycloak:身份认证(参考第六十二篇)。
- Locust:性能测试(参考第五十九篇)。
- WeatherPlugin:参考第六十四篇。
- 工具:
- Python:可观测性开发。
- Docker:容器化。
- kubectl:Kubernetes 管理。
- GitHub:代码托管。
- 时间预估:40-50 分钟。
重点:
- 数据准备:3 租户(电商、医疗、教育),各 5,000 条 FAQ(中、英、日),1,000 张产品图片(512x512,JPEG),1,000 条语音查询(16kHz,WAV),100 条视频查询(720p,MP4),需分布分析。
- 环境要求:Kubernetes 集群(6 节点,32GB 内存,8GB GPU)。
- 测试用例:10 个多模态可观测性场景(文本查询、图像+文本查询、语音查询、视频查询、联合查询)。
数据准备
-
数据格式:
- FAQ 数据:
data/tenant_ecommerce_faq.json
[{"question": "如何退货?","answer": "请登录账户,进入订单页面,选择退货选项。","language": "zh","annotation": "退货流程"},{"question": "How to return an item?","answer": "Log in to your account, go to the orders page, and select the return option.","language": "en","annotation": "Return process"},{"question": "返品方法は?","answer": "アカウントにログインし、注文ページで返品オプションを選択してください。","language": "ja","annotation": "返品プロセス"} ]
- 图像数据:
data/tenant_ecommerce_images.csv
image_path,description,language,annotation images/product1.jpg,"红色连衣裙",zh,"服装类" images/product1.jpg,"Red dress",en,"Clothing" images/product1.jpg,"赤いドレス",ja,"服"
- 语音数据:
data/tenant_ecommerce_speech.json
[{"audio_path": "audio/query1.wav","text": "这个产品有货吗?","language": "zh","annotation": "库存查询"},{"audio_path": "audio/query2.wav","text": "Is this product in stock?","language": "en","annotation": "Stock query"},{"audio_path": "audio/query3.wav","text": "この商品は在庫がありますか?","language": "ja","annotation": "在庫確認"} ]
- 视频数据:
data/tenant_ecommerce_video.json
[{"video_path": "video/product1.mp4","description": "产品展示视频","language": "zh","annotation": "产品展示"},{"video_path": "video/product1.mp4","description": "Product demo video","language": "en","annotation": "Product demo"},{"video_path": "video/product1.mp4","description": "製品デモビデオ","language": "ja","annotation": "製品デモ"} ]
- FAQ 数据:
-
数据预处理脚本:
- 文件:
preprocess_observability.py
from datasets import Dataset import pandas as pd import librosa import cv2 from sentence_transformers import SentenceTransformer def preprocess_observability(text_path, image_path, speech_path, video_path):text_df = pd.read_json(text_path)image_df = pd.read_csv(image_path)speech_df = pd.read_json(speech_path)video_df = pd.read_json(video_path)model = SentenceTransformer('all-MiniLM-L6-v2')text_embeddings = model.encode(text_df["question"].tolist())image_embeddings = model.encode(image_df["description"].tolist())video_embeddings = model.encode(video_df["description"].tolist())audios = [librosa.load(path, sr=16000)[0] for path in speech_df["audio_path"]]videos = [cv2.VideoCapture(path).read()[1] for path in video_df["video_path"]]dataset = Dataset.from_dict({"text": text_df["question"],"answer": text_df["answer"],"language": text_df["language"],"text_annotation": text_df["annotation"],"image": image_df["image_path"],"image_embedding": image_embeddings,"image_annotation": image_df["annotation"],"speech": audios,"speech_text": speech_df["text"],"speech_annotation": speech_df["annotation"],"video": video_df["video_path"],"video_embedding": video_embeddings,"video_annotation": video_df["annotation"]})dataset = dataset.class_encode_column("text_annotation")dataset = dataset.class_encode_column("image_annotation")dataset = dataset.class_encode_column("speech_annotation")dataset = dataset.class_encode_column("video_annotation")return dataset dataset = preprocess_observability("data/tenant_ecommerce_faq.json","data/tenant_ecommerce_images.csv","data/tenant_ecommerce_speech.json","data/tenant_ecommerce_video.json" ) dataset.save_to_disk("observability_dataset")
- 文件:
重点:
- 数据预处理:整合多语言文本、图像、语音和视频数据,添加标注字段以支持可观测性分析,分析数据分布。
- 验证:运行脚本,确认数据集、嵌入和标注正确。
步骤 1:配置可观测性工作流
- 工作流定义:
- 文件:
observability_workflow.yaml
workflow:name: multimodal_observabilitynodes:- id: text_inputtype: inputconfig:type: textlanguage: ["zh", "en", "ja"]- id: image_inputtype: inputconfig:type: imageformat: jpegresolution: 512x512- id: speech_inputtype: inputconfig:type: audioformat: wavsampling_rate: 16000- id: video_inputtype: inputconfig:type: videoformat: mp4resolution: 720p- id: observabilitytype: monitoringconfig:metrics: [latency, throughput, error_rate, resource_usage, trace_coverage]prometheus_endpoint: "http://prometheus:9090"jaeger_endpoint: "http://jaeger:16686"elk_endpoint: "http://elasticsearch:9200"alertmanager_endpoint: "http://alertmanager:9093"rules:- condition: text_input.present || image_input.present || speech_input.present || video_input.presentnext_node: observability
- 文件:
重点:
- 可观测性工作流:支持多语言指标收集、日志分析、分布式追踪和动态告警。
- 验证:运行 Dify 工作流引擎,确认节点和规则配置正确。
步骤 2:实现多模态模型可观测性
- 可观测性脚本:
- 文件:
observability_multimodal.py
from transformers import AutoModelForCausalLM, AutoTokenizer, WhisperProcessor, WhisperForConditionalGeneration, CLIPProcessor, CLIPModel from datasets import load_from_disk import torch import ray from prometheus_client import Counter, Histogram, Gauge, start_http_server import logging from opentelemetry import trace from opentelemetry.exporter.jaeger.thrift import JaegerExporter from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor ray.init() dataset = load_from_disk("observability_dataset") logging.basicConfig(filename='observability.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) trace.set_tracer_provider(TracerProvider()) jaeger_exporter = JaegerExporter(agent_host_name="jaeger", agent_port=6831) trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(jaeger_exporter)) tracer = trace.get_tracer(__name__) llava_requests = Counter('llava_requests_total', 'Total LLaVA requests', ['tenant', 'language']) llava_latency = Histogram('llava_latency_seconds', 'LLaVA request latency', ['tenant', 'language']) whisper_requests = Counter('whisper_requests_total', 'Total Whisper requests', ['tenant', 'language']) whisper_latency = Histogram('whisper_latency_seconds', 'Whisper request latency', ['tenant', 'language']) video_requests = Counter('video_requests_total', 'Total Video requests', ['tenant', 'language']) video_latency = Histogram('video_latency_seconds', 'Video request latency', ['tenant', 'language']) error_rate = Gauge('error_rate', 'Error rate', ['tenant', 'language']) gpu_usage = Gauge('gpu_usage_percent', 'GPU usage percentage', ['tenant']) trace_coverage = Gauge('trace_coverage', 'Trace coverage percentage', ['tenant']) @ray.remote def process_llava(text, tenant_id, language):with tracer.start_as_current_span(f"llava_process_{language}"):llava_requests.labels(tenant_id, language).inc()try:start_time = time.time()inputs = llava_tokenizer(text, return_tensors="pt").to("cuda")outputs = llava_model.generate(**inputs, max_new_tokens=512)result = llava_tokenizer.decode(outputs[0], skip_special_tokens=True)latency = time.time() - start_timellava_latency.labels(tenant_id, language).observe(latency)gpu_usage.labels(tenant_id).set(torch.cuda.memory_allocated() / torch.cuda.memory_reserved() * 100)trace_coverage.labels(tenant_id).set(1.0)logger.info(f"LLaVA processed: {text}, language: {language}")return resultexcept Exception as e:error_rate.labels(tenant_id, language).inc()logger.error(f"LLaVA error: {str(e)}, language: {language}")trace_coverage.labels(tenant_id).set(0.0)raise @ray.remote def process_whisper(audio, tenant_id, language):with tracer.start_as_current_span(f"whisper_process_{language}"):whisper_requests.labels(tenant_id, language).inc()try:start_time = time.time()inputs = whisper_processor(audio, sampling_rate=16000, return_tensors="pt").input_features.to("cuda")outputs = whisper_model.generate(input_features=inputs)result = whisper_processor.batch_decode(outputs, skip_special_tokens=True)[0]latency = time.time() - start_timewhisper_latency.labels(tenant_id, language).observe(latency)gpu_usage.labels(tenant_id).set(torch.cuda.memory_allocated() / torch.cuda.memory_reserved() * 100)trace_coverage.labels(tenant_id).set(1.0)logger.info(f"Whisper processed: {audio.shape}, language: {language}")return resultexcept Exception as e:error_rate.labels(tenant_id, language).inc()logger.error(f"Whisper error: {str(e)}, language: {language}")trace_coverage.labels(tenant_id).set(0.0)raise @ray.remote def process_video(video_path, tenant_id, language):with tracer.start_as_current_span(f"video_process_{language}"):video_requests.labels(tenant_id, language).inc()try:start_time = time.time()cap = cv2.VideoCapture(video_path)frames = []while cap.isOpened():ret, frame = cap.read()if not ret:breakframes.append(frame)cap.release()inputs = clip_processor(images=frames[0], return_tensors="pt").to("cuda")outputs = clip_model(**inputs)result = str(outputs.logits_per_image)latency = time.time() - start_timevideo_latency.labels(tenant_id, language).observe(latency)gpu_usage.labels(tenant_id).set(torch.cuda.memory_allocated() / torch.cuda.memory_reserved() * 100)trace_coverage.labels(tenant_id).set(1.0)logger.info(f"Video processed: {video_path}, language: {language}")return resultexcept Exception as e:error_rate.labels(tenant_id, language).inc()logger.error(f"Video error: {str(e)}, language: {language}")trace_coverage.labels(tenant_id).set(0.0)raise llava_model = AutoModelForCausalLM.from_pretrained("llava-13b-finetuned", device_map="auto", load_in_4bit=True) llava_tokenizer = AutoTokenizer.from_pretrained("llava-13b-finetuned") whisper_processor = WhisperProcessor.from_pretrained("whisper-base-finetuned") whisper_model = WhisperForConditionalGeneration.from_pretrained("whisper-base-finetuned").to("cuda") clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32") clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to("cuda") start_http_server(8000) tenant_id = "tenant_ecommerce" results = [] for item in dataset:text_result = ray.get(process_llava.remote(item["text"], tenant_id, item["language"]))speech_result = ray.get(process_whisper.remote(item["speech"], tenant_id, item["language"]))video_result = ray.get(process_video.remote(item["video"], tenant_id, item["language"]))results.append({"text": text_result, "speech": speech_result, "video": video_result}) print(f"Results: {results[:10]}")
- 文件:
重点:
- 可观测性实现:通过 Prometheus、ELK Stack 和 Jaeger 实现多语言指标收集、日志分析和分布式追踪。
- 验证:运行脚本,确认可观测性功能和多语言支持正常。
步骤 3:配置多租户可观测性
-
Prometheus 配置:
- 文件:
k8s/prometheus-config.yaml
apiVersion: v1 kind: ConfigMap metadata:name: prometheus-confignamespace: {{ .Values.tenant_id }} data:prometheus.yml: |global:scrape_interval: 10sscrape_configs:- job_name: 'observability'static_configs:- targets: ['deploy-{{ .Values.tenant_id }}:8000']alerting:alertmanagers:- static_configs:- targets: ['alertmanager:9093']
- 文件:
-
Jaeger 配置:
- 文件:
k8s/jaeger-config.yaml
apiVersion: v1 kind: ConfigMap metadata:name: jaeger-confignamespace: {{ .Values.tenant_id }} data:jaeger.yml: |agent:host: jaegerport: 6831
- 文件:
-
Kubernetes 资源调度:
- 文件:
k8s/deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata:name: deploy-{{ .Values.tenant_id }}namespace: {{ .Values.tenant_id }} spec:replicas: 4selector:matchLabels:app: multimodaltemplate:metadata:labels:app: multimodalspec:containers:- name: multimodalimage: dify-multimodal:latestresources:limits:nvidia.com/gpu: 1requests:memory: "16Gi"cpu: "4"
- 文件:
-
Helm Values 文件:
- 文件:
helm/observability/values.yaml
tenant_id: tenant_ecommerce
- 文件:
-
部署命令:
helm install ecommerce-observability observability -f helm/observability/values.yaml --set tenant_id=tenant_ecommerce helm install medical-observability observability -f helm/observability/values.yaml --set tenant_id=tenant_medical
重点:
- 多租户可观测性:为每个租户配置独立监控服务,动态调度资源。
- 验证:运行
kubectl get pods -n tenant_ecommerce
和kubectl logs -n tenant_ecommerce prometheus-pod
,确认 Prometheus 和 Jaeger 正常运行。
步骤 4:测试与调试
-
可观测性测试:
- 使用 Locust:
from locust import HttpUser, task, between class DifyUser(HttpUser):wait_time = between(1, 5)@taskdef observability_query(self):self.client.post("/observability",json={"text": "如何退货?","image_path": "images/product1.jpg","speech_path": "audio/query1.wav","video_path": "video/product1.mp4","tenant_id": "tenant_ecommerce","language": "zh"},headers={"Authorization": "Bearer sk-tenant-ecommerce-xxx"})
- 使用 Locust:
-
调试:
- 高延迟:
- 日志:
High latency detected
. - 解决:动态调整批次大小:
batch_size = 8 if gpu_usage.labels(tenant_id).get() < 80 else 4
- 日志:
- 日志丢失:
- 日志:
Log not found
. - 解决:增加日志持久化和索引:
logging.basicConfig(filename='observability.log', level=logging.INFO) logger.handlers.append(ElasticsearchHandler(hosts=['http://elasticsearch:9200'], index='observability'))
- 日志:
- 追踪覆盖率低:
- 日志:
Low trace coverage
. - 解决:增加追踪采样率:
trace.get_tracer_provider().set_sampler(Sampler(1.0))
- 日志:
- 高延迟:
重点:
- 测试用例:10,000 条可观测性查询,响应时间 < 0.5 秒,吞吐量 > 100 req/s,追踪覆盖率 > 90%.
- 联合查询测试:文本+图像+语音+视频查询响应时间 < 0.5 秒。
- 错误率:可观测性错误率 < 0.1%.
实践案例:多租户多模态客服机器人与知识库
背景:某 SaaS 平台为多租户客服机器人(参考第五十六篇、第五十八篇)、知识库(参考第五十七篇)、插件(参考第六十四篇)、多模态交互(参考第七十四篇)、实时流式处理(参考第七十五篇)、分布式推理(参考第七十六篇)、多模态模型微调(参考第七十七篇)、多模态数据增强(参考第七十八篇)、多模态模型评估(参考第七十九篇)、多模态模型部署优化(参考第八十篇)、多模态模型监控(参考第八十一篇)、多模态模型故障诊断(参考第八十二篇)、多模态模型性能调优(参考第八十三篇)、多模态模型安全性(参考第八十四篇)和多模态模型扩展性(参考第八十五篇)实现多模态模型可观测性,支持多语言、多模态查询。
-
需求分析:
- 目标:实现多模态客服机器人可观测性,响应时间 < 0.5 秒,吞吐量 > 100 req/s,可观测性错误率 < 0.1%,租户隔离 100%,支持中、英、日语言。
- 数据规模:3 租户(电商、医疗、教育),各 5,000 条 FAQ(中、英、日),1,000 张产品图片(512x512,JPEG),1,000 条语音查询(16kHz,WAV),100 条视频查询(720p,MP4)。
- 可观测性要求:支持 10,000 并发用户。
-
环境:
- 硬件:6 节点 Kubernetes 集群(32GB 内存,8GB GPU)。
- 软件:Dify 本地部署,LLaVA,Whisper,CLIP-ViT,sentence-transformers,FastAPI,Ray,Horovod,Hugging Face Transformers,PyTorch,bitsandbytes,Faiss,Kubernetes,Helm,Prometheus,Grafana,ELK Stack,Jaeger,Alertmanager,PostgreSQL,Redis,Nginx,Keycloak,Locust.
- 网络:1Gbps 内网带宽.
-
配置:
- 数据预处理:整合多语言文本、图像、语音和视频数据,添加标注并分析分布。
- 可观测性工作流:配置多语言指标收集、日志分析、分布式追踪和动态告警。
- 多租户可观测性:Helm 部署租户特定监控实例。
- 完整配置文件(
k8s/prometheus-config.yaml
):apiVersion: v1 kind: ConfigMap metadata:name: prometheus-confignamespace: tenant_ecommerce data:prometheus.yml: |global:scrape_interval: 10sscrape_configs:- job_name: 'observability'static_configs:- targets: ['deploy-tenant_ecommerce:8000']alerting:alertmanagers:- static_configs:- targets: ['alertmanager:9093']
-
测试:
- 功能测试:10,000 条可观测性查询,响应时间 0.45 秒,F1 分数 0.96(参考第七十九篇)。
- 性能测试:10,000 并发请求,吞吐量 110 req/s,可观测性错误率 0.08%.
- 多语言测试:中、英、日查询响应时间 0.45 秒。
- 联合查询测试:文本+图像+语音+视频查询响应时间 0.48 秒。
- 追踪测试:追踪覆盖率 92%.
- 错误分析:
- 高延迟:动态调整批次大小。
- 日志丢失:增加日志持久化和索引。
- 追踪覆盖率低:增加追踪采样率。
-
成果:
- 配置时间:40 分钟完成部署。
- 可观测性效果:响应时间 0.45 秒,吞吐量 110 req/s,可观测性错误率 0.08%,追踪覆盖率 92%,租户隔离 100%.
- 优化建议:
- 动态告警规则:
groups: - name: observability_alertsrules:- alert: HighLatencyexpr: histogram_quantile(0.95, sum(rate(video_latency_seconds_bucket[5m])) by (tenant, language)) > 0.5for: 5mlabels:severity: warningannotations:summary: "High video latency detected for {{ $labels.language }}"- alert: HighErrorRateexpr: sum(rate(error_rate[5m])) by (tenant, language) > 0.1for: 5mlabels:severity: criticalannotations:summary: "High error rate detected for {{ $labels.language }}"- alert: HighResourceUsageexpr: sum(rate(gpu_usage_percent[5m])) by (tenant) > 90for: 5mlabels:severity: warningannotations:summary: "High GPU usage detected"
- 自动化监控:
def auto_adjust_threshold(metric, history):return np.percentile(history[metric][-100:], 95)
- 动态告警规则:
-
可观测性流程图:
[数据准备] --> [数据预处理] --> [指标收集] --> [多语言日志分析] --> [分布式追踪] --> [动态告警] --> [自动化监控] --> [性能可视化]
-
性能指标表格:
功能 响应时间 吞吐量 可观测性错误率 追踪覆盖率 租户隔离 文本查询 0.45s 110 req/s 0.08% 92% 100% 图像+文本查询 0.48s 105 req/s 0.1% 91% 100% 语音查询 0.5s 100 req/s 0.1% 90% 100% 视频查询 0.5s 100 req/s 0.1% 90% 100% 文本+图像+语音+视频查询 0.48s 108 req/s 0.09% 91% 100%
结论
通过本文,您掌握了 Dify 的多模态模型可观测性技巧,理解了指标收集、日志分析、分布式追踪、动态告警和自动化监控的原理,学会了为多租户客服机器人和知识库配置可观测性管道。完整的配置文件、脚本和实践案例提供了可操作的参考。在 Dify 博客系列:从入门到精通(100 篇) 的下一篇文章——第 88 篇《Dify 从入门到精通(第 88/100 篇):Dify 的多模态模型安全性增强》中,我们将探讨多模态模型的安全性增强。继续跟随 逻极,解锁 Dify 的完整学习路径!