Trong bài viết này, chúng tôi muốn chỉ cho bạn cách kết hợp hai công nghệ ( LangChain và Anthropic ) và tạo một trợ lý cá nhân hỗ trợ tìm kiếm.
Ngày càng có nhiều sản phẩm AI được tạo ra bởi những người tham gia hackathons của chúng tôi. Cơ hội tiếp theo dành cho các nhà phát triển là cuộc thi hackathon chung của chúng tôi với Google Cloud Vertex AI , nơi mỗi người tham gia có cơ hội tạo ứng dụng AI của riêng họ bằng cách sử dụng công nghệ mới nhất từ Google Cloud . Ở đây chúng ta sẽ nói về một ứng dụng của AI đang ngày càng trở nên quan trọng hơn: công cụ tìm kiếm .
Trợ lý cá nhân hỗ trợ tìm kiếm là trợ lý kỹ thuật số sử dụng công nghệ công cụ tìm kiếm để giúp người dùng thực hiện các tác vụ như tìm thông tin, đặt chỗ, đặt lời nhắc và gửi tin nhắn. Những trợ lý này sử dụng các thuật toán tìm kiếm để thu thập và phân tích dữ liệu từ nhiều nguồn khác nhau, trình bày dữ liệu đó cho người dùng theo cách hữu ích và ngắn gọn.
Các ví dụ nổi bật về trợ lý cá nhân hỗ trợ tìm kiếm là Google Assistant , Siri , Alexa và Cortana . Những trợ lý này sử dụng hiệu quả khả năng tìm kiếm của họ để cung cấp thông tin chính xác và có liên quan, hoàn thành các nhiệm vụ và cải thiện phản hồi của họ khi họ tương tác với người dùng.
Anthropic là một tổ chức nghiên cứu tập trung vào phát triển các hệ thống AI tiên tiến. Sáng tạo mới nhất của họ, Claude, là một trợ lý AI thế hệ tiếp theo được thiết kế để trở nên hữu ích, trung thực và vô hại. Mô hình tiên tiến này đảm bảo độ tin cậy và khả năng dự đoán cao trong các nhiệm vụ khác nhau.
Các tính năng chính của Claude bao gồm:
Khả năng đàm thoại và xử lý văn bản linh hoạt
Duy trì sự an toàn và quyền riêng tư của người dùng là ưu tiên hàng đầu của nó
Các trường hợp sử dụng chính của Claude là:
tóm tắt
Tìm kiếm
Viết sáng tạo và hợp tác
hỏi đáp
hỗ trợ mã hóa
Những tính năng này làm cho Claude trở thành một công cụ AI lý tưởng cho nhiều ứng dụng, trao quyền cho người dùng trên các lĩnh vực khác nhau.
LangChain là một công cụ linh hoạt để xây dựng các ứng dụng mô hình ngôn ngữ đầu cuối. Nó cung cấp một khung mạnh mẽ giúp đơn giản hóa quá trình tạo, quản lý và triển khai các Mô hình học ngôn ngữ (LLM). LLM là các mô hình AI tiên tiến được thiết kế để hiểu, tạo và thao tác với văn bản giống con người bằng nhiều ngôn ngữ và tác vụ khác nhau.
Quản lý hiệu quả lời nhắc cho LLM
Khả năng tạo chuỗi nhiệm vụ cho quy trình công việc phức tạp
Thêm trạng thái vào AI, cho phép nó ghi nhớ thông tin từ các tương tác trước đó
Những khả năng này làm cho LangChain trở thành một nền tảng mạnh mẽ và thân thiện với người dùng để khai thác tiềm năng của các mô hình ngôn ngữ trong các ứng dụng đa dạng.
Cài đặt Flask : Để bắt đầu, hãy đảm bảo rằng bạn đã cài đặt Flask trong môi trường của mình. Bạn có thể làm điều này bằng cách sử dụng pip
:
pip install Flask
Tạo một thư mục mới : Tạo một thư mục mới cho dự án của bạn và điều hướng đến nó:
mkdir claude-langchain cd claude-langchain
Thiết lập môi trường ảo (tùy chọn) : Sử dụng môi trường ảo khi làm việc với các dự án Python là một cách thực hành tốt. Bạn có thể tạo một cái bằng venv
hoặc bất kỳ công cụ nào khác mà bạn chọn:
python -m venv venv source venv/bin/activate (Linux/Mac) venv\Scripts\activate (Windows)
Tạo tệp main.py
: Tạo tệp main.py
để viết mã ứng dụng Flask của bạn:
touch app.py # Linux/Mac echo.>app.py # Windows
Viết mã ứng dụng Flask của bạn : Mở tệp main.py
trong trình chỉnh sửa mã yêu thích của bạn và thêm mã sau:
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run()
Chạy ứng dụng Flask : Lưu tệp main.py
và chạy lệnh sau trong dấu nhắc lệnh/thiết bị đầu cuối của bạn:
python main.py
Mở trình duyệt của bạn : Mở trình duyệt web ưa thích của bạn và điều hướng đến http://127.0.0.1:5000/ . Bạn sẽ thấy "Xin chào, Thế giới!" hiển thị trên trang web.
Và thế là xong! Bạn đã khởi tạo thành công một dự án Flask và tạo một ứng dụng đơn giản.
Cài đặt python-dotenv và langchain : Để dễ dàng quản lý các biến môi trường bằng tệp .env
, chúng tôi sẽ sử dụng gói python-dotenv
. Đồng thời, hãy cài đặt langchain
. Cài đặt cả hai gói bằng pip
:
pip install python-dotenv langchain
Tạo tệp .env
: Tạo tệp .env
trong thư mục gốc của dự án của bạn:
touch .env # Linux/Mac echo.>.env # Windows
Thêm biến môi trường : Mở tệp .env
trong trình chỉnh sửa mã yêu thích của bạn và thêm biến môi trường của bạn. Mỗi biến phải nằm trên một dòng mới, ở định dạng KEY=VALUE:
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxx SERPAPI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Xin lưu ý rằng bạn cần có cả khóa API cho mô hình Claude của Anthropic và dịch vụ tìm kiếm trên web của SerpAPI .
Tải các biến môi trường : Sửa đổi tệp main.py
của bạn để tải các biến môi trường từ tệp .env
bằng cách sử dụng python-dotenv
. Cập nhật main.py
của bạn như sau:
import os from flask import Flask from dotenv import load_dotenv load_dotenv() app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run()
Bỏ qua tệp .env
trong kiểm soát phiên bản: Điều quan trọng là không chia sẻ thông tin nhạy cảm như khóa bí mật trong kiểm soát phiên bản. Nếu bạn đang sử dụng Git, hãy thêm dòng sau vào tệp .gitignore
của bạn (tạo một tệp nếu bạn không có):
.env
Bây giờ, dự án Flask của bạn được thiết lập để sử dụng các biến môi trường từ tệp .env
. Bạn có thể thêm nhiều biến hơn nếu cần và truy cập chúng bằng cách sử dụng os.environ.get('KEY')
. Hãy nhớ giữ tệp .env
ở chế độ riêng tư và không bao giờ chuyển nó sang kiểm soát phiên bản.
Hướng dẫn này được thiết kế cho người dùng trung cấp có hiểu biết cơ bản về Node.js, npm, React và Typescript. Chúng tôi sẽ sử dụng một ngăn xếp bao gồm các công nghệ này, cùng với Tailwind CSS để tạo kiểu. Ngăn xếp này được chọn vì tính mạnh mẽ, tính linh hoạt và sự hỗ trợ mạnh mẽ của cộng đồng đối với các công nghệ này. Ngoài ra, chúng tôi sẽ tích hợp mô hình Claude của Anthropic và LangChain, hai công nghệ AI mạnh mẽ sẽ cho phép ứng dụng của chúng tôi tạo ra phản hồi văn bản chính xác, giống con người.
Tải xuống trình cài đặt Node.js cho hệ điều hành của bạn từ trang web chính thức .
Làm theo lời nhắc cài đặt để cài đặt Node.js và npm. Phiên bản LTS (Hỗ trợ dài hạn) được khuyến nghị cho hầu hết người dùng.
Sau khi cài đặt, hãy xác minh cài đặt bằng cách kiểm tra các phiên bản của Node.js và npm từ thiết bị đầu cuối của bạn:
nút -v npm -v
Create React App (CRA) là một tiện ích dòng lệnh hỗ trợ chúng ta tạo một ứng dụng React.js mới. Chúng tôi sẽ cài đặt nó trên toàn cầu thông qua npm:
npm install -g create-react-app
Chúng tôi sẽ sử dụng CRA với mẫu Bản mô tả để tạo một dự án mới có tên ai-assistant-claude
.
npx create-react-app ai-assistant-claude --template typescript
Lệnh này tạo một thư mục mới có tên ai-assistant-claude
trong thư mục hiện tại của chúng ta, chứa một ứng dụng React mới có hỗ trợ Typescript.
Các bước tiếp theo trong hướng dẫn này dựa trên tài liệu CSS chính thức của Tailwind . Tham khảo các tài liệu này để biết thêm hướng dẫn cập nhật.
Chúng ta sẽ bắt đầu bằng cách cài đặt TailwindCSS và khởi tạo thư viện trong dự án của mình:
npm install -D tailwindcss npx tailwindcss init
Tiếp theo, chúng tôi định cấu hình đường dẫn mẫu của mình bằng cách thêm chúng vào tệp tailwind.config.js
. Dấu ++
biểu thị các dòng bạn sẽ thêm:
/** @type {import('tailwindcss').Config} */ module.exports = { -- content: [], ++ content: [ ++ "./src/**/*.{js,jsx,ts,tsx}", ++ ], theme: { extend: {}, }, plugins: [], }
Cuối cùng, chúng ta sẽ thêm các lệnh @tailwind
cho từng lớp của Tailwind vào tệp ./src/index.css
của mình:
@tailwind base; @tailwind components; @tailwind utilities;
Và Voila! Tailwind CSS hiện đã được tích hợp vào dự án của chúng tôi.
Trước khi chúng ta chuyển sang phần viết mã, hãy hoàn tất việc chuẩn bị của chúng ta bằng cách cài đặt các thư viện cần thiết như fontawesome
, react-markdown
, axios
và react-hook-form
.
npm i --save @fortawesome/fontawesome-svg-core npm install --save @fortawesome/free-solid-svg-icons npm install --save @fortawesome/react-fontawesome
npm install --save react-markdown
Sau khi hoàn thành các bước này, dự án của bạn đã được thiết lập với tất cả các công cụ và thư viện cần thiết. Tiếp theo, chúng ta sẽ bắt đầu xây dựng ứng dụng trợ lý AI sử dụng Claude API và LangChain của Anthropic để tạo phản hồi văn bản chính xác và giống con người.
Nếu bạn gặp phải bất kỳ sự cố nào trong quá trình cài đặt hoặc thiết lập, đây là một vài giải pháp phổ biến:
Đối với bất kỳ sự cố nào khác, hãy tham khảo tài liệu của các thư viện tương ứng hoặc đăng sự cố của bạn lên StackOverflow hoặc kho lưu trữ GitHub có liên quan.
Trong phần này, chúng ta sẽ quay lại ứng dụng Flask mà chúng ta đã khởi tạo trước đó và thêm các điểm cuối mới, chẳng hạn như /ask
và /search
. Các tính năng này sẽ đóng vai trò là điểm cuối cho các tính năng trò chuyện nâng cao và trò chuyện đơn giản của chúng tôi (tính năng trò chuyện nâng cao được cung cấp bởi kết quả tìm kiếm của Google).
Hãy bắt đầu với việc nhập các mô-đun cần thiết của chúng tôi:
from flask import Flask, jsonify, request from dotenv import load_dotenv from langchain.chat_models import ChatAnthropic from langchain.chains import ConversationChain from langchain.agents import Tool from langchain.agents import AgentType from langchain.utilities import SerpAPIWrapper from langchain.agents import initialize_agent from langchain.memory import ConversationBufferMemory from langchain.prompts.chat import ( ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate, ) load_dotenv() app = Flask(__name__)
Phần trên nhập tất cả các gói cần thiết và khởi tạo ứng dụng Flask của chúng tôi.
Chúng tôi sẽ bắt đầu bằng cách tạo một điểm cuối cơ bản ( /
) để kiểm tra xem máy chủ của chúng tôi có chạy chính xác không:
@app.route('/') def hello_world(): return 'Hello, World!'
Bằng cách truy cập URL gốc, chúng tôi sẽ nhận được phản hồi "Xin chào, Thế giới!", Cho biết rằng máy chủ của chúng tôi đang chạy như mong đợi.
/ask
Điểm cuốiĐiểm cuối này xử lý tin nhắn cho tính năng trò chuyện cơ bản của ứng dụng của chúng tôi. Nó đọc dữ liệu JSON từ yêu cầu, xử lý thông báo và tạo phản hồi bằng cách sử dụng mô hình Claude và LangChain của Anthropic.
@app.route('/ask', methods=['POST']) def ask_assistant(): # The code for /ask endpoint goes here
Trích xuất tin nhắn từ yêu cầu
Trước tiên, chúng tôi cần kiểm tra xem có dữ liệu nào được cung cấp hay không và trích xuất các thông báo từ dữ liệu đó.
data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message")
Tạo phản hồi
Đoạn mã sau tạo phản hồi trò chuyện bằng cách sử dụng mô hình ChatAnthropic()
của LangChain và ChatPromptTemplate
để cấu trúc cuộc trò chuyện của chúng ta. Lịch sử hội thoại được lưu trữ bằng ConversationBufferMemory
.
llm = ChatAnthropic() input = "" message_list = [] for message in messages: if message['role'] == 'user': message_list.append( HumanMessagePromptTemplate.from_template(message['content']) ) input = message['content'] elif message['role'] == 'assistant': message_list.append( AIMessagePromptTemplate.from_template(message['content']) ) # Adding SystemMessagePromptTemplate at the beginning of the message_list message_list.insert(0, SystemMessagePromptTemplate.from_template( "The following is a friendly conversation between a human and an AI. The AI is talkative and " "provides lots of specific details from its context. The AI will respond with plain string, replace new lines with \\n which can be easily parsed and stored into JSON, and will try to keep the responses condensed, in as few lines as possible." )) message_list.insert(1, MessagesPlaceholder(variable_name="history")) message_list.insert(-1, HumanMessagePromptTemplate.from_template("{input}")) prompt = ChatPromptTemplate.from_messages(message_list) memory = ConversationBufferMemory(return_messages=True) conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm) result = conversation.predict(input=input)
Sau khi tạo phản hồi, chúng tôi thay thế các dòng mới trong kết quả và trả về dưới dạng đối tượng JSON.
print(result) return jsonify({"status": "success", "message": result})
/search
Endpoint Điểm cuối /search
tương tự như /ask
nhưng bao gồm chức năng tìm kiếm để cung cấp các câu trả lời chi tiết hơn. Chúng tôi sử dụng SerpAPIWrapper
để thêm tính năng này.
@app.route('/search', methods=['POST']) def search_with_assistant(): data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message") llm = ChatAnthropic() # Get the last message with 'user' role user_messages = [msg for msg in messages if msg['role'] == 'user'] last_user_message = user_messages[-1] if user_messages else None # If there is no user message, return an error response if not last_user_message: return jsonify({"error": "No user message found"}), 400 input = last_user_message['content'] search = SerpAPIWrapper() tools = [ Tool( name = "Current Search", func=search.run, description="useful for when you need to answer questions about current events or the current state of the world" ), ] chat_history = MessagesPlaceholder(variable_name="chat_history") memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) agent_chain = initialize_agent( tools, llm, agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True, memory=memory, agent_kwargs = { "memory_prompts": [chat_history], "input_variables": ["input", "agent_scratchpad", "chat_history"] } ) result = agent_chain.run(input=input) print(result) return jsonify({"status": "success", "message": result})
Cuối cùng, chúng tôi thêm bản soạn sẵn tiêu chuẩn để chạy ứng dụng Flask của mình.
if __name__ == '__main__': app.run()
Nếu mọi thứ suôn sẻ, đây là mã phụ trợ cuối cùng của chúng tôi.
from flask import Flask, jsonify, request from dotenv import load_dotenv from langchain.chat_models import ChatAnthropic from langchain.chains import ConversationChain from langchain.agents import Tool from langchain.agents import AgentType from langchain.utilities import SerpAPIWrapper from langchain.agents import initialize_agent from langchain.memory import ConversationBufferMemory from langchain.prompts.chat import ( ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate, ) load_dotenv() app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' @app.route('/ask', methods=['POST']) def ask_assistant(): data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message") llm = ChatAnthropic() input = "" message_list = [] for message in messages: if message['role'] == 'user': message_list.append( HumanMessagePromptTemplate.from_template(message['content']) ) input = message['content'] elif message['role'] == 'assistant': message_list.append( AIMessagePromptTemplate.from_template(message['content']) ) # Adding SystemMessagePromptTemplate at the beginning of the message_list message_list.insert(0, SystemMessagePromptTemplate.from_template( "The following is a friendly conversation between a human and an AI. The AI is talkative and " "provides lots of specific details from its context. The AI will respond with plain string, replace new lines with \\n which can be easily parsed and stored into JSON, and will try to keep the responses condensed, in as few lines as possible." )) message_list.insert(1, MessagesPlaceholder(variable_name="history")) message_list.insert(-1, HumanMessagePromptTemplate.from_template("{input}")) prompt = ChatPromptTemplate.from_messages(message_list) memory = ConversationBufferMemory(return_messages=True) conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm) result = conversation.predict(input=input) print(result) return jsonify({"status": "success", "message": result}) @app.route('/search', methods=['POST']) def search_with_assistant(): data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message") llm = ChatAnthropic() # Get the last message with 'user' role user_messages = [msg for msg in messages if msg['role'] == 'user'] last_user_message = user_messages[-1] if user_messages else None # If there is no user message, return an error response if not last_user_message: return jsonify({"error": "No user message found"}), 400 input = last_user_message['content'] search = SerpAPIWrapper() tools = [ Tool( name = "Current Search", func=search.run, description="useful for when you need to answer questions about current events or the current state of the world" ), ] chat_history = MessagesPlaceholder(variable_name="chat_history") memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) agent_chain = initialize_agent( tools, llm, agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True, memory=memory, agent_kwargs = { "memory_prompts": [chat_history], "input_variables": ["input", "agent_scratchpad", "chat_history"] } ) result = agent_chain.run(input=input) print(result) return jsonify({"status": "success", "message": result}) if __name__ == '__main__': app.run()
Bây giờ, hãy kiểm tra ứng dụng của chúng tôi. Chạy ứng dụng phụ trợ bằng lệnh này, đồng thời vui lòng đảm bảo rằng môi trường ảo của chúng tôi đã được kích hoạt.
flask run
Chúng tôi sẽ biết mọi thứ sẽ ổn nếu thiết bị đầu cuối của chúng tôi trả về đầu ra này.
Nếu không có gì khó chịu, hãy kiểm tra hai điểm cuối của chúng tôi /ask
và /search
. Để phân biệt giữa hai loại này, hãy gửi tải trọng tương tự cho từng loại. Mở phần mềm tài liệu và thử nghiệm API REST của bạn hoặc chỉ sử dụng cURL. Nhưng trong hướng dẫn này, tôi sẽ sử dụng Insomnia .
Trước tiên, hãy gọi điểm cuối /ask
với tải trọng sau. Giả sử tôi đã hỏi về phần tiếp theo của trò chơi điện tử " The Legend of Zelda: Breath of the Wild ". Mà nó sẽ trả lời sai. Điều này có thể được mong đợi vì mô hình đã bị cắt đào tạo vào cuối năm 2021, trong đó vẫn chưa có thông báo nào về phần tiếp theo.
Còn điểm cuối /search
thì sao? Nếu bạn nhận thấy mã của chúng tôi từ trước đó, thì điểm cuối này được xử lý bằng một chuỗi phức tạp hơn, sử dụng Tác nhân.
Bằng cách sử dụng Tác nhân, chúng tôi có thể trao cho AI nhiều quyền hơn trong việc ra quyết định, bằng cách cung cấp cho nó nhiều công cụ hơn là chỉ mô hình của chính nó, như chúng tôi đã chứng minh, có những sai sót riêng.
Để minh họa cách thức hoạt động của /search
endpoint, hãy gọi nó với cùng tải trọng như trước đây.
Lần này, nó hoạt động tốt! Làm thế nào để nó hoạt động dưới mui xe? hãy nhìn vào đầu ra trong thiết bị đầu cuối của chúng tôi.
Khá thú vị, lần này mô hình AI không trả lời ngay mà dường như đang “suy ngẫm” về những việc cần làm để trả lời. Nó quyết định trả lời sau khi đưa ra quan sát từ kết quả tìm kiếm trên web rằng "Dựa trên tìm kiếm tin tức, có vẻ như phần tiếp theo có tên The Legend of Zelda: Tears of the Kingdom đã được công bố".
Vì vậy, theo logic đó, chúng ta có nên sử dụng điểm cuối /search
không? vì nó chính xác hơn, và do đó, thông minh hơn? Không hẳn. Thông thường, trong ứng dụng dựa trên chatbot, bot phải giữ lại ngữ cảnh của các cuộc trò chuyện trước đó để có thể đưa ra phản hồi như thể nó 'nhớ' toàn bộ ngữ cảnh của cuộc trò chuyện. Hãy xem liệu /search
endpoint có thể làm được điều đó không.
Hãy kiểm tra xem điểm cuối /search
có thể nhớ lại những gì chúng ta vừa hỏi không.
Điều này xảy ra bởi vì mặc dù thư viện LangChain có tính năng chuỗi bộ nhớ có thể được sử dụng để lưu giữ các cuộc hội thoại trước đây, máy chủ web và dịch vụ API REST được xây dựng trên nó vốn không có trạng thái. Điều đó có nghĩa là, máy chủ web sẽ coi mỗi yêu cầu là một yêu cầu mới.
Nhưng, chẳng phải chúng ta đã bao gồm các cuộc trò chuyện trước đó dưới dạng tải trọng sao? Đó là một câu hỏi hay. Hóa ra, chuỗi Đại lý được sử dụng trong LangChain hiện không hỗ trợ xử lý các lời nhắc tổng hợp, bao gồm cả yêu cầu và phản hồi của cả người dùng và AI. Chúng tôi chủ yếu sử dụng điều này để cung cấp các cuộc hội thoại ví dụ cho mô hình, tiếp tục điều chỉnh mô hình theo phản hồi mong muốn của chúng tôi.
Mặt khác, Tác nhân hoạt động bằng cách nhận một hướng dẫn duy nhất và phát triển các chuỗi suy nghĩ xung quanh nó. Đó là lý do tại sao, bất kể cuộc trò chuyện của chúng tôi kéo dài bao lâu, đại lý sẽ chỉ trả lời yêu cầu mới nhất.
Hãy kiểm tra điểm cuối /ask
của chúng tôi để nhận thấy sự khác biệt.
Lần này, nó đã trả lời bằng cách sử dụng các cuộc trò chuyện trước đây của chúng tôi dưới dạng ngữ cảnh bổ sung! Đến bây giờ, chúng tôi nhận ra rằng chúng tôi cần cả hai điểm cuối để xây dựng ứng dụng Trợ lý AI của mình. Nhưng làm thế nào để chúng ta kết hợp cả điểm cuối lỗi thời nhưng luôn ghi nhớ /ask
với điểm cuối hay quên nhưng kỹ lưỡng và cập nhật /search
? Tất nhiên là bằng cách xây dựng giao diện người dùng!
Hãy quay trở lại thư mục dự án ai-assistant-claude
. Trong dự án này, chúng tôi sẽ bắt đầu sửa đổi các thành phần React của mình, bắt đầu với tệp điểm đầu vào, App.tsx
.
import React from 'react'; import logo from './logo.svg'; import './App.css'; import { ChatClient } from './ChatClient'; function App() { return ( <div> <ChatClient/> </div> ); } export default App;
Trong đoạn mã trên, chúng tôi nhập thành phần ChatClient
, thành phần này sẽ được tạo trong bước tiếp theo. Sau đó, chúng tôi đưa thành phần <ChatClient/>
vào trong thành phần <div>
. Điều này thiết lập cấu trúc cho ứng dụng trợ lý AI của chúng tôi bằng cách sử dụng các thành phần React.
import React, { useState } from 'react'; import { ChatInput } from './ChatInput'; import { ChatHistory } from './ChatHistory'; export interface Message { content: string; role: string; } export const ChatClient: React.FC = () => { const [messages, setMessages] = useState<Array<Message>>([]); const [isLoading, setIsLoading] = useState(false) const handleSimpleChat = (message: string) => { // Send the message and past chat history to the backend // Update messages state with the new message let newMessages = [...messages, { content: message, role: 'user' }] setMessages(newMessages); let postData = { message: newMessages } setIsLoading(true) fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postData), }) .then((response) => response.json()) .then((data) => { if (data.status === "success") { setMessages([...newMessages, { content: data.message, role: 'assistant' }]) } setIsLoading(false) console.log('Success:', data); }) .catch((error) => { console.error('Error:', error); setIsLoading(false) }); }; const handleAdvancedChat = (message: string) => { // Trigger AI agent with Google Search functionality // Update messages state with the new message and AI response let newMessages = [...messages, { content: message, role: 'user' }] setMessages(newMessages); let postData = { message: newMessages } setIsLoading(true) fetch('/search', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postData), }) .then((response) => response.json()) .then((data) => { if (data.status === "success") { setMessages([...newMessages, { content: data.message, role: 'assistant' }]) } console.log('Success:', data); setIsLoading(false) }) .catch((error) => { console.error('Error:', error); setIsLoading(false) }); }; return ( <div className="h-screen bg-gray-100 dark:bg-gray-900 flex items-center justify-center"> <div className='flex flex-col items-center gap-2'> <h1 className='text-white text-xl'>AI Assistant with Claude and LangChain</h1> <div className="w-full max-w-md h-[80vh] bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden flex flex-col"> <ChatHistory messages={messages} isLoading={isLoading} /> <ChatInput onSimpleChat={handleSimpleChat} onAdvancedChat={handleAdvancedChat} /> </div> </div> </div> ); };
Thành phần này đóng vai trò là giao diện người dùng chính cho trợ lý AI của chúng tôi. Như được hiển thị, nó kết hợp thành phần ChatHistory
để hiển thị lịch sử hội thoại và thành phần ChatInput
để nhập văn bản.
Thành phần xử lý đầu vào từ thành phần ChatInput
, gửi yêu cầu đến phần phụ trợ bằng đầu vào này, sau đó hiển thị trạng thái tải. Nếu yêu cầu được xử lý thành công, thành phần này cũng sẽ hiển thị phản hồi nhận được từ phần phụ trợ.
import React from 'react'; import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; import { Message } from './ChatClient'; interface ChatHistoryProps { messages: Array<Message>; isLoading: boolean } export const ChatHistory: React.FC<ChatHistoryProps> = ({ messages, isLoading }) => { return ( <div className="p-4 h-full overflow-y-auto"> {messages.map((message, index) => ( <div key={index} className={`mb-3 ${ message.role === 'user' ? 'text-right' : 'text-left' }`} > <ReactMarkdown className={`inline-block px-3 py-2 rounded-md ${ index % 2 === 0 ? 'bg-gray-300 dark:bg-gray-700' : 'bg-blue-200 dark:bg-blue-900' }`} > {message.content} </ReactMarkdown> </div> ))} {isLoading && ( <div className="mb-3 text-left"> <ReactMarkdown className="inline-block px-3 py-2 rounded-md bg-blue-200 dark:bg-blue-900 animate-pulse" > {/* Put your desired loading content here */} Thinking... </ReactMarkdown> </div> )} </div> ); };
May mắn thay, TailwindCSS cung cấp các lớp tiện ích tích hợp sẵn cho hoạt ảnh đơn giản, chẳng hạn như animate-pulse
. Lớp này giúp chỉ ra rằng một yêu cầu đang chờ phản hồi. Trong thành phần này, chúng tôi cũng phân biệt vị trí của thông báo từ "người dùng" và "trợ lý".
import React, { useState } from 'react'; interface ChatInputProps { onSimpleChat: (message: string) => void; onAdvancedChat: (message: string) => void; } export const ChatInput: React.FC<ChatInputProps> = ({ onSimpleChat, onAdvancedChat }) => { const [input, setInput] = useState(''); const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { setInput(event.target.value); }; const handleSubmit = (handler: (message: string) => void) => { handler(input); setInput(''); }; return ( <div className="flex p-4 border-t border-gray-200 dark:border-gray-700"> <input type="text" value={input} onChange={handleInputChange} placeholder="Type your message..." className="flex-grow px-3 py-2 rounded-md bg-gray-200 text-gray-900 dark:bg-gray-700 dark:text-gray-100 focus:outline-none" /> <button onClick={() => handleSubmit(onSimpleChat)} className="ml-2 px-4 py-2 font-semibold text-gray-600 bg-white dark:text-gray-400 dark:bg-gray-800 border border-gray-300 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none" > Ask </button> <button onClick={() => handleSubmit(onAdvancedChat)} className="ml-2 px-4 py-2 font-semibold text-white bg-blue-500 border border-blue-600 rounded-md hover:bg-blue-400 focus:outline-none" > Ask and Search </button> </div> ); };
Cuối cùng, chúng tôi đã thêm hai nút vào kiểu nhập văn bản của mình. Nút đầu tiên được sử dụng để gửi dữ liệu đầu vào đến điểm cuối /ask
, điểm cuối này sẽ xử lý dữ liệu đầu vào bằng mô hình AI mà không cần bất kỳ cải tiến bổ sung nào.
Điểm cuối này nhận biết ngữ cảnh. Nút thứ hai, được đặt tên một cách khéo léo là "Hỏi và Tìm kiếm", sẽ gửi thông tin đầu vào đến điểm cuối /search
. Ngoài việc xử lý đầu vào thông qua mô hình AI, nút này cũng kích hoạt tìm kiếm trên web do AI điều khiển nếu tình huống yêu cầu.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <!-- manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <!-- Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. Only files inside the `public` folder can be referenced from the HTML. Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> -- <title>React App</title> ++ <title>Claude AI Assistant</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <!-- This HTML file is a template. If you open it directly in the browser, you will see an empty page. You can add webfonts, meta tags, or analytics to this file. The build step will place the bundled scripts into the <body> tag. To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> </body> </html>
Để hoàn thiện, chúng tôi cập nhật tiêu đề của ứng dụng trong trang index.html
bằng cách thay đổi nó từ " React App " thành " Claude AI Assistant ".
{ "name": "ai-assistant-claude", "version": "0.1.0", "private": true, ++ "proxy": "http://localhost:5000", "dependencies": {
Cuối cùng, chúng tôi thêm cấu hình proxy vào tệp package.json
và đặt thành http://localhost:5000
. Điều này giúp chúng tôi bỏ qua các giới hạn CORS phát sinh từ việc sử dụng các cổng khác nhau.
Để bắt đầu thử nghiệm, trước tiên hãy đảm bảo rằng ứng dụng phụ trợ (claude-langchain) đang chạy.
Tiếp theo, thay đổi thư mục thành ứng dụng giao diện người dùng (ai-assistant-claude) và khởi động ứng dụng bằng lệnh sau:
npm start
Ứng dụng có thể mất một chút thời gian để xây dựng. Khi đã sẵn sàng, nó sẽ tự động mở trong trình duyệt của bạn tại localhost:3000
.
Hãy kiểm tra cả tính năng tìm kiếm và nhận biết ngữ cảnh! Trước tiên, hãy hỏi về một trò chơi điện tử khác vẫn chưa được công bố phát hành vào năm 2021 – phần tiếp theo của trò chơi Yakuza mới nhất của SEGA . Ngoài ra, chúng tôi sẽ hỏi liệu trò chơi này có nhân vật được yêu thích Kazuma Kiryu từ các trò chơi trước hay không. Để thực hiện việc này, hãy nhấp vào nút " Hỏi ".
Cho vài giây suy nghĩ...
Đáng ngạc nhiên, AI đã trả lời sai. Yakuza: Like a Dragon thực sự là trò chơi Yakuza mới nhất nhưng có sự tham gia của một nhân vật chính khác, Ichiban Kasuga. Hãy diễn đạt lại câu hỏi và sử dụng nút " Hỏi và Tìm kiếm " lần này.
Giờ đây, AI mất nhiều thời gian hơn để quyết định xem nó có cần tìm kiếm trên web hay không. Sau khi xác định rằng tìm kiếm trên web là cần thiết và tìm thấy câu trả lời thỏa đáng, nó sẽ trả lại thông tin cho khách hàng/giao diện người dùng.
Lần này, nó trả lại tiêu đề "Like a Dragon Gaiden: The Man Who Erased His Name", trong đó thực sự có Kazuma Kiryu là nhân vật chính.
Hấp dẫn, phải không? Tác nhân, được cung cấp bởi Mô hình ngôn ngữ lớn, dự đoán những hành động nào là cần thiết với các tài nguyên có sẵn (tìm kiếm) và đưa ra câu trả lời thỏa đáng. LangChain giúp dễ dàng kết nối và "xâu chuỗi" mô hình, lời nhắc (hướng dẫn về mô hình) và các tính năng khác như tác nhân và công cụ.
Chúng tôi hy vọng bạn thích xây dựng ứng dụng này và tìm hiểu về những công nghệ này. Đối với những người muốn tìm hiểu sâu hơn, bạn có thể truy cập dự án đã hoàn thành cho phần đầu và phần phụ trợ .
Và nếu bạn muốn xây dựng bằng những công nghệ mới nhất do các đối tác của chúng tôi hiện thực hóa, hãy tham gia cùng chúng tôi trong thử thách AI tiếp theo!
*please see lablab.ai for all terms and conditions