一、相得益彰
在人工智能領域,我們常常遇到兩個核心挑戰:如何讓模型獲取最新知識,以及如何讓模型基於特定信息生成準確答案。RAG(Retrieval-Augmented Generation:檢索增強生成) 提供了一種解決這些挑戰的範式,而 LangChain 則提供了實現這一範式的完整工具箱。二者的結合,就像RAG給了建築師既有了設計藍圖,而LangChain又有了全套現代化工具,讓構建智能應用變得前所未有的高效和可靠。
本文將深入探討如何利用 LangChain 框架實現 RAG 架構,通過具體代碼示例展示如何構建一個能夠理解特定領域知識的智能問答系統。
二、什麼是LangChain
LangChain 是一個用於構建大模型應用的開發框架。它本身不是 RAG,但它提供了實現 RAG 所需的所有組件。
LangChain 將構建LLM應用時常見的功能都模塊化了,比如連接各種數據源、文本處理、調用模型、管理對話歷史等。開發者可以像搭積木一樣,將這些模塊組合起來,快速構建應用。
結合《構建AI智能體:十七、大模型的幻覺難題:RAG 解決AI才華橫溢卻胡言亂語的弊病》中介紹的“開卷”的比喻,要實現“開卷考試”(RAG)這個流程,你需要很多工具:找書(文檔加載)、翻書和找重點(文本分割與檢索)、把找到的重點段落和問題寫在草稿紙上(提示模板)、最後讓學生答題(調用模型)。
LangChain 就是提供了所有這些工具的超強工具箱:它給你提供了各種“找書”的方法(Document Loaders)、“翻書”的技巧(Text Splitters 和 Vectorstores)、“寫草稿紙”的模板(Prompt Templates)以及“組織考試流程”的説明書(Chains)。
三、為什麼需要LangChain
想象一下我們使用過程中的一個痛點,大語言模型是一個超級聰明但生活不能自理的大腦。它知識淵博,但它不知道你的文件、公司的數據庫;它一次只能回答一個問題,記不住之前的對話;它不會用工具,比如上網查資料或者按計算器;你想讓它幫你分析公司財報、當個24小時客服、或者幫你訂機票,直接告訴它去做是行不通的。你需要為它打理好一切。
當我們一個幫助手冊或職工手冊的PDF文檔,一般我們需要什麼信息都需要自己去逐頁逐行的定位到精準的段落才能找到答案,而現在有了LangChain,它能夠自動的按照自己的方式和邏輯快速精準的分析和答覆問題:
- 檢索:去你之前上傳的員工手冊PDF裏搜索“年假”關鍵詞。
- 組裝:把找到的相關段落和你的問題組合成一個新的、更詳細的提示。
- 提問:把這個組裝好的提示發給AI“大腦”。
- 回覆:把AI的回答清晰地返回給你。
四、LangChain是怎麼去做的
LangChain 可以理解為是一個用於開發由語言模型驅動的應用程序的框架。其核心價值在於提供了模塊化和可組合性。可以將 LangChain 想象成一副“樂高積木”,它提供了各種標準化組件(如模型交互、數據檢索、記憶管理、工作流編排),讓你可以通過組合這些組件,像搭積木一樣快速、靈活地構建起復雜的LLM應用,而無需從零開始處理各種底層細節。就像大模型的私人助理。它會把各種工具組合起來,搭建一個完整的流水線,讓大模型能真正幹活;
- 數據連接:喂資料,把你的PPT、PDF、網站文章等各種文件,拆解、整理、打包好,等大模型需要時立刻遞上去。
- 記憶管理:記筆記,把和用户的聊天記錄記下來,下次聊天時提醒大模型:“這位用户上次説他叫小明,喜歡咖啡。”
- 工具調用:遞工具,當大模型説“我需要算個數”或者“我需要查一下今天的新聞”時,助理立刻把計算器或者搜索引擎遞過去。
- 工作流編排:安排工作,把一個複雜任務拆成幾步,比如先讓“A模型”總結文章,再讓“B模型”把總結翻譯成法語。
LangChain 是一個工具箱和腳手架,它能把強大的AI大腦(LLM)和你自己的數據、工具、業務流程連接起來,拼裝成一個真正能用的、智能的應用程序。
五、LangChain 的核心模塊
要深入瞭解LangChain,首先需要了解它的核心模塊:
- Models(模型):這是與各種LLM交互的抽象。LangChain 支持多種模型提供商(OpenAI, Hugging Face等),可以用統一的接口調用它們。
ChatModels:用於聊天模式,輸入輸出是消息列表。
LLMs:用於補全模式,輸入輸出是字符串。 - Prompts(提示):管理、優化和模板化提示詞的組件。PromptTemplate 允許你創建帶有變量的模板,動態注入內容。如"請將以下文本翻譯成{language}:{text}"
- Indexes(索引):讓模型能夠與外部數據連接和交互,這是克服模型知識截止問題的關鍵。主要包括:
Document Loaders:從各種源(PDF, 網頁, Notion)加載文檔。
Text Splitters:將長文檔分割成模型上下文窗口能處理的小塊。
VectorStores:將文本塊轉換為向量(Embeddings)並存儲,以便快速進行相似性搜索。
Retrievers:從 VectorStores 中檢索與問題相關的文檔片段。 - Chains(鏈):將多個組件“鏈”在一起,形成一個序列化的工作流。這是 LangChain 的核心。一個鏈可以包含一個提示模板、一個模型和一個輸出解析器。
LLMChain 是最基本的鏈。 RetrievalQA 是一個高級鏈,它集成了檢索器和問答鏈。 - Agents(代理):鏈是預先定義好的固定流程,而代理則讓模型自己決定使用哪些工具、以什麼順序使用,來完成用户指令。代理相當於模型的“大腦”,工具則是它的“手腳”。
工具:可以是搜索引擎、計算器、數據庫查詢等任何函數。
- Memory(記憶):用於在鏈或代理的多次調用之間持久化狀態,如對話歷史。這對於聊天應用至關重要。
六、LangChain中的Chain Type
通俗的講,LangChain如果是一個功能強大的自動化汽車製造工廠,chain_type 就像是工廠裏的特定車輛組裝藍圖或流水線,是特定 Chain 的配置參數,參考以下代碼中的load_qa_chain方法中配置的chain_type;
from langchain.prompts import PromptTemplate
custom_prompt = PromptTemplate(
template="""請嚴格根據以下上下文來回答問題。如果上下文沒有提供足夠信息,請直接説"根據已知信息無法回答該問題",不要編造答案。
上下文:
{context}
問題:{question}
答案:""",
input_variables=["context", "question"]
)
chain = load_qa_chain(llm, chain_type="stuff", prompt=custom_prompt)
在 load_qa_chain 中, chain_type 參數決定了 RAG 流程中“增強”這一步的具體工作方式。即:如何將檢索到的多個相關文檔塊(context)與用户的問題(question)組合起來,發送給大語言模型(LLM)以生成最終答案。不同的 chain_type 在效果、成本和速度上有着顯著的權衡。
四種主要的 Chain Type 詳解:
假設我們針對問題 “LangChain 有哪些核心組件?” 檢索到了 4 個相關的文檔塊(chunks)。
1. stuff(堆疊)
- 工作方式:這是最簡單的方法。它將所有檢索到的文檔塊簡單地“堆疊”在一起,組合成一個巨大的提示(prompt),然後一次性發送給 LLM。Prompt 結構: 請根據以下信息回答問題:{context_chunk_1} ... {context_chunk_4} 問題:{question}
- 優點:
單次調用:只調用 LLM 一次,速度快。
最大上下文:LLM 可以同時看到所有信息,理論上能做出最綜合、最連貫的回答。
- 缺點:
上下文長度限制:如果檢索到的文檔塊很多或很長,很容易超過 LLM 的上下文窗口(context window)限制,導致報錯或信息被截斷。
- 適用場景:當檢索到的文檔總篇幅較短,並且確信不會超出所用 LLM 的上下文窗口時。這是最常用且默認的選項。
2. map_reduce(映射歸納)
- 工作方式:該方法分為兩個階段:
1. Map(映射):將每個文檔塊分別與問題組合,發送給 LLM,要求 LLM 針對該塊本身提取答案。這會進行 N 次(文檔塊的數量)LLM 調用。Prompt 結構(每次): 根據以下片段回答問題:{context_chunk_i} 問題:{question}
2. Reduce(歸納):將所有來自 Map 階段的答案(可能還有原始問題)再次組合,發送給 LLM,要求它將這些分散的答案歸納成一個最終、連貫的答案。這是第 N+1 次 LLM 調用。
- 優點:
突破上下文限制:可以處理任意數量的文檔,因為每個文檔都是獨立處理的。
- 缺點:
高成本/高延遲:需要進行多次 LLM 調用(N+1 次),成本更高,速度更慢。
可能丟失全局視角:在 Map 階段,LLM 無法看到不同文檔塊之間的關聯,可能導致歸納階段融合困難。
- 適用場景:當需要處理大量文檔,並且 stuff 方法不適用時。
3. refine(迭代細化)
- 工作方式:這是一個迭代、序列化的過程。
首先,將第一個文檔塊和問題發送給 LLM,得到一個初始答案。
然後,將下一個文檔塊、當前已有的答案和問題再次發送給 LLM,要求它根據新信息修正或完善(refine) 當前的答案。
重複步驟 2,直到所有文檔塊都處理完畢。最終的答案就是最後一次迭代的結果。
- 優點:
答案質量高:答案在迭代中不斷細化,可以產生非常精確和細緻的回答。
能處理長文檔:同樣不受上下文窗口限制。
- 缺點:
速度最慢:串行處理意味着調用次數多(N 次),且後續調用必須等待前一次完成,延遲很高。
答案依賴性:文檔塊的處理順序可能會影響最終答案。早期的錯誤可能被後續迭代放大或修正。
- 適用場景:當答案需要高度精確,並且願意以速度和成本為代價來換取質量時。
4. map_rerank(映射重排序)
- 工作方式:
Map(映射):與 map_reduce 的 Map 階段類似,為每個文檔塊單獨調用 LLM。但不同的是,它要求 LLM 不僅給出答案,還要為該答案基於當前塊的可信度輸出一個分數(score)。
Rerank(重排序):不再進行 Reduce 步驟。而是直接選擇分數最高的那個答案作為最終輸出。
- 優點:
單文檔答案:對於事實型、答案可能存在於單個文檔塊中的問題(例如,“某某人的生日是哪天?”)非常有效。
- 缺點:
不適用於綜合型問題:如果答案需要從多個文檔中綜合信息,此方法會失敗,因為它只選一個。
- 適用場景:主要用於問答(Answer Question) 而不是總結(Summarization),並且你確信答案完整地存在於某個單一的文檔塊中。
Chain Type的差異:
|
特性 |
stuff |
map_reduce |
refine |
map_rerank |
|
LLM調用次數 |
1 |
N + 1 |
N |
N |
|
速度 |
最快 |
中等 |
最慢 |
慢 |
|
Token 消耗 |
低 |
高 |
高 |
高 |
|
處理長文檔能力 |
差 |
好 |
好 |
好 |
|
答案質量 |
高 (上下文完整) |
中等 (可能丟失關聯) |
最高 (迭代細化) |
取決於最佳塊 |
|
適用場景 |
默認選擇,文檔短 |
文檔多,需平衡 |
文檔多,要求高精度 |
事實型問題,答案在單塊中 |
為了更直觀地理解和選擇,可以參考下面的決策流程:
選擇建議:
- 優先嚐試 stuff:在大多數情況下,這是最佳選擇。只需確保你的塊大小(chunk_size)和檢索數量( k)的乘積不會超出 LLM 的上下文窗口(記得為問題和答案留出空間)。
- 如果文檔太多太長 → 選擇 map_reduce 或 refine。
- 看重答案質量且不計較延遲 → 選 refine。
- 想要一種平衡的方案 → 選 map_reduce。
- 如果是事實型、答案明確的問題 → 可以嘗試 map_rerank。
七、LangChain與RAG的關係
通俗的講,RAG(檢索增強生成)是一種技術理念或架構模式,是一個具體的“技術方案”或“模式”,就像“做一份番茄炒蛋的菜譜”;而LangChain 是一個實現這種模式(以及其他模式)的強大工具包和框架,就像“一整套廚房用具和智能灶台”。
你可以這樣理解:
- RAG 是“菜譜”:它告訴你做一道菜(構建一個能回答問題的AI)需要哪些步驟(檢索、增強、生成)。
- LangChain 是“廚房和全套廚具”:它為你提供了實現這個菜譜所需要的所有工具(鍋、碗、瓢、盆、灶具),讓你能更高效、更標準地做出這道菜。
八、LangChain與RAG的協調工作原理
當我們用 LangChain 構建一個文檔問答應用時,實際上就是在實現一個標準的 RAG 流程。下圖直觀地展示了LangChain是如何實現RAG全流程的,它通過其豐富的組件和鏈,將RAG從一個理論架構變成了一個可以快速落地實現的具體方案。
綜合可知,在 LangChain 中實現 RAG,本質上是將檢索器(Retriever)和生成器(Generator)通過鏈(Chain)組合起來的工作流程。這個流程通常包含四個關鍵階段:
- 文檔加載與處理:從各種來源(PDF、網頁、數據庫等)加載文檔
- 文檔索引與存儲:將文檔分割並轉換為向量表示,存入向量數據庫
- 相關檢索:根據用户查詢找到最相關的文檔片段
- 增強生成:將檢索到的上下文與用户查詢組合,生成最終答案
LangChain 為每個階段都提供了豐富的組件和簡化接口,讓開發者可以專注於業務邏輯而不是底層實現細節。
九、綜合示例:DeepSeek+ Faiss構建本地知識庫檢索
接下來我們實現了一個基於 RAG 架構的 PDF 文檔問答系統,使用LangChain、DashScope中的deepseek-v3模型和 FAISS。讀取本地的文檔進行檢索;
from PyPDF2 import PdfReader
from langchain.chains.question_answering import load_qa_chain
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS
from typing import List, Tuple
import os
import pickle
DASHSCOPE_API_KEY = os.getenv('DASHSCOPE_API_KEY')
if not DASHSCOPE_API_KEY:
raise ValueError("請設置環境變量 DASHSCOPE_API_KEY")
def extract_text_with_page_numbers(pdf) -> Tuple[str, List[Tuple[str, int]]]:
"""
從PDF中提取文本並記錄每個字符對應的頁碼
參數:
pdf: PDF文件對象
返回:
text: 提取的文本內容
char_page_mapping: 每個字符對應的頁碼列表
"""
text = ""
char_page_mapping = []
for page_number, page in enumerate(pdf.pages, start=1):
extracted_text = page.extract_text()
if extracted_text:
text += extracted_text
# 為當前頁面的每個字符記錄頁碼
char_page_mapping.extend([page_number] * len(extracted_text))
else:
print(f"No text found on page {page_number}.")
return text, char_page_mapping
def process_text_with_splitter(text: str, char_page_mapping: List[int], save_path: str = None) -> FAISS:
"""
處理文本並創建向量存儲
參數:
text: 提取的文本內容
char_page_mapping: 每個字符對應的頁碼列表
save_path: 可選,保存向量數據庫的路徑
返回:
knowledgeBase: 基於FAISS的向量存儲對象
"""
# 創建文本分割器,用於將長文本分割成小塊
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", ".", " ", ""],
chunk_size=1000,
chunk_overlap=200,
length_function=len,
)
# 分割文本
chunks = text_splitter.split_text(text)
print(f"文本被分割成 {len(chunks)} 個塊。")
# 創建嵌入模型
embeddings = DashScopeEmbeddings(
model="text-embedding-v1",
dashscope_api_key=DASHSCOPE_API_KEY,
)
# 從文本塊創建知識庫
knowledgeBase = FAISS.from_texts(chunks, embeddings)
print("已從文本塊創建知識庫。")
# 為每個文本塊找到對應的頁碼信息
page_info = {}
current_pos = 0
for chunk in chunks:
chunk_start = current_pos
chunk_end = current_pos + len(chunk)
# 找到這個文本塊中字符對應的頁碼
chunk_pages = char_page_mapping[chunk_start:chunk_end]
# 取頁碼的眾數(出現最多的頁碼)作為該塊的頁碼
if chunk_pages:
# 統計每個頁碼出現的次數
page_counts = {}
for page in chunk_pages:
page_counts[page] = page_counts.get(page, 0) + 1
# 找到出現次數最多的頁碼
most_common_page = max(page_counts, key=page_counts.get)
page_info[chunk] = most_common_page
else:
page_info[chunk] = 1 # 默認頁碼
current_pos = chunk_end
knowledgeBase.page_info = page_info
print(f'頁碼映射完成,共 {len(page_info)} 個文本塊')
# 如果提供了保存路徑,則保存向量數據庫和頁碼信息
if save_path:
# 確保目錄存在
os.makedirs(save_path, exist_ok=True)
# 保存FAISS向量數據庫
knowledgeBase.save_local(save_path)
print(f"向量數據庫已保存到: {save_path}")
# 保存頁碼信息到同一目錄
with open(os.path.join(save_path, "page_info.pkl"), "wb") as f:
pickle.dump(page_info, f)
print(f"頁碼信息已保存到: {os.path.join(save_path, 'page_info.pkl')}")
return knowledgeBase
def load_knowledge_base(load_path: str, embeddings = None) -> FAISS:
"""
從磁盤加載向量數據庫和頁碼信息
參數:
load_path: 向量數據庫的保存路徑
embeddings: 可選,嵌入模型。如果為None,將創建一個新的DashScopeEmbeddings實例
返回:
knowledgeBase: 加載的FAISS向量數據庫對象
"""
# 如果沒有提供嵌入模型,則創建一個新的
if embeddings is None:
embeddings = DashScopeEmbeddings(
model="text-embedding-v1",
dashscope_api_key=DASHSCOPE_API_KEY,
)
# 加載FAISS向量數據庫,添加allow_dangerous_deserialization=True參數以允許反序列化
knowledgeBase = FAISS.load_local(load_path, embeddings, allow_dangerous_deserialization=True)
print(f"向量數據庫已從 {load_path} 加載。")
# 加載頁碼信息
page_info_path = os.path.join(load_path, "page_info.pkl")
if os.path.exists(page_info_path):
with open(page_info_path, "rb") as f:
page_info = pickle.load(f)
knowledgeBase.page_info = page_info
print("頁碼信息已加載。")
else:
print("警告: 未找到頁碼信息文件。")
return knowledgeBase
# 讀取PDF文件
pdf_reader = PdfReader('./電腦的日常維護.pdf')
# 提取文本和頁碼信息
text, char_page_mapping = extract_text_with_page_numbers(pdf_reader)
#print('page_numbers=',page_numbers)
print(f"提取的文本長度: {len(text)} 個字符。")
# 處理文本並創建知識庫,同時保存到磁盤
save_dir = "./vector_db"
knowledgeBase = process_text_with_splitter(text, char_page_mapping, save_path=save_dir)
# 示例:如何加載已保存的向量數據庫
# 註釋掉以下代碼以避免在當前運行中重複加載
"""
# 創建嵌入模型
embeddings = DashScopeEmbeddings(
model="text-embedding-v1",
dashscope_api_key=DASHSCOPE_API_KEY,
)
# 從磁盤加載向量數據庫
loaded_knowledgeBase = load_knowledge_base("./vector_db", embeddings)
# 使用加載的知識庫進行查詢
docs = loaded_knowledgeBase.similarity_search("客户經理每年評聘申報時間是怎樣的?")
# 直接使用FAISS.load_local方法加載(替代方法)
# loaded_knowledgeBase = FAISS.load_local("./vector_db", embeddings, allow_dangerous_deserialization=True)
# 注意:使用這種方法加載時,需要手動加載頁碼信息
"""
from langchain_community.llms import Tongyi
llm = Tongyi(model_name="deepseek-v3", dashscope_api_key=DASHSCOPE_API_KEY) # qwen-turbo
# 設置查詢問題
query = "如何進行系統性能優化"
if query:
# 執行相似度搜索,找到與查詢相關的文檔
docs = knowledgeBase.similarity_search(query,k=10)
# 加載問答鏈
chain = load_qa_chain(llm, chain_type="stuff")
# 準備輸入數據
input_data = {"input_documents": docs, "question": query}
# 執行問答鏈
response = chain.invoke(input=input_data)
print(response["output_text"])
print("來源:")
# 記錄唯一的頁碼
unique_pages = set()
# 顯示每個文檔塊的來源頁碼
for doc in docs:
#print('doc=',doc)
text_content = getattr(doc, "page_content", "")
source_page = knowledgeBase.page_info.get(
text_content.strip(), "未知"
)
if source_page not in unique_pages:
unique_pages.add(source_page)
print(f"文本塊頁碼: {source_page}")
輸出結果:
提取的文本長度: 7494 個字符。
文本被分割成 10 個塊。
已從文本塊創建知識庫。
頁碼映射完成,共 10 個文本塊
向量數據庫已保存到: ./vector_db
頁碼信息已保存到: ./vector_db\page_info.pkl
系統性能優化可以通過多種方式進行,以下是具體的方法和步驟:
### 1. **磁盤緩存優化**
- 調整系統對磁盤數據的緩存策略,以提高讀寫效率。
### 2. **桌面菜單優化**
- 減少桌面圖標和菜單項的加載時間,提高桌面響應速度。
### 3. **文件系統優化**
- 定期進行磁盤碎片整理,確保文件存儲連續,減少磁盤尋道時間。
### 4. **網絡系統優化**
- 調整網絡協議和設置,優化網絡連接速度和穩定性。
### 5. **開機速度優化**
- **設置開機信息停留時間**:通過拖動滑塊調整開機信息顯示的時間。
- **採用快速啓動方式**:選中“採用Windows XP快速啓動方式”複選框。
- **關閉不必要的開機自啓動程序**:在“請選擇開機不自動運行的項目”列表中取消選中不需要的程序。
### 6. **系統安全優化**
- 關閉不必要的後台服務和防火牆規則,減少系統資源佔用。
### 7. **系統個性優化**
- 根據用户需求調整系統界面和功能設置。
### 8. **後台服務優化**
- 停用或調整不常用的後台服務,以釋放系統資源。
### 9. **使用系統工具**
- 利用**Windows優化大師**等工具,對系統進行全面優化,包括垃圾文件清理、冗餘DLL分析等。
### 操作步驟示例:
1. **單擊“系統性能優化”選項**,選擇“開機速度優化”。
2. **調整啓動信息停留時間**:拖動滑塊設置開機信息停留時間。
3. **設置啓動方式**:勾選“採用Windows XP快速啓動方式”和“異常時啓動磁盤錯誤檢查等待時間”。
4. **選擇不開機自啓動的項目**:在列表框中取消選中不需要開機自啓動的程序。
5. **單擊“優化”按鈕**完成設置。
通過這些方法,可以顯著提高系統的運行效率和響應速度。
來源:
文本塊頁碼: 13
文本塊頁碼: 10
文本塊頁碼: 9
文本塊頁碼: 7
文本塊頁碼: 1
文本塊頁碼: 18
文本塊頁碼: 4
文本塊頁碼: 16
文本塊頁碼: 2
示例的文檔是一份《電腦的日常維護.pdf》手冊,我們提問的是“如何進行系統性能優化?”,以下截取文檔的部分圖片:
思路分析:
- 知識庫構建(Indexing Pipeline):
- 從 PDF 提取文本和精確的頁碼信息。
- 對文本進行分塊(chunking)。
- 使用 DashScope 的嵌入模型將文本塊轉換為向量。
- 將向量存入 FAISS 向量數據庫。
- 將向量庫和關鍵的頁碼映射信息保存到本地。
- 問答推理(Retrieval & Generation Pipeline):
- 從本地加載預先構建好的知識庫(FAISS 索引和頁碼信息)。
- 接收用户查詢。
- 在向量庫中檢索與查詢最相關的文本塊。
- 將相關文本塊和查詢組合,發送給 LLM(Tongyi 的 Deepseek-v3 模型)生成答案。
- 輸出答案並標註答案來源的頁碼。
十、總結
LangChain 與 RAG 的結合為構建高效、準確的智能問答系統提供了強大基礎。通過 LangChain 提供的模塊化組件,我們可以輕鬆實現RAG架構的各個階段,從文檔處理到最終答案生成。
這種組合的優勢在於:
- 知識實時性:可以隨時更新知識庫,而不需要重新訓練模型
- 答案可追溯:可以顯示答案來源,提高可信度和可解釋性
- 定製化能力強:可以針對特定領域或企業需求定製知識庫
- 開發效率高:LangChain抽象了複雜流程,大幅減少開發時間
無論是想構建企業知識庫、智能客服系統還是專業問答助手,LangChain與RAG的組合都能提供強大的技術基礎和靈活的定製能力,協助將AI技術轉化為實際業務價值。