Biến Raspberry Pi thành một AI Agentic RAG cục bộ bằng LLM nguồn mở

Nội dung

    Cùng bắt đầu xây dựng một tác nhân AI, cung cấp kiến ​​thức từ hướng dẫn sử dụng của tất cả các thiết bị của mình và để nó trả lời các câu hỏi của mình khi cần. Tác nhân này chạy trên Raspberry Pi của mình và luôn sẵn sàng giúp mình. Có nhiều khuôn khổ ngoài kia giúp bạn xây dựng các tác nhân AI. LlamaIndex là khuôn khổ tôi đã chọn, nhưng bạn có thể xây dựng nó bằng bất kỳ khuôn khổ tác nhân nào. Vì tôi muốn ứng dụng này chạy miễn phí, tôi đã sử dụng LLM nguồn mở cho dự án. Ollama rất tuyệt vời cho mục đích này, cho phép bạn tải xuống LLM nguồn mở.

    Trong bài viết này chúng ta đã xem xét:

    • Cách xây dựng tác nhân RAG và ReAct bằng LlamaIndex.
    • Các yếu tố cần cân nhắc khi chạy chương trình này trên máy có công suất thấp như Raspberry Pi.
    • Sử dụng Streamlit để xây dựng giao diện người dùng đơn giản cho ứng dụng.

    Chuẩn bị không gian cho hướng dẫn sử dụng

    Bắt đầu bằng cách thu thập các hướng dẫn sử dụng mà bạn muốn AI truy cập và lưu chúng vào một thư mục cụ thể. Tôi chỉ cần tải mọi thứ lên trong thư mục này: docs/user_manuals.

    Xây dựng một công cụ truy vấn từ các tài liệu

    Tiếp theo, chúng ta cần xử lý bit RAG của tác nhân AI này. Điều này bao gồm việc lấy các tệp PDF từ thư mục, đọc và phân đoạn chúng, chuyển đổi các khối thành nhúng và lưu chúng vào cơ sở dữ liệu vector. Đây là tổng quan rất nhanh về khía cạnh chuẩn bị dữ liệu của RAG và người đọc được khuyến khích tìm hiểu sâu hơn về nó. Mặc dù quá trình này nghe có vẻ phức tạp, nhưng may mắn thay, LlamaIndex cho phép chúng ta thực hiện điều này chỉ với 3 dòng mã:

    documents = SimpleDirectoryReader(“docs/user_manuals”).load_data(show_progress=True)”docs/user_manuals”).load_data(show_progress=True)
    index = VectorStoreIndex.from_documents(documents, show_progress=True)
    query_engine = index.as_query_engine()

    Tuy nhiên, trước khi xác định chỉ mục và công cụ truy vấn, mô hình LLM nhúng cần phải được thay đổi. LlamaIndex sử dụng các mô hình OpenAI theo mặc định, nhưng bạn có thể sử dụng bất kỳ mô hình nào bạn thích. Trong trường hợp của tôi, việc sử dụng mô hình nguồn mở từ Ollama có thể được thực hiện như sau:

    Settings.embed_model = OllamaEmbedding(model_name=”all-minilm:22m”)“all-minilm:22m”)

    Người đọc được khuyến khích thử nghiệm với các mô hình nhúng khác nhau. Tôi đã thành công khác nhau với những mô hình tôi đã thử, chẳng hạn nhưnomic-embed-text, nhưngall-minilm:22mcho tôi thành công nhất. Với mô hình nomic, nó thường không phân tích cú pháp và nhúng một số phần nhất định của một số tài liệu. Bạn có thể thử mã khởi động 5 dòng do LlamaIndex cung cấp để kiểm tra hiệu suất của các mô hình nhúng:LlamaIndex — LlamaIndex. Tuy nhiên, tôi quyết định không sử dụng mã đó cho dự án của mình vì tôi muốn tác nhân của mình suy luận thông qua phản hồi mà nó nhận được từ công cụ truy vấn — tôi sẽ nói thêm về điều đó sau.

    Xây dựng tác nhân ReAct

    Mộttác nhân ReAct, viết tắt của Reason + Act, là một tác nhân lý luận suy nghĩ và hành động từng bước để giải quyết một nhiệm vụ phức tạp. Khá đơn giản để tạo một tác nhân ReAct trong LlamaIndex và cung cấp cho nó các công cụ. Đầu tiên, chúng ta sẽ chuyển đổi công cụ truy vấn được xác định trước đó thành một công cụ mà tác nhân có thể sử dụng:

    user_manual_query_tool = QueryEngineTool.from_defaults(
    query_engine,
    name=”user_manual_query_tool”,”user_manual_query_tool”,
    description=(
    “A RAG engine with various user manuals of appliances.”,
    “Use a detailed plain text question as input to the tool.”),
    )

    Bước tiếp theo là xác định tác nhân ReAct, cung cấp cho nó quyền truy cập vào công cụ truy vấn hướng dẫn sử dụng:

    system_prompt = “”””””
    System prompt:

    You are an AI assistant specialized in answering questions about appliances using information from user manuals.

    1. Always rely on the **user_manual_query_tool** to retrieve information; do not use prior knowledge.
    2. If a query cannot be answered using the tool, respond with: “I cannot find this information in the user manuals provided.”
    3. Do not attempt to generate answers without using the tool.
    4. Give detailed responses to the user’s queries, explaining everything in steps.
    5. Return the response in markdown format.

    Note: The tool name is **user_manual_query_tool**, and it should be used for all queries.
    “””

    self.agent = ReActAgent.from_tools(
    tools=[user_manual_query_tool],
    verbose=True,
    context=system_prompt
    )

    Tôi cũng đã định nghĩa một lời nhắc hệ thống cho tác nhân ở trên, để tôi có được câu trả lời nhất quán cho các truy vấn của mình từ hướng dẫn sử dụng. Nếu không có lời nhắc này, đôi khi tôi thấy tác nhân trả về phản hồi từ kiến ​​thức của riêng mình, thay vì truy vấn chỉ mục kiến ​​thức.

    Ngoài ra, LlamaIndex sử dụng OpenAI LLM làm mặc định, nhưng chúng ta có thể dễ dàng thay đổi cài đặt để sử dụng Ollama:

    Settings.llm = Ollama(model=”llama3.1″)”llama3.1″)

    Tuy nhiên, tại thời điểm này, tôi bắt đầu nghi ngờ liệu điều này có thể chạy trên Raspberry Pi của tôi hay không, vì phải dựa vào llama3.1 (tham số 8b) có thể là một mô hình mạnh mẽ để chạy trên Raspberry Pi. Để kiểm tra mọi thứ, tôi đã tạo một lớp cho tác nhân:

    from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
    from llama_index.llms.ollama import Ollama
    from llama_index.embeddings.ollama import OllamaEmbedding
    from llama_index.core.agent import ReActAgent
    from llama_index.core.tools import QueryEngineTool
    import time
    import nest_asyncio

    nest_asyncio.apply()

    class UserManualAgent:
    _instance = None

    def __new__(cls, *args, **kwargs):
    if cls._instance is None:
    cls._instance = super(UserManualAgent, cls).__new__(cls, *args, **kwargs)
    return cls._instance

    def __init__(self):
    if not hasattr(self, ‘initialized’):
    Settings.embed_model = OllamaEmbedding(model_name=”all-minilm:22m”)
    Settings.llm = Ollama(model=”llama3.1″)

    documents = SimpleDirectoryReader(“docs/user_manuals”).load_data(show_progress=True)
    index = VectorStoreIndex.from_documents(documents, show_progress=True)
    query_engine = index.as_query_engine()

    user_manual_query_tool = QueryEngineTool.from_defaults(
    query_engine,
    name=”user_manual_query_tool”,
    description=(
    “A RAG engine with various user manuals of appliances.”,
    “Use a detailed plain text question as input to the tool.”),
    )

    system_prompt = “””
    System prompt:

    You are an AI assistant specialized in answering questions about appliances using information from user manuals.

    1. Always rely on the **user_manual_query_tool** to retrieve information; do not use prior knowledge.
    2. If a query cannot be answered using the tool, respond with: “I cannot find this information in the user manuals provided.”
    3. Do not attempt to generate answers without using the tool.
    4. Give detailed responses to the user’s queries, explaining everything in steps.
    5. Return the response in markdown format.

    Note: The tool name is **user_manual_query_tool**, and it should be used for all queries.
    “””

    self.agent = ReActAgent.from_tools(
    tools=[user_manual_query_tool],
    verbose=True,
    context=system_prompt
    )

    print(“done initializing agent”)
    self.initialized = True

    def query(self, question):
    print(“user query: “, question)
    start_time = time.time()
    try:
    response = self.agent.chat(question)
    response_time = time.time() – start_time
    print(“response time: “, response_time)
    except ValueError as e:
    response = “I cannot find this information in the user manuals provided.”
    print(e)
    return response

    if __name__ == “__main__”:
    agent = UserManualAgent()
    response = agent.query(“How to setup the Roborock vacuum?”)
    print(response)

    Lưu ý rằng tôi đã tạo một singleton cho lớp này, do đó các lệnh gọi riêng biệt đến hàm tạo không khởi tạo tác nhân nhiều lần.

    Tối ưu hóa tác nhân cho Raspberry Pi

    Bước tiếp theo là cài đặt Ollama và các mô hình phụ thuộc trên Raspberry Pi, kéo mã đã đề cập ở trên, cài đặt tất cả các phụ thuộc và chạy lớp để xem nó có hoạt động không. Và như tôi lo sợ, LLM đã hết thời gian chờ khi phản hồi. Tuy nhiên, tôi nên đề cập rằng tôi đã có thể chạy và trò chuyện với llama3.1 trên Raspberry Pi một mình. Tuy nhiên, nó không hoạt động hoàn toàn với tác nhân ReAct. Tôi bắt đầu thử nghiệm xung quanh với các mô hình Ollama khác nhau và các giá trị thời gian chờ và sự kết hợp cuối cùng đã hoạt động là:

    Settings.llm = Ollama(model=”tinyllama”, request_timeout=60*5)”tinyllama”, request_timeout=60*5)

    Sự kết hợp này hầu như không gây ra thời gian chờ, nhưng vẫn chậm. Đối với tôi, thời gian phản hồi nằm trong khoảng 45–60 giây. Có thể đạt được hiệu suất tốt hơn với phiên bản RAM 16 GB của Pi hoặc sử dụng SSD để lưu trữ thay vì thẻ SD và tôi có thể cân nhắc những điều này trong tương lai.

    Đây là hạn chế khi sử dụng một mô hình nhỏ hơn như TinyLlama làm tác nhân lý luận và tôi nhận ra rằng tôi có thể phải thử nghiệm với các mô hình.

    Cuối cùng tôi quyết định thay thế Ollama trong thiết lập của mình bằngOpenRoutervà sử dụng LLM nguồn mở từ đó. Nó có giới hạn về số lượng yêu cầu mà người dùng có thể thực hiện mỗi phút, nhưng là một giải pháp thay thế tuyệt vời khi bạn không có đủ tài nguyên phần cứng để chạy mô hình LLM lớn hơn.

    LlamaIndex cũng cung cấp khả năng tích hợp dễ dàng với OpenRouter. Mã cuối cùng cho lớp hướng dẫn sử dụng như sau:

    from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
    from llama_index.llms.openrouter import OpenRouter
    from llama_index.embeddings.ollama import OllamaEmbedding
    from llama_index.core.agent import ReActAgent
    from llama_index.core.tools import QueryEngineTool

    import nest_asyncio
    nest_asyncio.apply()
    import time
    from dotenv import load_dotenv
    import os
    load_dotenv()

    class UserManualAgent:
    _instance = None

    def __new__(cls, *args, **kwargs):
    if cls._instance is None:
    cls._instance = super(UserManualAgent, cls).__new__(cls, *args, **kwargs)
    return cls._instance

    def __init__(self):
    if not hasattr(self, ‘initialized’):
    Settings.embed_model = OllamaEmbedding(model_name=”all-minilm:22m”)
    llm = OpenRouter(
    api_key=os.getenv(“OPEN_ROUTER_KEY”),
    model=”meta-llama/llama-3.2-3b-instruct:free”,
    request_timeout=60*5
    )

    Settings.llm = llm

    Settings.chunk_size = 700
    Settings.chunk_overlap = 20

    documents = SimpleDirectoryReader(“docs/user_manuals”).load_data(show_progress=True)
    index = VectorStoreIndex.from_documents(documents, show_progress=True)
    query_engine = index.as_query_engine()

    user_manual_query_tool = QueryEngineTool.from_defaults(
    query_engine,
    name=”user_manual_query_tool”,
    description=(
    “A RAG engine with various user manuals of appliances.”,
    “Use a detailed plain text question as input to the tool.”),
    )

    system_prompt = “””
    System prompt:

    You are an AI assistant specialized in answering questions about appliances using information from user manuals.

    1. Always rely on the **user_manual_query_tool** to retrieve information; do not use prior knowledge.
    2. If a query cannot be answered using the tool, respond with: “I cannot find this information in the user manuals provided.”
    3. Do not attempt to generate answers without using the tool.
    4. Give detailed responses to the user’s queries, explaining everything in steps.
    5. Return the response in markdown format.

    Note: The tool name is **user_manual_query_tool**, and it should be used for all queries.
    “””

    self.agent = ReActAgent.from_tools(
    tools=[user_manual_query_tool],
    verbose=True,
    context=system_prompt
    )

    print(“done initializing agent”)
    self.initialized = True

    def query(self, question):
    print(“user query: “, question)
    start_time = time.time()
    try:
    response = self.agent.chat(question)
    response_time = time.time() – start_time
    print(“response time: “, response_time)
    except ValueError as e:
    response = “I cannot find this information in the user manuals provided.”
    print(e)
    return response

    if __name__ == “__main__”:
    agent = UserManualAgent()
    response = agent.query(“How to setup the Roborock vacuum?”)

    print(response)

    Sau khi thay đổi kích thước khối và chồng lấn khối, tôi cũng đạt được hiệu suất nhanh hơn khi nhúng tài liệu vào Raspberry Pi.

    Tạo giao diện người dùng bằng streamlit để tương tác với tác nhân

    Bước cuối cùng là tạo một giao diện người dùng nhanh bằng streamlit để tương tác với tác nhân. Đây là mã cho giao diện người dùng:

    import streamlit as st
    from user_manual_agent import UserManualAgent

    @st.cache_resource
    def initialize_agent():
    user_manual_agent = UserManualAgent()
    return user_manual_agent

    # Function to cycle through messages
    def display_interface(user_manual_agent):
    # Streamlit app
    st.title(“User Manual Agent”)
    st.write(“Ask any questions you have about your appliances :sunglasses:”)

    # Input text box for user query
    user_query = st.text_input(“Enter your question:”)

    # Button to submit the query
    if st.button(“Submit”):
    if user_query:
    with st.spinner(“Fetching response…”):
    response = user_manual_agent.query(user_query)
    st.markdown(“**Response**: \n\n”)
    st.markdown(response.response)

    else:
    st.write(“Please enter a question.”)

    user_manual_agent = initialize_agent()
    display_interface(user_manual_agent)

    Lưu ý việc sử dụng @st.cache_resourcekhi tạo tác nhân. Điều này ngăn không cho tác nhân được khởi tạo nhiều lần nếu ứng dụng được truy cập nhiều lần.

    Bây giờ bạn cũng có thể biến Raspberry Pi thành trợ lý cá nhân!

    Nguôn: https://medium.com/@muhammad.taha/turn-your-raspberry-pi-into-a-local-ai-agent-to-help-you-figure-out-your-appliances-4aea2d39ee91

    Để lại một bình luận

    Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *