流式回應技術和可設定的代理系統為開發更自然、更人工智慧的AI應用提供了強大基礎。透過結合這些技術,我們可以建立出反應迅速、個人化與功能豐富的AI助手。

在實際應用中,這些技術可以應用於各種場景,從客戶服務聊天機器人到個人助理、教育工具甚至娛樂應用。關鍵在於理解使用者需求,並利用這些技術創造出流暢、自然的互動體驗。

隨著AI技術的不斷發展,我們可以期待更多創新功能的出現,進一步模糊AI與人類互動的界限。流式回應只是這一旅程的開始,未來的AI應用將更加人工智慧、自然與個人化。

對於開發者來說,掌握這些技術不僅能提升使用者經驗,還能為建立下一代AI應用奠定堅實基礎。透過靈活運用Streamlit、代理設定檔案和代理引擎等工具,我們能夠快速原型化和佈署各種AI驅動的解決方案,滿足不斷變化的市場需求。 這段程式碼展示了一個使用OpenAI API的人工智慧代理系統,主要功能是處理使用者輸入並提供回應,同時支援工具和函式呼叫。讓我們探討這個系統的核心元件和工作流程。

基本回應機制的實作

人工智慧代理的核心功能是處理使用者輸入並生成回應。在get_response函式中,我們可以看到這個基本流程:

async def get_response(self, user_input, thread_id=None):
    self.messages += [{"role": "user", "content": user_input}]
    response = self.client.chat.completions.create(
        model=self.model,
        messages=self.messages,
        temperature=0.7,
    )
    self.last_message = str(response.choices[0].message.content)
    return self.last_message
  • 這個函式首先將使用者輸入增加到訊息歷史中,保持對話上下文
  • 使用預先設定的OpenAI客戶端傳送請求,指定模型和訊息歷史
  • 溫度引數(temperature)設為0.7,提供一定的回應變化性,平衡創意和一致性
  • 最後提取並回傳AI的回應內容

代理工具與函式系統

Nexus平台採用了外掛式設計,允許開發者擴充套件代理功能:

  1. 外掛系統設計

    • 新的代理引擎定義可放置在nexus_agents資料夾中
    • 系統會自動發現並整合這些自定義代理
  2. 函式定義與使用

    • Nexus支援兩種函式型別:原生函式(code)和語義函式(prompt)
    • 函式定義只需放入nexus_actions資料夾即可自動被發現

函式定義範例

以下是兩種不同型別函式的定義方式:

@agent_action
def get_current_weather(location, unit="fahrenheit"):
    """Get the current weather in a given location"""
    return f"""
    The current weather in {location} is 0 {unit}.
    """

@agent_action
def recommend(topic):
    """System:
    Provide a recommendation for a given {{topic}}.
    Use your best judgment to provide a recommendation. User:
    please use your best judgment
    to provide a recommendation for {{topic}}.
    """
    pass
  • 使用@agent_action裝飾器將普通函式轉換為代理可用的動作
  • 第一個函式是原生函式,包含實際執行的程式碼邏輯
  • 第二個函式是語義函式,不包含實際程式碼,只有提示文字,執行時會將提示傳送給LLM
  • 函式的檔案字元串(docstring)會被用作工具描述,對於語義函式,它還作為提示範本

工具規範生成

系統會自動為這些函式生成符合OpenAI標準的工具規範:

{
  "type": "function",
  "function": {
    "name": "get_current_weather",
    "description": "Get the current weather in a given location",
    "parameters": {
      "type": "object",
      "properties": {
        "location": {
          "type": "string",
          "description": "location"
        },
        "unit": {
          "type": "string",
          "enum": [
            "celsius",
            "fahrenheit"
          ]
        }
      },
      "required": [
        "location"
      ]
    }
  }
}
  • 系統自動從函式定義中提取名稱、描述和引數訊息
  • 引數型別和必要性被正確識別並增加到規範中
  • 對於列舉型別引數,系統也能正確識別可能的值
  • 這個規範將被傳送給LLM,使其瞭解如何呼叫這些函式

流式回應與工具呼叫實作

get_response_stream函式實作了更複雜的邏輯,包括工具呼叫:

def get_response_stream(self, user_input, thread_id=None):
    self.last_message = ""
    self.messages += [{"role": "user", "content": user_input}]
    
    if self.tools and len(self.tools) > 0:
        response = self.client.chat.completions.create(
            model=self.model,
            messages=self.messages,
            tools=self.tools,
            tool_choice="auto",
        )
    else:
        response = self.client.chat.completions.create(
            model=self.model,
            messages=self.messages,
        )
        
    response_message = response.choices[0].message
    tool_calls = response_message.tool_calls
  • 函式首先檢查代理是否有可用工具
  • 如果有工具,它會在API呼叫中包含這些工具定義,並設定tool_choice="auto"讓LLM自行決定是否使用工具
  • 如果沒有工具,則進行標準的LLM呼叫
  • 最後檢查回應中是否包含工具呼叫請求

平行工具呼叫執行

當LLM決定使用工具時,系統會執行以下邏輯:

if tool_calls:
    available_functions = {
        action["name"]: action["pointer"] for action in self.actions
    }
    self.messages.append(response_message)
    
    for tool_call in tool_calls:
        function_name = tool_call.function.name
        function_to_call = available_functions[function_name]
        function_args = json.loads(tool_call.function.arguments)

        function_response = function_to_call(
            **function_args, _caller_agent=self
        )
        
        self.messages.append({
            "tool_call_id": tool_call.id,
            "role": "tool",
            "name": function_name,
            "content": str(function_response),
        })
        
    second_response = self.client.chat.completions.create(
        model=self.model,
        messages=self.messages,
    )
    
    response_message = second_response.choices[0].message
  • 系統建立一個函式名稱到實際函式指標的對映
  • 將LLM的初始回應增加到訊息歷史中
  • 對每個工具呼叫,提取函式名稱、引數並執行對應函式
  • 這種實作支援平行函式呼叫,即LLM可以一次請求多個工具執行
  • 所有工具執行結果被增加到訊息歷史中
  • 然後進行第二次LLM呼叫,將工具結果提供給LLM以生成最終回應
  • 這種方式允許LLM根據工具執行結果調整其回應

代理系統的實用設計模式

Nexus平台的設計體現了幾個重要的軟體設計模式:

  1. 外掛架構模式:透過資料夾約定實作簡單而有效的外掛系統,無需複雜的序號產生器制

  2. 裝飾器模式:使用@agent_action裝飾器簡化函式到工具的轉換過程

  3. 轉接器模式:將不同型別的函式(原生和語義)統一轉換為標準工具規範

  4. 工廠模式:動態建立和管理不同型別的代理和工具

這種設計使得Nexus系統既靈活又易於擴充套件,開發者可以輕鬆增加新功能而無需修改核心程式碼。下一章將探討如何透過檢索增強生成(RAG)模式,使代理能夠消費外部記憶和知識,進一步增強其能力。

代理工具系統的實際應用

這種工具系統設計使人工智慧代理能夠執行各種實際任務,從簡單的資料檢索到複雜的業務流程自動化。例如:

  • 資料分析代理可以呼叫函式來處理和視覺化資料
  • 客戶服務代理可以查詢產品訊息、檢查庫存或建立訂單
  • 開發助手可以生成程式碼、執行測試或查詢API檔案

透過這種模組化設計,開發者可以根據特定需求定製代理的能力,而無需從頭開始構建複雜系統。

開發人工智慧代理的記憶與知識系統:RAG技術全解析

人工智慧代理系統要真正實用,必須具備兩個關鍵能力:記憶與知識。沒有這兩項,再先進的AI也只是一個「失憶的天才」—擁有處理資訊的能力,卻無法記住過去的互動或應用專業知識。

在這篇文章中,玄貓將探討如何為AI代理系統構建有效的記憶與知識架構,特別聚焦於檢索增強生成(Retrieval Augmented Generation, RAG)技術的實作。無論你是想開發具有持久記憶的聊天機器人,還是建立能查詢專業檔案的知識助手,這篇都能幫助你實作目標。

記憶與知識在AI系統中的關鍵作用

在深入技術細節前,先釐清一個重要概念:記憶與知識雖然相關,但在AI系統中扮演不同角色。

**記憶(Memory)**是代理系統對過去互動的儲存,包括對話歷史、使用者偏好、任務進度等。良好的記憶系統讓AI能夠維持連貫對話,記住使用者偏好,甚至從過去經驗中學習。

**知識(Knowledge)**則是代理系統可參考的外部資訊,如檔案、資料函式庫或網頁內容。知識系統讓AI能夠回答特定領域問題,即使這些資訊不在其預訓練資料中。

兩者結合,創造出既能保持上下文連貫,又能提供專業資訊的人工智慧系統。

檢索增強生成(RAG)技術基礎

RAG已成為構建知識型AI系統的標準方法。它的核心思想很簡單:在生成回應前,先從外部資料來源檢索相關資訊,並將其作為上下文提供給語言模型。

RAG系統如何運作

一個完整的RAG系統涉及兩個主要階段:

1. 索引階段(Indexing)

這是準備工作,需要提前完成:

  1. 檔案載入與轉換:將PDF、網頁或其他格式的檔案轉換為純文字
  2. 文字分割:將長文字分割成適合處理的小塊(chunks)
  3. 向量化:使用嵌入模型(embedding model)將每個文字塊轉換為數值向量
  4. 儲存:將這些向量及其對應的文字存入向量資料函式庫

在實作這個階段時,我發現文字分割策略對檢索品質影響極大。過大的塊可能包含太多不相關資訊,過小的塊則可能失去上下文連貫性。我通常採用300-500字的重疊分割,在保留上下文的同時確保檢索精確度。

2. 檢索與生成階段(Retrieval and Generation)

這是使用者實際使用系統時發生的過程:

  1. 查詢向量化:將使用者問題轉換為向量
  2. 相似度搜尋:在向量資料函式庫中找出與查詢最相似的文字塊
  3. 上下文增強:將檢索到的相關文字塊作為上下文加入提示(prompt)中
  4. 回應生成:語言模型根據增強後的提示生成回應

下圖展示了RAG系統的完整工作流程:

  graph TD
    A[檔案載入] --> B[文字分割]
    B --> C[向量化/嵌入]
    C --> D[存入向量資料函式庫]
    E[使用者查詢] --> F[查詢向量化]
    F --> G[相似度搜尋]
    G --> H[檢索相關上下文]
    H --> I[提示增強]
    I --> J[語言模型生成]
    J --> K[回應]

實作RAG系統的關鍵元件

構建RAG系統需要以下核心元件:

  1. 檔案處理器:負責載入、解析和分割檔案
  2. 嵌入模型:將文字轉換為向量表示
  3. 向量資料函式庫:高效儲存和檢索向量
  4. 大模型語言(LLM):生成最終回應
  5. 協調框架:協調各元件間的工作流程

在接下來的章節,玄貓將使用LangChain框架實際構建一個完整的RAG系統,並展示如何在Nexus代理平台中應用這些技術。

使用LangChain構建RAG工作流程

LangChain是一個強大的框架,專為開發根據LLM的應用而設計。它提供了豐富的工具和抽象,大簡化了RAG系統的實作過程。

檔案索引與向量儲存

首先,讓我們看如何使用LangChain處理檔案並建立向量索引:

from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma

# 1. 載入檔案
loader = PyPDFLoader("knowledge_document.pdf")
documents = loader.load()

# 2. 文字分割
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    length_function=len,
)
chunks = text_splitter.split_documents(documents)

# 3. 建立嵌入和向量儲存
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(chunks, embeddings)

這段程式碼展示了RAG系統的索引階段。首先使用PyPDFLoader載入PDF檔案,然後透過RecursiveCharacterTextSplitter將檔案分割成大小適中的文字塊,設定了1000字元的塊大小和200字元的重疊區域。重疊區域很重要,它能確保跨越多個塊的相關資訊不會被分隔。接著,使用OpenAI的嵌入模型將每個文字塊轉換為向量,並儲存在Chroma向量資料函式庫中。

構建檢索增強生成鏈

有了向量儲存後,就可以建立完整的RAG查詢鏈:

from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate

# 定義提示範本
template = """使用以下上下文來回答問題。如果你不知道答案,就說你不知道,不要試圖編造答案。

上下文: {context}

問題: {question}
回答: """

PROMPT = PromptTemplate(
    template=template,
    input_variables=["context", "question"]
)

# 建立LLM
llm = ChatOpenAI(model_name="gpt-3.5-turbo")

# 構建RAG鏈
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
    chain_type_kwargs={"prompt": PROMPT}
)

# 使用RAG鏈回答問題
response = qa_chain.run("什麼是向量資料函式庫的主要優勢?")
print(response)

這段程式碼建立了完整的RAG查詢流程。首先定義了一個提示範本,指導語言模型如何使用檢索到的上下文回答問題。然後建立一個ChatOpenAI例項作為生成模型,並構建RetrievalQA鏈,將向量儲存、檢索器和語言模型連線起來。

search_kwargs={"k": 3}指定每次檢索回傳3個最相關的文字塊。chain_type="stuff"表示使用"stuff"方法,即將所有檢索到的文字直接合併到提示中。這種方法適合檢索內容較少的情況;對於大量檢索結果,可能需要使用"map_reduce"或"refine"等更複雜的方法。

最佳化RAG系統效能

在實際應用中,我發現以下幾個技巧可以顯著提升RAG系統的效能:

1. 最佳化分塊策略

不同型別的檔案可能需要不同的分塊策略:

# 對於結構化檔案(如學術論文)
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=400,
    chunk_overlap=50,
    separators=["\n\n", "\n", " ", ""]
)

# 對於程式碼檔案
from langchain.text_splitter import Language
text_splitter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON,
    chunk_size=700,
    chunk_overlap=50
)

第一個分割器針對結構化檔案,使用tiktoken編碼器精確計算token數量,並設定了分隔符優先順序,先嘗試按段落分割,再按行分割,最後按空格分割。第二個分割器專為Python程式碼設計,能理解程式語言的結構,避免將函式或類別分割開。選擇合適的分割策略能顯著提升檢索品質。

2. 實作混合搜尋

結合關鍵字和語義搜尋通常能獲得更好結果:

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

# 基本檢索器
basic_retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 8})

# LLM壓縮器
llm = ChatOpenAI(temperature=0)
compressor = LLMChainExtractor.from_llm(llm)

# 上下文壓縮檢索器
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=basic_retriever
)

# 使用壓縮檢索器
compressed_docs = compression_retriever.get_relevant_documents("什麼是向量資料函式庫?")

這段程式碼實作了一個高階檢索策略。首先建立一個基本檢索器,檢索8個最相似的檔案。然後使用LLMChainExtractor作為壓縮器,它能從檢索到的檔案中提取最相關的部分。ContextualCompressionRetriever將兩者結合,先檢索更多檔案,再透過LLM篩選出真正相關的內容。這種方法能在保持高召回率的同時提高精確度。

在代理系統中實作記憶與知識管理

代理系統需要的不僅是檢索能力,還需要持久化記憶。下面玄貓將展示如何在代理系統中實作多種記憶模式。

對話記憶型別

LangChain提供了多種記憶型別,適用於不同場景:

from langchain.memory import ConversationBufferMemory, ConversationSummaryMemory
from langchain.chains import ConversationChain

# 簡單緩衝記憶
buffer_memory = ConversationBufferMemory()

# 摘要記憶(適合長對話)
summary_memory = ConversationSummaryMemory(llm=ChatOpenAI())

# 將記憶應用到對話鏈
conversation = ConversationChain(
    llm=ChatOpenAI(),
    memory=summary_memory,
    verbose=True
)

# 進行對話
response = conversation.predict(input="我的名字是張小明")
print(response)

response = conversation.predict(input="你還記得我的名字嗎?")
print(response)

這段程式碼展示了兩種記憶型別:ConversationBufferMemory直接儲存完整對話歷史,適合短期對話;ConversationSummaryMemory則使用LLM動態生成對話摘要,適合長期對話,能避免上下文長度限制問題。ConversationChain將LLM和記憶系統連線起來,建立一個能記住過去互動的對話系統。

實作語義記憶系統

下面是一個更高階的語義記憶系統實作,它能根據對話內容的相關性檢索過去記憶:

from langchain.memory import VectorStoreRetrieverMemory
from langchain.chains import ConversationChain

# 建立向量儲存
memory_vectorstore = Chroma(
    collection_name="memory",
    embedding_function=OpenAIEmbeddings()
)

# 建立記憶檢索器
retriever = memory_vectorstore.as_retriever(search_kwargs={"k": 5})
memory = VectorStoreRetrieverMemory(retriever=retriever)

# 增加一些初始記憶
memory.save_context(
    {"input": "我最喜歡的顏色是藍色"},
    {"output": "我會記住你喜歡藍色"}
)
memory.save_context(
    {"input": "我住在台北"},
    {"output": "瞭解,你住在台北"}
)

# 檢索相關記憶
print(memory.load_memory_variables({"prompt": "我喜歡什麼顏色?"}))

這段程式碼實作了根據向量儲存的語義記憶系統。與簡單按時間順序儲存對話不同,VectorStoreRetrieverMemory將對話內容向量化並存入Chroma資料函式庫。當需要回憶時,它會根據當前問題的語義相似度檢索最相關的過去對話,而不僅是最近的對話。這種方式模擬了人類根據話題關聯回憶過去經驗的能力。

在Nexus平台實作代理記憶系統

Nexus是一個開放原始碼的代理開發平台,讓我們看如何在其中實作記憶系統:

from nexus import Agent, Memory, Document
from nexus.memory import SemanticMemory

# 建立語義記憶例項
semantic_memory = SemanticMemory()

# 建立代理並設定記憶
agent = Agent(
    name="知識助手",
    llm="gpt-3.5-turbo",
    memory=semantic_memory,
    system_prompt="你是一個有記憶能力的助手,能記住使用者提供的訊息並在需要時回憶。"
)

# 增加記憶
semantic_memory.add("使用者資料", "使用者名是王小明,他喜歡籃球和電影。")

# 使用代理回答問題
response = agent.generate("我喜歡什麼運動?")
print(response)

這段程式碼展示瞭如何在Nexus平台上實作語義記憶系統。SemanticMemory類別提供了增加和檢索記憶的功能,它在內部使用向量儲存實作語義搜尋。當代理收到問題時,會自動檢索相關記憶並將其納入提示中,使LLM能夠利用這些記憶生成回應。這種方式讓代理系統擁有了"長期記憶",能夠記住使用者偏好和過去互動。