三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

基于LLM的微服务日志智能诊断:从原理到工程实践

基于LLM的微服务日志智能诊断:从原理到工程实践

1. 从“日志海洋”到“智能诊断”:为什么我们需要LLM?

在微服务架构下工作过几年的工程师,对下面这个场景一定不陌生:凌晨三点,监控告警响了,某个核心服务的错误率突然飙升。你睡眼惺忪地打开日志平台,输入服务名和时间范围,按下回车。瞬间,屏幕上涌出几十万甚至上百万条日志,它们来自十几个不同的服务实例,夹杂着INFO、WARN、ERROR各种级别,像一场突如其来的数据海啸。你需要在其中找到那条“病根”日志,它可能隐藏在某个不起眼的角落,前后关联着五六条其他服务的调用链日志。手动筛选、模式匹配、上下文关联……这个过程不仅耗时,更考验工程师的经验、耐心,甚至是一点运气。我们管这叫“日志考古”,而大多数时候,它效率低下且容易出错。

这就是传统日志分析工具的瓶颈:它们擅长收集、存储、检索(基于关键词或简单规则),但不擅长“理解”。一条日志“ERROR: Failed to connect to database: Connection timed out”对机器来说只是一串字符,但对有经验的工程师来说,它能立刻联想到网络问题、数据库负载过高、连接池配置错误、防火墙规则变更等一系列可能性。这种“理解”和“关联”的能力,正是人类专家与冰冷工具之间的鸿沟。

而大型语言模型的出现,让我们看到了填平这道鸿沟的可能。LLM(Large Language Model)本质上是一个经过海量文本训练的、拥有强大语义理解和生成能力的模型。它不仅能“读懂”日志在说什么(语义理解),还能根据已有的知识(训练数据中包含的故障模式、系统知识)进行推理和关联(逻辑推理)。我们不再需要编写复杂的正则表达式去匹配所有可能的错误信息变体,也不再需要手动维护一个庞大的“错误码-解决方案”知识库。我们可以尝试让LLM扮演那个“有经验的SRE专家”,让它来阅读、分析、归纳海量日志,并直接给出根因推测和解决建议。

这个想法听起来很美好,但把它做成一个稳定、可靠、能在生产环境落地的“微服务日志智能诊断系统”,中间隔着十万八千里。这绝不是简单地把日志扔给ChatGPT的API然后等待奇迹发生。它涉及日志的预处理、上下文构建、提示工程、成本控制、结果验证等一系列复杂工程问题。在之前的系列文章中,我们已经搭建了日志收集、存储、索引和基础分析流水线。今天,我们就来深入第九个核心环节:如何真正地让LLM成为日志分析专家,而不仅仅是一个昂贵的文本补全工具。

2. 核心挑战:为什么直接调用LLM API会“翻车”?

在兴奋地开始编码之前,我们必须先冷静下来,识别出将LLM应用于日志分析场景时,会遇到的几个核心挑战。盲目开始,大概率会得到不靠谱、成本高、速度慢的结果。

2.1 挑战一:上下文长度限制与信息过载

所有主流的LLM API(如GPT-4、Claude、文心一言等)都有严格的上下文窗口限制。比如,GPT-4 Turbo的上下文窗口是128K tokens,听起来很大,但一条微服务日志可能包含时间戳、服务名、实例ID、线程号、日志级别、类路径、消息体、堆栈跟踪、MDC(映射诊断上下文)字段等等。一次严重的故障,在几分钟内产生几十万条日志是常态。即使经过筛选,只保留ERROR级别的日志,其文本量也远超任何LLM单次处理的极限。

更糟糕的是,LLM的计费通常与输入输出的tokens数量强相关。把海量原始日志直接塞给API,不仅在技术上不可行,在经济上也是灾难。因此,如何从海量日志中提炼出最相关、最精简的“分析摘要”输入给LLM,是第一个必须解决的工程问题。

2.2 挑战二:日志的非结构化与噪声

日志本质上是半结构化或非结构化的文本。虽然我们鼓励使用结构化日志(如JSON格式),但现实中大量遗留系统、第三方库输出的日志仍然是自由文本格式,格式不一,包含大量调试信息、变量插值内容。例如:

2024-05-27 10:15:30.123 INFO [user-service-5d8f7] com.example.UserController - Processing request for user id: 12345, clientIp: 10.0.1.100 2024-05-27 10:15:30.456 ERROR [order-service-a2b3c] com.example.OrderService - Failed to update inventory for sku ABC-123. Exception: java.sql.SQLTransientConnectionException: Connection is not available, request timed out after 30000ms.

LLM需要从这些杂乱的信息中,识别出实体(如服务名order-service、错误类型SQLTransientConnectionException、资源sku ABC-123)、提取关键事件(“更新库存失败”),并忽略无关的噪声(如INFO级别的请求处理日志)。这要求我们在喂给LLM之前,必须进行有效的日志解析、关键信息抽取和噪声过滤

2.3 挑战三:提示工程的质量决定输出上限

“Garbage in, garbage out” 在LLM场景下尤为突出。你给LLM的指令(即Prompt)质量,直接决定了它分析结果的质量。一个模糊的Prompt如“分析这些日志,告诉我哪里错了”,LLM可能会给你一个笼统的、基于最常见错误的回答,或者干脆开始胡言乱语。

我们需要设计一个高度结构化、角色明确、任务清晰的Prompt。这个Prompt需要:

  1. 为LLM定义明确的角色:例如,“你是一个拥有10年经验的分布式系统SRE专家,擅长从复杂的微服务日志中定位根因故障。”
  2. 明确输入数据的格式和含义:告诉LLM每条日志字段代表什么(时间戳、服务、级别、消息)。
  3. 规定具体的分析步骤和输出格式:例如,“请按以下步骤分析:a) 总结关键错误事件链;b) 推断最可能的根本原因;c) 提供具体的排查建议或修复步骤。请以JSON格式输出。”
  4. 提供少量示例(Few-shot Learning):给出一两个“输入日志片段”和“理想输出分析”的例子,让LLM更好地理解我们的期望。

2.4 挑战四:结果的可靠性、可解释性与成本

LLM可能会“幻觉”(Hallucinate),即生成看似合理但完全错误或没有根据的分析。在运维场景下,一个错误的根因指向可能导致团队浪费数小时排查错误的方向。因此,系统不能完全信任LLM的单一输出,必须将其结果视为“高置信度的假设”,需要与其他监控数据(指标、链路追踪)进行交叉验证,并提供其推理过程的引用(例如,指出分析结论是基于哪几条具体日志得出的),以增强可解释性。

同时,每次调用LLM API都产生费用。我们需要设计策略,在保证分析效果的前提下,尽可能减少不必要的调用,例如,只有当日志中出现特定级别的错误模式,或错误数量超过阈值时,才触发LLM深度分析。

3. 系统架构设计:构建LLM日志分析流水线

基于以上挑战,我们不能设计一个“日志输入,分析结果输出”的黑盒。我们需要一个精心设计的流水线,将原始日志逐步加工成适合LLM消化、并能产出可靠结果的“营养餐”。下图勾勒了这个智能诊断核心模块的架构:

注:此处用文字描述架构图,实际系统中可用绘图工具绘制) 整个流水线可以分为四个主要阶段:

  1. 触发与采集层:由日志Agent和流处理平台(如Flink)构成,实时监控日志流,当检测到预设的错误模式或阈值(如5分钟内同一服务ERROR日志>100条)时,触发诊断流程,并收集相关时间窗口内的关联日志(可能涉及上下游服务)。
  2. 预处理与浓缩层:这是降低成本和提升效果的关键。收集到的原始日志会经过:a)解析:将非结构化日志解析为结构化字段;b)过滤:移除无关的DEBUG/INFO日志,保留WARN、ERROR及关键业务日志;c)聚类:将相似或相同的错误日志合并(去重),并统计频次;d)摘要:利用更小、更快的模型(或启发式规则)生成一段简短的日志摘要,描述“在什么时间、什么服务、发生了什么类型的问题”。
  3. LLM推理层:将预处理后的“浓缩日志摘要”和“关键原始日志片段”作为输入,结合精心设计的Prompt,调用LLM API(或部署的私有模型)进行分析。这一层需要处理API调用、重试、限流、以及将LLM的自然语言输出解析为结构化数据(如JSON)。
  4. 后处理与反馈层:对LLM输出的结果进行格式化、丰富(例如,附加上相关监控图表链接、知识库条目),并推送到告警平台或协作工具(如钉钉、飞书、Jira)。同时,非常重要的一步是建立人工反馈闭环,允许工程师对诊断结果进行“正确”、“部分正确”、“错误”的标注,这些反馈数据将用于持续优化Prompt和预处理规则。

4. 实战:从零构建一个诊断Prompt与处理模块

理论说再多,不如一行代码。让我们聚焦最核心的LLM推理层,看看如何用代码实现一个基本的诊断模块。我们假设使用OpenAI的GPT-4 API,并已经拥有了预处理后的日志数据。

4.1 步骤一:设计一个强大的系统Prompt

Prompt是我们的“魔法咒语”,设计好坏天差地别。下面是一个经过多次迭代的、相对成熟的Prompt示例:

SYSTEM_PROMPT = """你是一个资深的微服务系统SRE专家,拥有超过10年的线上故障排查经验。你的任务是分析提供的微服务集群日志,精准定位故障的根本原因,并提供可操作的修复建议。 **分析原则:** 1. **关联性优先**:重点关注错误在服务间的传播链。例如,A服务的超时是否导致了B服务的失败。 2. **时间线推理**:严格遵循日志时间戳,构建事件发生的先后顺序。 3. **根因推断**:区分表面现象和根本原因。例如,“数据库连接失败”是现象,根本原因可能是“网络分区”、“数据库过载”或“连接池配置错误”。 4. **保守与精准**:基于日志证据说话。没有明确证据时,提出可能性并注明是推测。 **输入数据格式说明:** 你将收到一个JSON数组,每个元素是一条日志,包含以下字段: - `timestamp`: 日志时间 (ISO 8601格式) - `service`: 产生日志的服务名称 - `level`: 日志级别 (ERROR, WARN, INFO) - `message`: 日志主要内容 - `exception`: 异常堆栈信息(如果有) **你的输出必须是严格的JSON格式,包含以下字段:** { “summary”: “一段简短的中文总结,描述发生了什么故障。”, “timeline”: [“按时间顺序排列的关键事件点列表”], “root_cause_analysis”: { “most_likely”: “最可能的根本原因,需详细解释,并引用相关日志证据(如‘根据服务A在[时间]的日志:...’)。”, “other_possibilities”: [“其他可能性较小的原因列表”] }, “action_items”: [ { “action”: “具体的操作建议”, “target”: “针对的服务或组件”, “priority”: “HIGH/MEDIUM/LOW” } ] } 现在,开始分析以下日志: """

这个Prompt明确了角色、输入、输出格式和分析原则,为LLM提供了清晰的“工作说明书”。

4.2 步骤二:实现日志预处理与上下文构建

我们不能把成百上千条日志直接塞进Prompt。需要先进行浓缩。

import json from datetime import datetime, timedelta from collections import Counter import re class LogPreprocessor: def __init__(self, time_window_minutes=10): self.time_window = timedelta(minutes=time_window_minutes) def preprocess_for_llm(self, raw_logs): """ 将原始日志列表预处理为适合LLM分析的浓缩上下文。 :param raw_logs: List of dict, 原始日志数据 :return: dict,包含摘要和关键日志 """ if not raw_logs: return None # 1. 按时间排序 sorted_logs = sorted(raw_logs, key=lambda x: x['timestamp']) start_time = sorted_logs[0]['timestamp'] end_time = sorted_logs[-1]['timestamp'] # 2. 提取ERROR和WARN日志作为关键事件 critical_logs = [log for log in sorted_logs if log['level'] in ['ERROR', 'WARN']] # 3. 对错误信息进行简单聚类(按错误消息的前50个字符近似聚类) error_messages = [log['message'] for log in critical_logs if log['level'] == 'ERROR'] # 简单的基于关键词的聚类(生产环境可用更复杂的算法如TF-IDF + 聚类) error_counter = Counter() for msg in error_messages: # 提取关键错误类型,例如“Connection timed out”, “NullPointerException” simplified_msg = self._extract_error_keyword(msg) error_counter[simplified_msg] += 1 # 4. 生成自然语言摘要 summary = self._generate_summary(start_time, end_time, critical_logs, error_counter) # 5. 选择最具代表性的日志样本(每个错误类型选最早、最晚和一条典型) sample_logs = self._select_sample_logs(critical_logs, error_counter) return { “analysis_window”: f“{start_time} 至 {end_time}”, “summary_for_llm”: summary, “sample_critical_logs”: sample_logs[:15], # 限制样本数量,控制token “raw_log_count”: len(raw_logs), “critical_log_count”: len(critical_logs) } def _extract_error_keyword(self, message): # 简单示例:提取异常类名或最后一个冒号后的内容 match = re.search(r‘Exception:\s*(\w+)’, message) if match: return match.group(1) # 或者提取常见错误短语 for phrase in [“timeout”, “failed to connect”, “null pointer”, “out of memory”]: if phrase in message.lower(): return phrase return message[:50] + “...” # 截断作为兜底 def _generate_summary(self, start, end, critical_logs, error_counter): service_set = {log[‘service’] for log in critical_logs} summary = f“在{start}到{end}期间,共发现{len(critical_logs)}条WARN/ERROR级别日志,涉及服务:{‘, ’.join(service_set)}。主要错误类型包括:” for err, count in error_counter.most_common(3): summary += f“ ‘{err}’({count}次);” return summary def _select_sample_logs(self, logs, error_counter): # 简化实现:返回时间上最早、最晚以及中间几条ERROR日志 error_logs = [log for log in logs if log[‘level’] == ‘ERROR’] if not error_logs: return logs[:5] samples = [] if error_logs: samples.append(error_logs[0]) # 最早错误 samples.append(error_logs[-1]) # 最新错误 if len(error_logs) > 2: samples.append(error_logs[len(error_logs)//2]) # 中间一个错误 return samples

4.3 步骤三:集成LLM API调用与结果解析

import openai from typing import Dict, Any import json class LLMLogAnalyzer: def __init__(self, api_key, model=“gpt-4-turbo-preview”): openai.api_key = api_key self.model = model self.system_prompt = SYSTEM_PROMPT # 即上面定义的系统Prompt async def analyze_logs(self, preprocessed_data: Dict[str, Any]) -> Dict[str, Any]: """ 调用LLM进行日志分析。 """ if not preprocessed_data: return {“error”: “No preprocessed data provided”} # 构建用户消息(即实际的日志数据) user_message_content = { “analysis_window”: preprocessed_data[“analysis_window”], “summary”: preprocessed_data[“summary_for_llm”], “sample_logs”: preprocessed_data[“sample_critical_logs”] } user_message = json.dumps(user_message_content, ensure_ascii=False, indent=2) try: response = await openai.ChatCompletion.acreate( model=self.model, messages=[ {“role”: “system”, “content”: self.system_prompt}, {“role”: “user”, “content”: user_message} ], temperature=0.1, # 低温度,确保输出稳定、确定性高 max_tokens=1500, # 限制输出长度 response_format={“type”: “json_object”} # 强制要求JSON输出,仅部分模型支持 ) result_text = response.choices[0].message.content # 解析JSON结果 result = json.loads(result_text) # 附加上下文信息 result[“_meta”] = { “model_used”: self.model, “preprocessing_summary”: preprocessed_data[“summary_for_llm”] } return result except json.JSONDecodeError as e: return {“error”: f“Failed to parse LLM response as JSON: {e}”, “raw_response”: result_text} except Exception as e: return {“error”: f“LLM API call failed: {e}”}

4.4 步骤四:一个完整的诊断流程示例

假设我们收到了来自order-servicepayment-service的一批日志。

# 模拟的原始日志数据 raw_logs_example = [ {“timestamp”: “2024-05-27T10:15:30.456Z”, “service”: “order-service”, “level”: “ERROR”, “message”: “Failed to update inventory for sku ABC-123. Exception: java.sql.SQLTransientConnectionException: Connection is not available, request timed out after 30000ms.”, “exception”: “full stack trace here...”}, {“timestamp”: “2024-05-27T10:15:31.100Z”, “service”: “order-service”, “level”: “ERROR”, “message”: “Payment processing failed for order 789. Could not call payment-service.”, “exception”: null}, {“timestamp”: “2024-05-27T10:15:25.123Z”, “service”: “payment-service”, “level”: “WARN”, “message”: “High latency detected on database shard-2. Avg query time > 2s”, “exception”: null}, {“timestamp”: “2024-05-27T10:16:00.000Z”, “service”: “payment-service”, “level”: “ERROR”, “message”: “Database connection pool exhausted. Unable to acquire connection for 60s.”, “exception”: “...”}, ] # 1. 预处理 preprocessor = LogPreprocessor() preprocessed_data = preprocessor.preprocess_for_llm(raw_logs_example) print(“预处理摘要:”, preprocessed_data[“summary_for_llm”]) # 输出可能类似:在2024-05-27T10:15:25.123Z到2024-05-27T10:16:00.000Z期间,共发现4条WARN/ERROR级别日志,涉及服务:order-service, payment-service。主要错误类型包括: ‘SQLTransientConnectionException’(1次); ‘Database connection pool exhausted’(1次); # 2. 调用LLM分析 analyzer = LLMLogAnalyzer(api_key=“your-api-key”) analysis_result = await analyzer.analyze_logs(preprocessed_data) print(json.dumps(analysis_result, indent=2, ensure_ascii=False))

LLM可能返回的JSON结果示例:

{ “summary”: “故障始于支付服务数据库延迟升高,导致数据库连接池被耗尽,进而引发订单服务更新库存时数据库连接超时,最终导致订单支付流程失败。”, “timeline”: [ “10:15:25 - payment-service 报告数据库分片2高延迟(>2s)。”, “10:16:00 - payment-service 数据库连接池被耗尽,持续60秒无法获取连接。”, “10:15:30 - order-service 更新库存时发生数据库连接超时异常(SQLTransientConnectionException)。”, “10:15:31 - order-service 因无法调用支付服务,导致支付处理失败。” ], “root_cause_analysis”: { “most_likely”: “根本原因很可能是支付服务(payment-service)所依赖的数据库分片(shard-2)出现性能瓶颈或资源竞争,导致查询延迟飙升。高延迟使得数据库连接持有时间变长,最终耗尽了应用层的数据库连接池(见payment-service在10:16:00的日志)。连接池耗尽后,order-service尝试获取数据库连接时发生超时,引发后续连锁故障。”, “other_possibilities”: [ “数据库服务器本身负载过高或存在死锁。”, “网络在特定时间段出现波动,导致数据库响应变慢。” ] }, “action_items”: [ { “action”: “立即检查 payment-service 数据库分片2的监控指标(CPU、内存、IO、慢查询日志)。”, “target”: “payment-service 数据库 (shard-2)”, “priority”: “HIGH” }, { “action”: “临时扩容 payment-service 的数据库连接池最大连接数,作为应急缓解措施。”, “target”: “payment-service 应用配置”, “priority”: “MEDIUM” }, { “action”: “审查 order-service 中关于支付服务调用的熔断和降级策略是否生效。”, “target”: “order-service 熔断配置”, “priority”: “MEDIUM” } ] }

这个结果已经具备了很高的可操作性,为值班工程师提供了清晰的排查方向。

5. 避坑指南与生产环境考量

将上述Demo代码用于生产环境,还有很长的路要走。以下是我在实际落地过程中踩过的坑和总结的经验。

5.1 成本控制:如何避免“账单爆炸”

LLM API调用是按Token计费的,无节制地使用会让成本失控。

  • 策略1:分级触发。不要所有错误都调用LLM。定义清晰的触发规则,例如:① 同一错误模式在5分钟内出现超过10次;② 出现CRITICAL或特定关键词(如“OutOfMemoryError”)的日志;③ 多个关联服务同时报错(通过简单的服务依赖图判断)。
  • 策略2:日志采样与摘要前置。如我们前面所做,预处理环节必须大幅压缩输入Token数量。除了聚类和采样,还可以尝试用更小的、专门训练的模型(如Sentence-BERT)对日志进行向量化,然后只选取与核心错误语义最相关的少数日志。
  • 策略3:缓存分析结果。对于相同的错误模式(可通过日志指纹算法识别),可以缓存之前的LLM分析结果一段时间(例如1小时),在此期间内重复出现的相同错误直接返回缓存结果,无需再次调用API。
  • 策略4:考虑私有化部署模型。对于日志量巨大的企业,长期使用商用API成本可能很高。可以考虑在内部GPU集群上部署较小的开源模型(如Llama 3、Qwen等),专门针对日志分析任务进行微调(Fine-tuning)。虽然初期投入大,但长期来看单次推理成本极低,且数据不出域,安全性更高。

5.2 效果优化:提升诊断准确率

  • Prompt的持续迭代:将LLM分析视为一个产品,Prompt就是它的核心逻辑。需要建立A/B测试机制,对比不同Prompt版本的分析结果,并收集人工反馈(“这个诊断是否正确?”)来持续优化Prompt。可以将表现好的Prompt版本进行归档和管理。
  • 引入领域知识:在Prompt或输入上下文中,加入你们系统的特定知识,比如服务依赖关系图、常见的故障模式手册、已知的数据库配置项等。这能极大地提升LLM在你们具体环境下的推理能力。例如,可以在系统Prompt里加一句:“请注意,inventory-db是一个Redis集群,user-db是一个MySQL主从。”
  • 融合多源数据:不要只给LLM看日志。将相关的关键性能指标(如对应数据库的CPU使用率、网络延迟)、分布式链路追踪(Trace)中的关键跨度(Span)信息,也浓缩后作为上下文提供给LLM。多模态的信息能让它的判断更全面。例如:“在payment-service报错期间,其数据库的CPU使用率从40%飙升到95%。”
  • 结果置信度与人工审核:LLM的输出应附带一个“置信度”评分(可以通过让LLM自我评估,或者用多个模型投票等方式实现)。对于低置信度的分析,系统应自动转为“待人工审核”状态,并通知工程师,而不是直接作为结论发出。

5.3 工程化与可靠性

  • 异步与限流:诊断服务应该是异步的、非阻塞的。接收到触发信号后,将诊断任务放入消息队列(如RabbitMQ、Kafka),由后台Worker消费处理。必须对调用LLM API的速率进行严格限流,防止意外流量打爆API配额或导致巨额账单。
  • 完善的错误处理与降级:LLM服务可能不稳定(API超时、限流、内部错误)。代码必须有重试机制、断路器模式(Circuit Breaker)。当LLM服务不可用时,系统应能优雅降级,例如回退到基于规则库的简单诊断,或仅提供聚类后的原始日志,并标记“智能诊断暂不可用”。
  • 可观测性:这个智能诊断系统本身也需要被监控。记录每次诊断的耗时、消耗的Token数、触发原因、LLM返回结果的置信度、以及后续的人工反馈。这些数据是优化系统、证明其价值(ROI)的关键。

6. 超越诊断:LLM在可观测性领域的更多想象

当我们构建好这个基础的日志诊断引擎后,会发现LLM的能力边界还可以继续拓展。

  • 自动生成故障报告:在诊断完成后,可以进一步让LLM根据分析结果、涉及的监控图表链接,生成一份结构清晰的故障复盘报告初稿,包含故障时间线、影响范围、根因、行动项和后续改进建议,极大减轻工程师的文书工作。
  • 智能告警摘要:对接告警平台,当同时爆发大量相关告警时(俗称“告警风暴”),让LLM快速阅读这些告警标题和内容,生成一个统一的摘要,告诉值班工程师“核心问题是什么”,避免在几十条告警中迷失。
  • 运维知识库的自动问答:将内部的运维手册、故障复盘文档、系统架构图说明等知识库内容向量化。当工程师遇到新问题时,可以直接用自然语言提问(例如:“上次payment-service数据库连接池耗尽是怎么解决的?”),系统利用检索增强生成技术,从知识库中找到相关内容,并让LLM生成简洁的答案。
  • 日志规范检查与优化建议:让LLM扮演“代码评审员”,扫描新提交的日志打印语句,指出哪些日志信息不足(例如,异常日志没有打印关键业务ID)、哪些是多余的调试日志、以及是否符合团队的日志规范,从源头提升日志质量。

让LLM成为日志分析专家,不是一个一蹴而就的魔法开关,而是一个需要精心设计流水线、持续迭代Prompt、严谨评估效果的系统工程。它不能替代工程师的深度思考和系统知识,但可以成为一个强大的“副驾驶”,帮我们从繁琐、重复的日志“考古”工作中解放出来,更快地锁定问题,更准地判断方向。这个过程本身,也是对我们自身如何理解系统、如何定义问题的一次深刻反思。开始动手搭建你的第一个原型吧,从一小段日志、一个清晰的Prompt开始,你会亲眼看到“智能”是如何从数据中浮现出来的。

← 返回列表