Làm chủ OpenAI Agent (Phần 2): Tìm hiểu sâu về Agent, Runner và xử lý kết quả

Viewed 4

Chia sẻ bởi Tù Bà Khuỳm

Chào mừng bạn quay trở lại với series khám phá OpenAI Agent! Trong phần trước, chúng ta đã có cái nhìn tổng quan về khái niệm AI Agent. Hôm nay, chúng ta sẽ đi sâu hơn vào các thành phần cốt lõi, bao gồm cách tạo và cấu hình đối tượng Agent, các phương thức thực thi agent thông qua Runner, và quan trọng nhất là cách hiểu và tận dụng đối tượng kết quả RunResult. Mục tiêu là giúp bạn nắm vững cách xây dựng, chạy và quản lý luồng hoạt động của một OpenAI Agent một cách hiệu quả.

Tìm hiểu lớp Agent: Định nghĩa vai trò và năng lực cho AI

Trong hệ sinh thái OpenAI Agent, lớp Agent chính là viên gạch nền tảng để xây dựng nên các trợ lý AI thông minh. Việc cấu hình đúng và chi tiết cho đối tượng Agent sẽ quyết định trực tiếp đến hiệu suất và khả năng của nó.

Các tham số cấu hình chính của Agent

Một đối tượng Agent được khởi tạo với một số tham số quan trọng:

  1. name (Tên định danh):
    • Đây là một chuỗi ký tự giúp bạn đặt tên và phân biệt các agent khác nhau, đặc biệt hữu ích khi bạn xây dựng các quy trình phức tạp liên quan đến nhiều agent.
  2. instruction (Chỉ dẫn cốt lõi):
    • Đây là phần quan trọng nhất. Nó là kim chỉ nam xác định vai trò, nhiệm vụ cụ thể, và các quy tắc hoạt động mà agent phải tuân theo. Một instruction càng rõ ràng, chi tiết và mạch lạc thì agent hoạt động càng hiệu quả và đúng mục đích.
  3. model (Mô hình ngôn ngữ):
    • Bạn cần chọn một mô hình ngôn ngữ lớn (LLM) từ OpenAI để cung cấp sức mạnh xử lý ngôn ngữ cho agent. Việc lựa chọn model phụ thuộc vào độ phức tạp của tác vụ:
      • Tác vụ phức tạp, cần suy luận sâu: Nên chọn các model mạnh như gpt-4 hoặc gpt-4-turbo.
      • Tác vụ đơn giản, cần tốc độ và chi phí tối ưu: Có thể sử dụng các model như gpt-3.5-turbo hoặc các phiên bản nhẹ hơn.
    • (Lưu ý: Việc sử dụng các model từ bên thứ ba sẽ được đề cập trong một bài viết khác).
  4. tools (Công cụ - Tùy chọn):
    • Đây là danh sách các công cụ (functions) mà agent được phép sử dụng để thực hiện các tác vụ vượt ra ngoài khả năng xử lý ngôn ngữ thông thường (ví dụ: truy cập API, tìm kiếm web, tính toán). Phần này sẽ được tìm hiểu kỹ hơn ở bài sau.
  5. handoff (Chuyển giao - Tùy chọn):
    • Danh sách các agent khác mà agent hiện tại có thể chuyển giao nhiệm vụ khi cần thiết. Đây là cơ chế quan trọng trong việc xây dựng các hệ thống multi-agent phức tạp.
  6. output_type (Định dạng đầu ra - Tùy chọn):
    • Cho phép bạn định nghĩa cấu trúc dữ liệu mong muốn cho kết quả đầu ra của agent. Nếu không khai báo, kết quả mặc định sẽ là một chuỗi (string). Tính năng này tương tự như "structured output" trong các thư viện khác, giúp bạn nhận về dữ liệu có cấu trúc rõ ràng (ví dụ: một đối tượng Python, JSON).

Tầm quan trọng của instruction chi tiết

Một instruction mơ hồ, thiếu chi tiết có thể dẫn đến hành vi không mong muốn và khó kiểm soát của agent. Một chỉ dẫn tốt thường bao gồm các yếu tố sau:

  • Vai trò (Role): Agent đóng vai trò gì? (Ví dụ: "Bạn là một trợ lý dịch thuật chuyên nghiệp.")
  • Nhiệm vụ chính (Task): Agent cần thực hiện công việc gì? (Ví dụ: "Nhiệm vụ chính của bạn là dịch văn bản giữa tiếng Anh và tiếng Việt một cách chính xác và tự nhiên.")
  • Quy tắc và giới hạn (Rules/Constraints): Các quy tắc cụ thể agent phải tuân theo. (Ví dụ: "Nếu người dùng cung cấp văn bản tiếng Anh, hãy dịch sang tiếng Việt. Nếu là tiếng Việt, dịch sang tiếng Anh. Chỉ trả về nội dung đã dịch, không thêm lời giải thích nào khác.")
  • Định dạng đầu ra (Output Format - nếu cần): Mô tả cách agent nên trình bày kết quả.

Ví dụ về instruction chi tiết cho agent dịch thuật:

Bạn là một trợ lý dịch thuật chuyên nghiệp.
Nhiệm vụ chính của bạn là dịch văn bản giữa tiếng Anh và tiếng Việt một cách chính xác và tự nhiên.
Quy tắc:
- Nếu người dùng cung cấp văn bản bằng tiếng Anh, hãy dịch sang tiếng Việt.
- Nếu người dùng cung cấp văn bản bằng tiếng Việt, hãy dịch sang tiếng Anh.
- Chỉ trả về nội dung văn bản đã dịch, không thêm bất kỳ lời giải thích hay định dạng nào khác.

Một instruction được cấu trúc tốt như trên sẽ giúp OpenAI Agent hiểu rõ nhiệm vụ và hoạt động một cách đáng tin cậy hơn.

Lớp Runner: Trái tim thực thi của OpenAI Agent

Sau khi đã định nghĩa Agent, chúng ta cần một cơ chế để thực thi nó. Đó chính là nhiệm vụ của lớp Runner. Runner chịu trách nhiệm quản lý toàn bộ vòng đời của một lần chạy agent, bao gồm việc giao tiếp với LLM, gọi các công cụ (nếu có), và xử lý việc chuyển giao (handoff).

Lớp Runner cung cấp các phương thức khác nhau để chạy agent, phù hợp với các nhu cầu và kịch bản sử dụng khác nhau.

Các phương thức chạy Agent: run_sync, run (async), và run_stream

Có ba cách chính để thực thi một OpenAI Agent thông qua Runner:

1. run_sync: Chạy đồng bộ

  • Cách hoạt động: Khi bạn gọi runner.run_sync(...), chương trình của bạn sẽ dừng lại và chờ cho đến khi agent hoàn thành toàn bộ quá trình xử lý và trả về kết quả cuối cùng. Chỉ sau khi có kết quả, các dòng code tiếp theo mới được thực thi.
  • Trường hợp sử dụng: Phù hợp cho các script đơn giản, các tác vụ chạy nền không yêu cầu tương tác tức thì, hoặc khi bạn cần đảm bảo một tác vụ agent phải hoàn thành trước khi thực hiện bước tiếp theo.
  • Ví dụ (minh họa ý tưởng): Script bắt đầu -> Gọi run_sync để dịch "Hello world" -> Script dừng lại chờ -> Agent xử lý, trả về "Xin chào thế giới" -> Script nhận kết quả -> In kết quả và các log tiếp theo. Toàn bộ quá trình này có thể mất vài giây.

2. run: Chạy bất đồng bộ (Async)

  • Cách hoạt động: Phương thức runner.run(...) được thiết kế để chạy bất đồng bộ, thường kết hợp với thư viện asyncio của Python. Khi bạn gọi phương thức này (sử dụng await), chương trình sẽ không bị chặn. Nó có thể tiếp tục thực hiện các tác vụ khác trong khi chờ agent xử lý ở chế độ nền.
  • Trường hợp sử dụng: Rất quan trọng cho các ứng dụng cần độ phản hồi cao như web application, chatbot, hoặc khi cần xử lý đồng thời nhiều yêu cầu. Nó cũng hữu ích khi agent cần gọi các API bên ngoài hoặc thực hiện các tác vụ tốn thời gian mà không muốn làm "đơ" luồng chính của ứng dụng.
  • Ví dụ (minh họa ý tưởng): Sử dụng asyncio để chạy đồng thời hai tác vụ:
    • Tác vụ 1: await runner.run(...) để dịch một câu dài.
    • Tác vụ 2: Một vòng lặp in ra log "Đang xử lý tác vụ khác..." mỗi giây.
    • Kết quả: Bạn sẽ thấy log từ Tác vụ 2 xuất hiện ngay lập tức và lặp lại, trong khi Tác vụ 1 (agent dịch thuật) vẫn đang chạy ngầm. Khi Tác vụ 1 hoàn thành, kết quả dịch mới được trả về mà không làm gián đoạn Tác vụ 2.

3. run_stream: Chạy bất đồng bộ và nhận kết quả từng phần (Streaming)

  • Cách hoạt động: Đây có lẽ là phương thức thú vị và hữu ích nhất cho trải nghiệm người dùng. runner.run_stream(...) cũng chạy bất đồng bộ, nhưng thay vì chờ đến kết quả cuối cùng, nó cho phép bạn nhận được các "mảnh" thông tin (stream events) liên tục trong suốt quá trình agent hoạt động.
  • Trường hợp sử dụng: Lý tưởng cho các giao diện người dùng tương tác (như chatbot). Nó tạo ra hiệu ứng chữ chạy giống như khi bạn dùng ChatGPT, giúp người dùng cảm thấy quá trình xử lý đang diễn ra và không phải chờ đợi quá lâu trong im lặng. Bạn có thể hiển thị từng phần của câu trả lời ngay khi nó được tạo ra.
  • Ví dụ (minh họa ý tưởng): Gọi runner.run_stream(...) để dịch một đoạn văn bản dài. Thay vì đợi vài giây để nhận toàn bộ bản dịch, bạn sẽ nhận được từng từ hoặc cụm từ của bản dịch ngay khi agent tạo ra chúng. Bạn có thể lặp qua các stream event này và hiển thị chúng lên màn hình theo thời gian thực.

So sánh ví von: Xem bóng đá

Hãy tưởng tượng việc chạy agent giống như xem một trận đấu bóng đá:

  • run_syncrun (async): Giống như bạn chỉ biết kết quả cuối cùng của trận đấu (ví dụ: tỷ số 2-1) sau khi 90 phút kết thúc. Bạn không biết diễn biến, ai ghi bàn, khi nào.
  • run_stream: Giống như bạn đang xem trận đấu trực tiếp với bình luận viên. Bạn thấy từng pha bóng, bàn thắng được ghi ngay khi nó xảy ra, thẻ phạt được rút ra. Thông tin đến với bạn liên tục, theo đúng diễn biến thực tế. Khi áp dụng vào OpenAI Agent, run_stream cho phép bạn "thấy" agent đang nghĩ gì, chuẩn bị gọi công cụ nào, kết quả của công cụ đó ra sao, và đang soạn câu trả lời như thế nào.

Giải mã đối tượng RunResult: Khai thác kết quả và lịch sử chạy

Khi bạn thực thi một agent bằng các phương thức của Runner (run_sync, run, run_stream), bạn sẽ nhận lại một đối tượng chứa thông tin chi tiết về quá trình thực thi đó.

  • Với run_syncrun (async), bạn nhận về đối tượng RunResult.
  • Với run_stream, bạn nhận về đối tượng RunResultStream.

Cả hai lớp này đều kế thừa từ một lớp cơ sở và chứa nhiều thông tin hữu ích. Việc hiểu rõ các thuộc tính của đối tượng này là rất quan trọng để khai thác tối đa khả năng của agent.

Các thuộc tính quan trọng của RunResult / RunResultStream

  1. final_output:
    • Đây là kết quả cuối cùng mà agent (hoặc agent cuối cùng trong chuỗi handoff) tạo ra. Thường là câu trả lời bạn mong muốn cung cấp cho người dùng cuối.
    • Kiểu dữ liệu của final_output phụ thuộc vào cấu hình output_type của agent cuối cùng. Nếu không định nghĩa, nó sẽ là string. Nếu có định nghĩa (ví dụ: Pydantic model), nó sẽ là một đối tượng có cấu trúc đó.
  2. last_agent:
    • Tham chiếu đến đối tượng Agent đã tạo ra final_output.
    • Trong trường hợp chỉ có một agent, đây chính là agent đó.
    • Trong trường hợp có handoff, thuộc tính này cho biết agent nào trong chuỗi đã chịu trách nhiệm xử lý và đưa ra kết quả cuối cùng. Điều này hữu ích để giữ lại agent chuyên biệt đó cho lần tương tác tiếp theo, thay vì quay lại agent điều phối ban đầu.
  3. run_items (Đặc biệt hữu ích với run_stream):
    • Đây là danh sách các bước trung gian (run item) đã diễn ra trong quá trình agent hoạt động. Mỗi item đại diện cho một sự kiện cụ thể. Một số loại run_item phổ biến:
      • MessageOutputItem: Tin nhắn phản hồi từ LLM.
      • ToolCallItem: LLM quyết định gọi một công cụ nào đó.
      • ToolOutputItem: Kết quả trả về sau khi công cụ được thực thi.
      • HandoffCallItem: LLM quyết định gọi công cụ handoff để chuyển giao nhiệm vụ.
      • HandoffOutputItem: Xác nhận việc handoff đã xảy ra, chứa thông tin về agent nguồn và đích.
    • Thông tin này cực kỳ giá trị cho việc gỡ lỗi (debugging), phân tích luồng hoạt động chi tiết của agent, hoặc hiển thị tiến trình xử lý cho người dùng khi dùng run_stream.
  4. Phương thức to_input_list():
    • Đây là một phương thức cực kỳ quan trọng để duy trì ngữ cảnh hội thoại. Nó chuyển đổi toàn bộ lịch sử của lần chạy hiện tại (bao gồm input ban đầu, các bước trung gian, và output cuối cùng) thành một danh sách có định dạng chuẩn, sẵn sàng để làm đầu vào (input) cho lần chạy agent tiếp theo.

Duy trì ngữ cảnh hội thoại qua nhiều lượt chạy

Một trong những thách thức khi làm việc với chatbot hay các trợ lý AI là làm sao để chúng "nhớ" được những gì đã nói trước đó. OpenAI Agent giải quyết vấn đề này một cách hiệu quả thông qua phương thức to_input_list() của đối tượng RunResult.

Quy trình để duy trì hội thoại diễn ra như sau:

  1. Lần chạy đầu tiên: Bạn cung cấp input ban đầu (ví dụ: câu hỏi của người dùng) cho agent thông qua runner.run_sync(...) hoặc các phương thức khác.
  2. Lưu kết quả: Bạn nhận được đối tượng RunResult (hoặc RunResultStream).
  3. Lần chạy tiếp theo:
    • Lấy RunResult từ lần chạy trước.
    • Gọi phương thức result.to_input_list() để lấy toàn bộ lịch sử dưới dạng danh sách input chuẩn.
    • Thêm input mới của người dùng (ví dụ: câu hỏi tiếp theo) vào danh sách này.
    • Truyền toàn bộ danh sách input (bao gồm lịch sử cũ và input mới) vào lệnh gọi runner.run_sync(...) tiếp theo.

Bằng cách này, ở mỗi lượt chạy mới, agent sẽ nhận được không chỉ yêu cầu hiện tại mà còn cả toàn bộ bối cảnh của cuộc hội thoại trước đó, giúp nó đưa ra phản hồi phù hợp và mạch lạc hơn.

Ví dụ (minh họa ý tưởng):

  • Lượt 1:
    • User Input: "Hello world"
    • runner.run_sync(["Hello world"]) -> Trả về result1
    • result1.final_output: "Xin chào thế giới"
  • Lượt 2:
    • User Input: "How are you?"
    • history = result1.to_input_list()
    • new_input = history + ["How are you?"]
    • runner.run_sync(new_input) -> Trả về result2
    • result2.final_output: (Agent trả lời dựa trên việc biết lượt trước đã chào hỏi)

Việc hiểu và sử dụng RunResult cùng phương thức to_input_list() cho phép bạn xây dựng các OpenAI Agent có khả năng trò chuyện tự nhiên và ghi nhớ ngữ cảnh một cách hiệu quả.

Kết luận

Qua bài viết này, chúng ta đã đi sâu vào ba thành phần cốt lõi của OpenAI Agent:

  1. Lớp Agent: Cách định nghĩa vai trò, năng lực và quy tắc hoạt động thông qua các tham số như instruction, model, tools.
  2. Lớp Runner: Cơ chế thực thi agent với các phương thức linh hoạt run_sync, run (async), và run_stream phù hợp cho nhiều kịch bản khác nhau.
  3. Đối tượng RunResult / RunResultStream: Cách khai thác kết quả cuối cùng (final_output), thông tin về agent thực thi (last_agent), các bước trung gian (run_items), và đặc biệt là phương thức to_input_list() để duy trì ngữ cảnh hội thoại.

Nắm vững các khái niệm này là nền tảng vững chắc để bạn tiếp tục xây dựng những ứng dụng AI mạnh mẽ và thông minh hơn với OpenAI Agent. Trong các phần tiếp theo, chúng ta sẽ khám phá về cách sử dụng công cụ (tools) và cơ chế chuyển giao (handoff) để mở rộng khả năng của agent. Hãy đón chờ nhé!

0 Answers