Giới thiệu
Trong quá trình phát triển tác nhân, việc cân bằng giữa tự chủ và độ tin cậy là một thách thức lớn mà nhiều đội ngũ gặp phải. Tại Portia, chúng tôi đã phát triển nhiều tác nhân sẵn sàng cho sản xuất cùng với các đối tác thiết kế và hôm nay, chúng tôi rất vui mừng chia sẻ giải pháp của mình: Tự chủ Kiểm soát. Đây là khả năng điều khiển mức độ tự chủ của một tác nhân trong mỗi bước của kế hoạch tác nhân. Chúng tôi triển khai điều này bằng cách sử dụng giao diện PlanBuilder mới được cải tiến để xây dựng các hệ thống tác nhân, và hôm nay, chúng tôi hân hoan công bố phát hành nó trên SDK mã nguồn mở của mình. Chúng tôi tin rằng đây là một giao diện đơn giản và thanh lịch (không có mã boilerplate phức tạp của nhiều khung tác nhân) và là cách tốt nhất để tạo ra các hệ thống tác nhân mạnh mẽ và đáng tin cậy - chúng tôi rất mong chờ xem bạn sẽ xây dựng gì với nó!
Nếu bạn đang xây dựng các tác nhân, hãy chia sẻ với chúng tôi! Hãy xem SDK mã nguồn mở của chúng tôi và cho chúng tôi biết bạn đang phát triển gì trên Discord. Chúng tôi cũng rất vui khi thấy mọi người tham gia đóng góp vào kho mã - nếu bạn muốn bắt đầu với điều này, hãy kiểm tra các vấn đề mở và cho chúng tôi biết nếu bạn muốn đảm nhận một trong số đó.
Ví dụ ngay lập tức
Giao diện PlanBuilder của chúng tôi được thiết kế để cảm thấy trực quan và chúng tôi thấy rằng các tác nhân được xây dựng với nó rất dễ theo dõi, vì vậy hãy cùng đi vào ngay một ví dụ:
python
from portia import PlanBuilderV2, StepOutput
plan = (
PlanBuilderV2("Chạy kế hoạch này để xử lý yêu cầu hoàn tiền.")
.input(name="refund_info", description="Thông tin yêu cầu hoàn tiền của khách hàng")
.invoke_tool_step(
step_name="read_refund_policy",
tool="file_reader_tool",
args={"filename": "./refund_policy.txt"},
)
.single_tool_agent_step(
step_name="read_refund_request",
task=f"Tìm email yêu cầu hoàn tiền từ {Input('customer_email_address')}",
tool="portia:google:gmail:search_email",
)
.llm_step(
step_name="llm_refund_review",
task="Xem xét yêu cầu hoàn tiền theo chính sách hoàn tiền. "
"Quyết định xem yêu cầu hoàn tiền có được chấp thuận hay từ chối. "
"Trả về quyết định theo định dạng: 'CHẤP NHẬN' hoặc 'TỪ CHỐI'.",
inputs=[StepOutput("read_refund_policy"), StepOutput("read_refund_request")],
output_schema=RefundDecision,
)
.function_step(
function=record_refund_decision,
args={"refund_decision": StepOutput("llm_refund_review")})
.react_agent_step(
task="Tìm khoản thanh toán mà khách hàng muốn hoàn lại.",
tools=["portia:mcp:mcp.stripe.com:list_customers", "portia:mcp:mcp.stripe.com:list_payment_intents"],
inputs=[StepOutput("read_refund_request")],
)
# Ví dụ đầy đủ bao gồm nhiều bước hơn để thực sự xử lý việc hoàn tiền v.v.
.build()
)
Mẫu trên là một đoạn trích đã được sửa đổi từ tác nhân hoàn tiền Stripe của chúng tôi (xem ví dụ đầy đủ tại đây), thiết lập một tác nhân hoạt động như sau:
- Đọc chính sách hoàn tiền của công ty: bước này sử dụng
invoke_tool_step, có nghĩa là công cụ được gọi trực tiếp với các tham số đã chỉ định mà không có sự tham gia của LLM. Những bước này rất hữu ích khi bạn cần sử dụng một công cụ (thường để lấy dữ liệu) nhưng không cần sự linh hoạt của LLM để gọi công cụ vì các tham số bạn muốn sử dụng là cố định (điều này thường giúp chúng rất nhanh!). - Đọc yêu cầu hoàn tiền từ email: đối với bước này, chúng tôi muốn linh hoạt tìm email trong hộp thư đến dựa trên thông tin hoàn tiền được truyền vào tác nhân. Để làm điều này, chúng tôi sử dụng
single_tool_agent, đây là một LLM gọi một công cụ duy nhất một lần để đạt được nhiệm vụ của nó. Trong trường hợp này, tác nhân tạo truy vấn tìm kiếm hộp thư đến dựa trên thông tin hoàn tiền được truyền vào để tìm email hoàn tiền. - Đánh giá yêu cầu hoàn tiền theo chính sách hoàn tiền:
llm_stepkhá dễ hiểu ở đây - nó sử dụng LLM đã cấu hình của bạn để đánh giá xem chúng tôi có nên cung cấp hoàn tiền dựa trên yêu cầu và chính sách hay không. Chúng tôi sử dụng đối tượngStepOutputđể cung cấp kết quả từ các bước trước đó, và trườngoutput_schemacho phép chúng tôi trả về quyết định dưới dạng một đối tượng pydantic thay vì dưới dạng văn bản. - Ghi nhận quyết định hoàn tiền: chúng tôi có một hàm python mà chúng tôi sử dụng để ghi lại các quyết định đã đưa ra - chúng tôi có thể gọi điều này dễ dàng với một
function_step, cho phép gọi trực tiếp các hàm python như một phần của quá trình thực hiện kế hoạch. - Tìm khoản thanh toán trong Stripe: việc tìm một khoản thanh toán trong Stripe yêu cầu sử dụng một số công cụ từ máy chủ MCP từ xa của Stripe (điều này dễ dàng được kích hoạt trong tài khoản Portia của bạn). Do đó, chúng tôi thiết lập một tác nhân ReAct với các công cụ cần thiết và nó có thể liên kết thông minh các công cụ Stripe cần thiết để tìm khoản thanh toán. Thêm một điều thú vị, Portia sử dụng MCP Auth theo mặc định nên các cuộc gọi công cụ này sẽ hoàn toàn được xác thực.
Tự chủ Kiểm soát
Như đã chứng minh trong ví dụ trên, sức mạnh của PlanBuilderV2 đến từ việc bạn có thể dễ dàng kết nối và kết hợp các loại bước khác nhau, tùy thuộc vào tình huống và yêu cầu của bạn. Điều này cho phép bạn kiểm soát lượng tự chủ mà hệ thống của bạn có tại mỗi điểm trong quá trình thực thi, với một số bước (ví dụ: react_agent_step) sử dụng các mô hình ngôn ngữ với tự chủ cao trong khi những bước khác được kiểm soát và hạn chế cẩn thận (ví dụ: invoke_tool_step).
Theo kinh nghiệm của chúng tôi, chính ‘tự chủ kiểm soát’ là chìa khóa để giúp các tác nhân thực hiện đáng tin cậy, cho phép chúng tôi chuyển từ các nguyên mẫu thú vị sang các tác nhân thực tế, sản xuất. Thường thì, các nguyên mẫu được xây dựng với ‘tự chủ hoàn toàn’, cho phép một tác nhân ReAct truy cập vào tất cả các công cụ và để chúng tự do thực hiện một nhiệm vụ. Cách tiếp cận này có thể thực hiện được với trình xây dựng kế hoạch của chúng tôi và có thể hoạt động tốt trong một số tình huống, nhưng trong những trường hợp khác (đặc biệt là đối với các nhiệm vụ phức tạp hơn) nó có thể dẫn đến các tác nhân không đáng tin cậy. Chúng tôi nhận thấy rằng các nhiệm vụ thường cần được chia nhỏ và cấu trúc thành các nhiệm vụ con có thể quản lý, với mức độ tự chủ cho mỗi nhiệm vụ con được kiểm soát, để chúng có thể được thực hiện đáng tin cậy. Ví dụ, chúng tôi thường thấy các bước nghiên cứu và thu thập thông tin trong một hệ thống được thực hiện với các bước tác nhân ReAct có tự chủ cao vì chúng thường sử dụng các công cụ chỉ đọc không ảnh hưởng đến các hệ thống khác. Sau đó, khi đến bước tác nhân thực hiện hành động, các bước này được thực hiện với mức độ tự chủ bằng không hoặc thấp để chúng có thể được thực hiện theo cách kiểm soát hơn.
Cấu trúc Điều khiển đơn giản
Mở rộng ví dụ trên, PlanBuilderV2 cũng cung cấp các cấu trúc điều khiển quen thuộc mà bạn có thể sử dụng khi chia nhỏ các nhiệm vụ cho hệ thống tác nhân của mình. Điều này giúp bạn kiểm soát hoàn toàn để đảm bảo rằng nhiệm vụ được tiếp cận theo cách đáng tin cậy:
python
# Các bước điều kiện (if, else if, else)
.if_(condition=lambda review: review.decision == REJECTED,
args={"llm_review_decision": StepOutput("llm_refund_review")})
.function_step(
function=handle_rejected_refund,
args={"proposed_refund": StepOutput("proposed_refund")})
.endif()
# Vòng lặp - ở đây chúng tôi sử dụng .loop(over=...), nhưng cũng có các lựa chọn khác cho
# .loop(while=...) và .loop(do_while=...)
.loop(over=StepOutput("Items"), step_name="Loop")
.function_step(
function=lambda item: print(item),
args={"item": StepOutput("Loop")})
.end_loop()
Một điều thú vị: Chúng tôi đã chọn .if_() thay vì .if() (chú ý dấu gạch dưới) vì if là một từ khóa bị hạn chế trong python.
Giao diện Người - Tác nhân
Một khía cạnh quan trọng khác trong việc đưa một tác nhân vào sản xuất là khả năng chuyển giao quyền kiểm soát giữa tác nhân và con người một cách liền mạch. Trong khi chúng tôi xây dựng niềm tin vào các hệ thống tác nhân, thường có những bước quan trọng yêu cầu xác minh hoặc đầu vào từ con người. Giao diện PlanBuilder của chúng tôi cho phép cả hai được xử lý dễ dàng, sử dụng hệ thống làm rõ của Portia:
python
# Đảm bảo một người xác nhận bất kỳ hoàn tiền nào mà tác nhân của chúng tôi cung cấp
builder.user_verify(
message=f"Bạn có hài lòng để tiếp tục với hoàn tiền đề xuất sau: {StepOutput('proposed_refund')}?")
# Cho phép người dùng cuối của bạn cung cấp đầu vào về cách tác nhân hoạt động
builder.user_input(
message="Bạn muốn hoàn tiền như thế nào?",
options=["Trả lại thẻ mua hàng", "thẻ quà tặng"],
)
Kiểm soát tác nhân của bạn bằng mã
Bước function_step được trình bày trước đó là một bổ sung chính cho PlanBuilderV2. Trong nhiều hệ thống tác nhân, tất cả các cuộc gọi công cụ và hàm đều đi qua một mô hình ngôn ngữ, điều này có thể chậm và cũng có thể giảm độ tin cậy. Với function_step, hàm được gọi với các args đã cung cấp tại điểm đó trong chuỗi với độ tin cậy đầy đủ. Chúng tôi đã thấy một số trường hợp sử dụng cho điều này:
- Guardrails: nơi các kiểm tra mã định nghĩa và đáng tin cậy được sử dụng để xác minh hành vi của tác nhân (xem ví dụ dưới đây)
- Biến đổi dữ liệu: khi bạn muốn thực hiện một biến đổi dữ liệu đơn giản để liên kết các công cụ với nhau, nhưng bạn không muốn trả giá cho độ trễ của một cuộc gọi LLM bổ sung để thực hiện biến đổi, bạn có thể thực hiện biến đổi đó trong mã.
- Kết nối các hàm hiện có: khi bạn đã có tính năng mà bạn cần trong mã, bạn có thể sử dụng hàm
function_stepđể dễ dàng kết nối nó vào tác nhân của bạn.
python
# Thêm một guardrail để ngăn tác nhân của chúng tôi cung cấp hoàn tiền lớn
builder.function_step(
step_name="reject_payments_above_limit",
function=reject_payments_above_limit,
args={"proposed_refund": StepOutput("proposed_refund"), "limit": Input("payment_limit")})
Những điều tiếp theo?
Chúng tôi rất vui mừng khi xây dựng các tác nhân với PlanBuilderV2 và mong muốn chia sẻ nó rộng rãi hơn. Chúng tôi thấy rằng nó bổ sung cho tác nhân lập kế hoạch của chúng tôi một cách tốt: tác nhân lập kế hoạch của chúng tôi có thể được sử dụng để tạo động các kế hoạch từ ngôn ngữ tự nhiên khi điều này cần thiết cho trường hợp sử dụng của bạn, trong khi trình xây dựng kế hoạch có thể được sử dụng nếu bạn muốn kiểm soát cẩn thận các bước mà hệ thống tác nhân của bạn thực hiện bằng mã.
Chúng tôi cũng có nhiều tính năng hơn đang đến trong vài tuần tới sẽ tiếp tục làm cho giao diện trình xây dựng kế hoạch trở nên mạnh mẽ hơn:
- Chạy song song: chạy các bước song song với
.parallel(). - Lưu trữ tự động: thêm
cache=Truevào các bước để tự động lưu trữ kết quả - đây là một thay đổi lớn khi bạn muốn lặp lại các bước sau trong một kế hoạch mà không phải thực hiện lại toàn bộ kế hoạch. - Bộ xử lý lỗi bước: chỉ định
.on_error()sau một bước để đính kèm một bộ xử lý lỗi cho nó,.retry()để cho phép thử lại các bước hoặc sử dụngexit_step()để thoát một cách thanh lịch khỏi một kế hoạch. - Kế hoạch liên kết: liên kết các kế hoạch với nhau bằng cách tham chiếu đầu ra từ các lần chạy kế hoạch trước đó.
python
plan = (
PlanBuilderV2("Chạy kế hoạch này để xử lý yêu cầu hoàn tiền.")
# 1. Chạy các bước tiếp theo song song
.parallel()
.invoke_tool_step(
tool="file_reader_tool",
args={"filename": "./refund_policy.txt"},
# 2. Thêm lưu trữ tự động vào một bước
cache=True
)
# 3. Thêm xử lý lỗi vào một bước
.on_error()
.react_agent_step(
# 4. Liên kết các kế hoạch với nhau bằng cách tham chiếu đầu ra từ một lần chạy trước đó
# Ở đây, chúng tôi có thể có một tác nhân trước đó xác định các khoản hoàn tiền của khách hàng cần xử lý
task=f"Đọc yêu cầu hoàn tiền từ hộp thư đến của tôi từ {PlanRunOutput(previous_run)}.",
tools=["portia:google:gmail:search_email"],
)
# Tiếp tục thực hiện chuỗi
.series()
)
Hãy khen ngợi gaurava05 vì đã thêm ExitStep như một đóng góp mã nguồn mở trong PR này.
Vậy hãy thử nghiệm với PlanBuilder mới của chúng tôi và cho chúng tôi biết bạn tiến bộ như thế nào - chúng tôi rất háo hức muốn thấy những gì bạn xây dựng! 🚀
Để biết thêm chi tiết về PlanBuilderV2, hãy xem tài liệu của chúng tôi, kế hoạch ví dụ hoặc ví dụ hoàn tiền Stripe đầy đủ. Bạn cũng có thể tham gia Discord của chúng tôi để nghe các cập nhật trong tương lai.