Harness Academy
Tài liệu gốc 29 24 phútTiếng Việt + English
docs/guardrails.mdXem bản gốc
Ngôn ngữ bài đọc
Bản dịch tiếng Việt

Bản dịch đầy đủ theo đúng cấu trúc tài liệu; code, liên kết và định danh kỹ thuật được giữ nguyên.

Lan can đầu vào, đầu ra và công cụ

Các lan can đặt một lớp xác thực trên ba cạnh của quá trình chạy agent: lời nhắc trên đường vào đến mô hình, công cụ gọi mô hình thực hiện trong quá trình thực hiện và đầu ra trên đường đi tới người gọi. Tiếp cận chúng khi đầu vào hoặc đầu ra không có cấu trúc phải được sàng lọc trước khi nó được thực hiện -- một nỗ lực đưa vào nhanh chóng mà bạn không bao giờ muốn gửi, PII bạn phải xử lý lại, một yêu cầu lạc đề mà bạn muốn từ chối với giá rẻ hoặc một câu trả lời phải trích dẫn nguồn của nó trước khi bạn hiển thị. Nếu không có lan can, khung sẽ gửi bất cứ thứ gì người dùng nhập và trả về bất cứ thứ gì mà mô hình tạo ra, đúng nguyên văn; một lan can xen vào một điều khiển có thể gọi được mà bạn có thể đưa ra quyết định cuối cùng.

Mặc dù Pydantic AI Harness có trên các bản phát hành 0.x nhưng API có thể thay đổi giữa các bản phát hành nhỏ; khi đó, cảnh báo ngừng sử dụng và hướng dẫn di chuyển ghi chú phát hành sẽ cho bạn (hoặc người đại diện của bạn) biết chính xác cách nâng cấp. Xem chính sách phiên bản.

vấn đề

agent lấy đầu vào không có cấu trúc từ người dùng và trả lại đầu ra không có cấu trúc cho người gọi. Bản thân khung không đưa ra lý do về "điều này không an toàn để gửi" hoặc "điều này không an toàn để hiển thị" -- một nỗ lực chèn nhanh sẽ tiếp cận mô hình như hiện trạng và mọi kết quả đầu ra mà mô hình tạo ra sẽ được trả về nguyên trạng. Bạn cần một nơi để kiểm tra giá trị và quyết định điều gì sẽ xảy ra tiếp theo.

Giải pháp

Ba khả năng -- InputGuardrail, OutputGuardrailvà ToolGuardrail -- mỗi bọc một guard có thể gọi được bạn cung cấp. Người bảo vệ kiểm tra một giá trị và trả về một trong năm kết quả. Đối với hai cạnh của đường chạy:

Kết quảInputGuardrailOutputGuardrail
cho phépgửi lời nhắc tới mô hìnhtrả lại đầu ra cho người gọi
chặnbỏ qua cuộc gọi mô hình; một tin nhắn từ chối trở thành phản hồinâng cao OutputBlocked
thay thếviết lại lời nhắc được gửi đến mô hình (redaction)thay thế một đầu ra đã được vệ sinh
thử lại-- (không hợp lệ cho đầu vào)gửi kết quả trở lại mô hình để thử lại
phê duyệt-- (không hợp lệ cho đầu vào)-- (không hợp lệ cho đầu ra)

ToolGuardrail sử dụng các kết quả giống nhau ở cả hai phía của lệnh gọi công cụ; xem Cuộc gọi công cụ. Sự bất đối xứng giữa đầu vào block và đầu ra block là cố ý. Việc chặn đầu vào không tiêu tốn token, do đó, việc từ chối một cách khéo léo hầu như luôn đúng. Chặn đầu ra có nghĩa là mô hình đã tạo ra thứ gì đó mà bạn không muốn bị lộ, do đó, việc nâng cao buộc người gọi phải quyết định xem phải làm gì tiếp theo.

Cả hai InputGuardrail, OutputGuardrailvà các loại hỗ trợ của chúng là xuất khẩu cấp cao nhất:

from pydantic_ai import Agent
from pydantic_ai_harness import GuardrailResult, InputGuardrail, OutputGuardrail


def no_secrets(prompt: str) -> bool:
    return 'api_key' not in prompt.lower()


def no_pii(output: object) -> GuardrailResult:
    if 'SSN' in str(output):
        return GuardrailResult.block('The response contained personal data.')
    return GuardrailResult.allow()


agent = Agent(
    'openai:gpt-5.4',
    capabilities=[
        InputGuardrail(guard=no_secrets),
        OutputGuardrail(guard=no_pii),
    ],
)

Một người bảo vệ trả lại một trần trụi bool (True = cho phép, False = khối) cho trường hợp đơn giản, hoặc một GuardrailResult để có được những kết quả phong phú hơn. Các bộ bảo vệ cũng có thể không đồng bộ -- trả về một giá trị có thể chờ đợi bool/GuardrailResult, ví dụ: để gọi API kiểm duyệt.

OutputGuardrail nhận được đầu ra không thay đổi - không có chuỗi tự động. Đối với đầu ra chuỗi, bộ bảo vệ sẽ đọc nó trực tiếp; đối với đầu ra được nhập (mô hình Pydantic), bộ bảo vệ sẽ lấy phiên bản mô hình, vì vậy hãy chọn thứ tự tuần tự phù hợp với kiểm tra (đọc một trường hoặc gọi output.model_dump_json() đối với văn bản JSON). Điều này tránh được cái bẫy của str(MyModel(...)) sản xuất một MyModel(field=...) Repr ẩn nội dung trường khỏi các kiểm tra dựa trên biểu thức chính quy.

Nhiều vệ sĩ cùng một lúc

InputGuardrail.guard và OutputGuardrail.guard lấy một cái có thể gọi được hoặc một trình tự của chúng; ToolGuardrail's guard và result_guard lấy một cái có thể gọi được mỗi cái. Trong một chuỗi, những người bảo vệ chạy theo thứ tự, và điều gì xảy ra tiếp theo phụ thuộc vào về bản án:

Bản ánTác dụng lên dây chuyền
allowchuyển sang người bảo vệ tiếp theo
replacephần còn lại của chuỗi kiểm tra giá trị thay thế
block / retrychuỗi kết thúc ở đó, vì không để lại giá trị nào để đánh giá

replace luồng về phía trước là điều làm cho trật tự có ý nghĩa: đặt một trình biên tập đầu tiên và mọi thứ sau khi nó nhìn thấy văn bản đã được làm sạch.

from pydantic_ai import Agent
from pydantic_ai_harness import InputGuardrail
from pydantic_ai_harness.guardrails.detectors import blocked_keywords, redact_secrets

agent = Agent(
    'openai:gpt-5.4',
    capabilities=[InputGuardrail(guard=[redact_secrets, blocked_keywords(['internal-only'])])],
)

một InputGuardrail giữ hai tờ séc không giống như hai tờ séc InputGuardrail khả năng: chuỗi là một vị trí trong danh sách khả năng, một đơn đặt hàng quyết định, và một tập hợp các nhịp, và chỉ có chuỗi xử lý lại sự điều chỉnh vào kiểm tra theo sau nó.

Việc xử lý lại chỉ tiếp cận lịch sử tin nhắn của lần chạy nếu chuỗi kết thúc: a sau này block kết thúc nó trước InputGuardrail viết lại lời nhắc đã được làm sạch, vì vậy bản gốc vẫn còn trong lịch sử. Đặt trình chỉnh sửa cuối cùng khi điều đó quan trọng, tại chi phí của việc kiểm tra trước khi nó nhìn thấy văn bản gốc.

Một chuỗi trống bị từ chối -- khi lan can lần đầu tiên chạy chứ không phải khi nó được thực hiện được xây dựng. Một lan can không kiểm tra thứ gì sẽ đọc như đã định cấu hình và hoạt động như vắng mặt, điều này đáng bị mắc lỗi hơn là bỏ qua. Một bộ và một trình vòng lặp một lần bị từ chối theo cùng một cách: một tập hợp không có thứ tự nào để chuỗi chạy vào và một trình vòng lặp được sử dụng sau yêu cầu đầu tiên, vì chuỗi xây dựng lại theo yêu cầu.

Máy dò làm sẵn

pydantic_ai_harness.guardrails.detectors giữ các tấm séc mà bạn thường làm viết lại: chúng là các hàm đơn giản trả về một GuardrailResult, vì vậy họ thả thành một chuỗi bên cạnh của riêng bạn.

from pydantic_ai_harness.guardrails import detectors

detectors.redact_secrets  # rewrites vendor API keys, tokens, and whole private-key blocks out of text
detectors.redact_personal_data  # rewrites emails, card numbers, IBANs, US SSNs
detectors.blocked_keywords(['internal-only'])  # refuses text containing any of them

Hai trình biên tập viết lại thay vì từ chối, đây là mặc định hữu ích: agent trích dẫn lại key vẫn hoàn thành công việc và chặn câu trả lời làm mất nó trong khi vẫn để lại chìa khóa trong lịch sử tin nhắn.

Mỗi cái là secret_data() và personal_data() được gọi với giá trị mặc định. Sử dụng trực tiếp các nhà máy đó khi bạn cần thu hẹp hoặc mở rộng chúng:

from pydantic_ai_harness.guardrails.detectors import secret_data

secret_data(only=['aws_access_key', 'private_key'])
secret_data(extra={'internal_ticket': r'INT-\d{4}'})
secret_data(placeholder='***')  # the default is `[redacted:{name}]`, which says what it removed

Trình phát hiện đọc văn bản nên chúng phù hợp trực tiếp với lời nhắc. Một đầu ra agent có thể là một phiên bản mô hình và việc thay thế một chuỗi đã được lọc bằng một chuỗi sẽ thay đổi nó gõ, vậy for_text khiến bạn phải nói điều gì sẽ xảy ra:

from pydantic_ai_harness import OutputGuardrail
from pydantic_ai_harness.guardrails.detectors import for_text, redact_secrets

OutputGuardrail(guard=for_text(redact_secrets))  # raises on a non-string output
OutputGuardrail(guard=for_text(redact_secrets, on_other='allow'))  # skips it deliberately

Người bảo vệ kết quả công cụ nhận được ToolResultInfo, không chỉ là một giá trị. sử dụng for_tool_result_text để điều chỉnh bộ dò tìm kết quả chuỗi trần hoặc văn bản các kênh của một ToolReturn:

from pydantic_ai_harness import ToolGuardrail
from pydantic_ai_harness.guardrails.detectors import for_tool_result_text, redact_secrets

ToolGuardrail(result_guard=for_tool_result_text(redact_secrets))

Bộ điều hợp kiểm tra một chuỗi return_value và tất cả văn bản trong content, lõi nào gửi dưới dạng một phần nhắc nhở người dùng riêng biệt. Các phần văn bản mà mô hình đọc dưới dạng một nhịp được khử trùng thành một chuỗi, vì vậy content=[a, b] được đối xử tương tự như content=a + b và một bí mật chia rẽ giữa hai người vẫn bị bắt. một phần không mang nội dung mô hình không kết thúc một khoảng: a CachePoint giữa hai văn bản bị nhà cung cấp loại bỏ mà không lưu vào bộ nhớ đệm, do đó văn bản ở hai bên của nó được nối với nhau, trong khi hình ảnh hoặc tài liệu giữa chúng thì không, vì việc kết hợp giữa các nội dung thực sẽ phát minh ra sự kề cận.

Khoảng thời gian giữ các phần của nó và mỗi phần siêu dữ liệu riêng của nó khi dọn dẹp chúng một bởi một người đã tạo ra những gì việc vệ sinh toàn bộ nhịp tạo ra. Các bộ phận chạy vào nhau là trường hợp cả hai khác nhau: một chìa khóa ở cuối một phần và một từ ở đầu từ tiếp theo là một token cho mô hình, do đó khoảng đó sụp đổ thành một phần duy nhất mang kết quả toàn bộ nhịp. A CachePoint trong một nhịp thu gọn được giữ nguyên, không bị rớt. Một trước văn bản đầu tiên hoặc sau văn bản cuối cùng giữ bên mình; một giữa hai văn bản được hợp nhất đã mất phần phân chia được đánh dấu và di chuyển về phía trước văn bản đã hợp nhất, thu hẹp tiền tố được lưu trong bộ nhớ đệm thay vì mở rộng nó. Khi nó điều chỉnh lại một trong hai kênh, nó sẽ bảo toàn ToolReturn siêu dữ liệu, loại, và nội dung phi văn bản.

Như với for_text, kết quả không phải văn bản sẽ tăng theo mặc định. on_other='allow' bỏ qua toàn bộ kết quả thay vì một phần của nó: không return_value cũng không content được quét. A ToolReturn của ai return_value là số đếm có cấu trúc do kết quả không phải là văn bản nên văn bản nhạy cảm trong đó content không được quét theo lựa chọn đó. Việc từ chối là người gọi chịu trách nhiệm về kết quả; quét content sẽ trả lại một bản chỉnh sửa từ chi nhánh được ghi lại là cho phép kết quả và nó vẫn không đồng đều, vì kết quả có cấu trúc đó không phải là một ToolReturn không có kênh văn bản riêng biệt để quét. Khi có kết quả có thể mang theo nhạy cảm content, rời đi on_other theo mặc định và áp dụng thay vào đó, máy dò sang trường đầu ra.

Bộ dò tìm ở phía đầu vào cần có lời nhắc bằng văn bản. Một dấu nhắc đa phương thức đạt đến bảo vệ được hiển thị dưới dạng văn bản, do đó, trình phát hiện phù hợp sẽ trả về replace, cái nào InputGuardrail từ chối thay vì đánh rơi các bộ phận đính kèm (xem "Phiên bản (replace)" bên dưới). Thay vào đó hãy bảo vệ đầu ra khi lời nhắc có thể mang theo tệp đính kèm.

Khi hình dạng là chưa đủ. email khớp với bất kỳ thứ gì có hình dạng giống như một địa chỉ, bởi vì đó hoàn toàn là một địa chỉ:

from pydantic_ai_harness.guardrails.detectors import redact_personal_data

redact_personal_data('git clone git@github.com:pydantic/pydantic-ai.git')
# replaces `git@github.com`, leaving a command that no longer runs

Bộ bảo vệ đầu vào sẽ viết lại lời nhắc tại chỗ để mô hình nhận được phiên bản bị hỏng. Trên một agent xử lý mã hoặc đường dẫn, hãy sử dụng personal_data(only=['us_ssn', 'credit_card', 'iban'])hoặc đặt bộ dò ở đầu ra thay vì lời nhắc. credit_card nó đã bớt tệ hơn. Nó bao gồm 13 đến 19 chữ số mà ISO/IEC 7812 cho phép, trong bất kỳ nhóm nào và chỉ khi chữ số đứng đầu là từ 2 đến 6, đó là ký tự bắt đầu của thẻ thanh toán và dấu thời gian mili giây thì không. Sau đó, mỗi trận đấu sẽ được kiểm tra bằng thuật toán Luhn, thuật toán này sẽ loại bỏ hầu hết các lần chạy còn lại. Không phải tất cả chúng - khoảng một lần trong bốn năm liên tiếp trong mười năm đều đáp ứng tổng kiểm tra một cách tình cờ, do đó, những năm niêm yết nhanh chóng vẫn có thể mất một năm.

iban có cùng một vấn đề và cùng một câu trả lời: mã quốc gia cộng với hai chữ số là một hình dạng mà văn bản thông thường xuất hiện liên tục, do đó, chỉ cho phép khoảng trắng ở nơi biểu mẫu in đặt chúng và mọi kết quả trùng khớp đều được kiểm tra theo chữ số ISO 7064 mod-97 mà IBAN mang theo.

Khóa truy cập bí mật AWS cố tình vắng mặt trong các giá trị mặc định. Đó là bốn mươi ký tự của base64 không có tiền tố phân biệt, vì vậy không có gì trong giá trị đánh dấu nó là khóa và chỉ riêng mẫu cho hình dạng sẽ mang theo base64 thông thường. So khớp một nghĩa là bám vào tên được viết bên cạnh nó -- aws_secret_access_key = ... -- đó là những gì pydantic-ai-shields thực hiện và chỉ tìm thấy khóa ở nơi nó được viết dưới dạng bài tập. Mẫu hẹp hơn đó không được vận chuyển ở đây; vượt qua nó extra= nếu đó là cách chìa khóa đến được agent của bạn.

only= chọn mẫu; nó không sắp xếp lại chúng. Thứ tự ứng dụng là một phần trong hợp đồng của mỗi bản đồ -- iban chạy trước credit_card vì vậy số tài khoản cách nhau không được gắn nhãn thẻ -- và nó giữ thứ tự đó bất kể thứ tự nào bạn liệt kê only trong.

Khóa riêng được khớp cho dù ngắt dòng của nó là dòng mới thực hay dòng thoát \n một tệp tài khoản dịch vụ JSON hoặc một .env dòng mang theo, đó là cách người ta thường đến cửa sổ trò chuyện. Chấm dứt hay không, đó là một private_key mẫu chứ không phải là hai tên, vì vậy only=['private_key'] không thể chọn khối hoàn chỉnh và để lại một khóa được dán mà không có nó END điểm đánh dấu không được chỉnh sửa.

Những điều này không làm được. Regex tìm thấy thông tin xác thực vì thông tin xác thực có hình dạng. Nó không tìm thấy lời nhắc tiêm, đó là ngôn ngữ thông thường, và nó không hiểu ngữ cảnh, do đó, trình biên tập đôi khi sẽ lấy một chuỗi chỉ trông giống như một chiếc chìa khóa. Chúng là một lớp rẻ tiền, không phải là câu trả lời.

GuardrailResult

Xây dựng một GuardrailResult với các phương thức lớp của nó, không phải các trường thô:

from pydantic_ai_harness import GuardrailResult

GuardrailResult.allow()                 # let the value through
GuardrailResult.block('reason')         # refuse; `reason` is optional (a default is used otherwise)
GuardrailResult.replace(cleaned_value)  # substitute a sanitized value and continue
GuardrailResult.retry('instruction')    # ask the model to redo the output or the tool call
GuardrailResult.approve()               # ToolGuardrail arguments only: defer the call for human approval

Thông báo chặn/thử lại được tạo ra tại thời điểm người bảo vệ quyết định, do đó, nó có thể mang lý do riêng của người bảo vệ thay vì một chuỗi bị đóng băng tại thời điểm xây dựng.

Biên tập (replace)

Trở lại GuardrailResult.replace(value) để vệ sinh hơn là từ chối. InputGuardrail viết lại lời nhắc được gửi đến mô hình; OutputGuardrail thay thế đầu ra được trả về cho người gọi.

def scrub_emails(text: str) -> GuardrailResult:
    cleaned = EMAIL_RE.sub('[email]', text)
    return GuardrailResult.replace(cleaned) if cleaned != text else GuardrailResult.allow()


agent = Agent(
    'openai:gpt-5.4',
    capabilities=[
        InputGuardrail(guard=scrub_emails),   # strip PII before it reaches the model
        OutputGuardrail(guard=scrub_emails),  # strip PII before it reaches the caller
    ],
)

Việc biên tập đầu vào yêu cầu chế độ tuần tự - nó không tương thích với parallel=True, vì bộ bảo vệ song song chạy cùng với lệnh gọi mô hình đã bắt đầu bằng lời nhắc ban đầu. Nó cũng yêu cầu một lời nhắc văn bản. Người bảo vệ nhìn thấy lời nhắc đa phương thức (agent.run_sync(['describe this', BinaryContent(...)])) được hiển thị dưới dạng văn bản và một chuỗi được viết lại trên lời nhắc được tạo từ nhiều phần sẽ loại bỏ hình ảnh, tài liệu và âm thanh đính kèm, do đó replace tăng lên UserError thay vào đó. Trở lại allow hoặc block cho những lời nhắc đó hoặc bảo vệ đầu ra.

Thử lại (retry)

OutputGuardrail có thể gửi đầu ra xấu trở lại mô hình thay vì chặn nó. Trở lại GuardrailResult.retry(instruction) -- hướng dẫn là lời nhắc thử lại mà mô hình nhìn thấy. Điều này sử dụng lại máy móc thử lại thông thường của pydantic-ai và được tính vào ngân sách thử lại đầu ra của lần chạy.

def must_cite_sources(output: object) -> GuardrailResult:
    if not has_citations(output):
        return GuardrailResult.retry('Include at least one source citation.')
    return GuardrailResult.allow()


OutputGuardrail(guard=must_cite_sources)

Truy cập bối cảnh chạy

Một người bảo vệ có thể mất một RunContext làm tham số đầu tiên khi nó cần trạng thái chạy -- deps đối với chính sách nhận thức về người thuê hoặc vai trò, lịch sử tin nhắn để kiểm tra nhận biết cuộc hội thoại. Tham số được phát hiện từ chữ ký, vì vậy những người bảo vệ chỉ nhắc nhở không cần khai báo nó:

from pydantic_ai import RunContext
from pydantic_ai_harness import InputGuardrail


def tenant_policy(ctx: RunContext[MyDeps], prompt: str) -> bool:
    return ctx.deps.tier == 'pro' or 'advanced-feature' not in prompt


InputGuardrail(guard=tenant_policy)

Bảo vệ đầu vào song song

Bộ bảo vệ chậm (bộ phân loại LLM, cuộc gọi mạng) chạy tuần tự sẽ tăng thêm độ trễ cho mỗi lượt. Đặt parallel=True Thay vào đó, hãy chạy trình bảo vệ đồng thời với lệnh gọi mô hình, chồng chéo cả hai để trình bảo vệ không thêm độ trễ trên đường truyền. Cuộc gọi mẫu bị hủy ngay khi người bảo vệ báo cáo vi phạm.

InputGuardrail(guard=slow_async_classifier, parallel=True)

Chế độ song song trao đổi token để lấy độ trễ: chế độ tuần tự không bao giờ gọi mô hình khi khối bảo vệ, nhưng chế độ song song đã bắt đầu lệnh gọi mô hình -- nếu bảo vệ chỉ ngắt sau khi mô hình phản hồi thì các token đó đã được sử dụng. Để kiểm tra cục bộ nhanh (regex, tra cứu từ khóa), tuần tự là mặc định tốt hơn. replace không có sẵn dưới parallel=True.

Con đường khó khăn

block là con đường duyên dáng. Thay vào đó, để làm cho người gọi nhìn thấy một ngoại lệ, hãy nâng cao từ người bảo vệ:

from pydantic_ai_harness import InputBlocked


def strict_guard(prompt: str) -> bool:
    if contains_credentials(prompt):
        raise InputBlocked('credentials detected')
    return True

Bất kỳ ngoại lệ nào được đưa ra bởi người bảo vệ đều được truyền bá nguyên trạng -- sử dụng InputBlocked / OutputBlocked / ToolBlocked từ mô-đun này hoặc các loại ngoại lệ của riêng bạn. ToolBlocked mang theo tool_name và một tùy chọn reason.

Cuộc gọi công cụ

ToolGuardrail kiểm tra cả hai mặt của lệnh gọi công cụ: guard xem các đối số được xác thực trước khi công cụ chạy, result_guard xem những gì nó trả về trước khi mô hình thực hiện.

from pathlib import Path

import httpx
from pydantic_ai import Agent
from pydantic_ai_harness import GuardrailResult, ToolGuardrail
from pydantic_ai_harness.guardrails import ToolCallInfo, ToolResultInfo

WORKSPACE = Path('/workspace')


def stay_in_the_workspace(call: ToolCallInfo) -> GuardrailResult:
    if call.name == 'write_file':
        # `resolve()` before the containment check: a prefix test on the raw
        # string accepts `/workspace/../etc/passwd`.
        target = Path(str(call.args['path'])).resolve()
        if not target.is_relative_to(WORKSPACE):
            return GuardrailResult.block(f'{target} is outside the workspace.')
    return GuardrailResult.allow()


def scrub_secrets(info: ToolResultInfo) -> GuardrailResult:
    # Text only. `str()` on a structured result or a `ToolReturn` yields a repr, and
    # replacing it with that string would change the result's type -- but only on the
    # calls where the pattern happened to match.
    if not isinstance(info.result, str):
        return GuardrailResult.allow()
    cleaned = SECRET_RE.sub('[redacted]', info.result)
    return GuardrailResult.replace(cleaned) if cleaned != info.result else GuardrailResult.allow()


agent = Agent(
    'openai:gpt-5.4',
    capabilities=[ToolGuardrail(guard=stay_in_the_workspace, result_guard=scrub_secrets)],
)


@agent.tool_plain
def write_file(path: str, content: str) -> str:
    Path(path).write_text(content)
    return f'wrote {path}'


@agent.tool_plain
def fetch_page(url: str) -> str:
    return httpx.get(url).text

Bản đồ kết quả dựa trên luồng điều khiển Pydantic AI thay vì cơ chế song song:

Kết quảguard (lập luận)result_guard (kết quả)
cho phépchạy công cụtrả về kết quả không thay đổi (người bảo vệ được giao đối tượng mà công cụ tạo ra, vì vậy hãy đọc nó và sử dụng replace thay vì làm biến đổi nó)
chặnbỏ qua việc thực hiện; thông báo từ chối trở thành kết quả của công cụ (SkipToolExecution)tin nhắn từ chối thay thế kết quả
thay thếchạy công cụ với các đối số được thay thế (ánh xạ); cuộc gọi được ghi trong lịch sử tin nhắn sẽ giữ nguyên các đối số ban đầu của mô hình. Việc thay thế được tin cậy để khớp với chữ ký của công cụ: các khóa mà công cụ không chấp nhận tiếp cận nó dưới dạng đối số từ khóa và nâng cao giá trị trần TypeError không nêu tên công cụ cũng như người bảo vệthay thế một kết quả vệ sinh
thử lạiyêu cầu model thực hiện lại cuộc gọi (ModelRetry)yêu cầu model thực hiện lại cuộc gọi (ModelRetry); công cụ này đã chạy một lần nên tác dụng phụ của nó đã xảy ra và việc thử lại sẽ chạy lại
phê duyệttrì hoãn lời kêu gọi phê duyệt của con người (ApprovalRequired)-- (công cụ đã chạy)

block duyên dáng ở cả hai giai đoạn: agent nhìn thấy văn bản từ chối nơi nó mong đợi một kết quả công cụ, vì vậy nó có thể giải thích việc từ chối hoặc thử một cách tiếp cận khác. Thay vào đó, để chạy thất bại, hãy nâng cao ToolBlocked từ người bảo vệ.

Con người trong vòng lặp

Pydantic AI đã sở hữu chuyến đi khứ hồi phê duyệt: nâng cao cuộc gọi ApprovalRequired được giữ lại, cuộc chạy kết thúc với một DeferredToolRequests đầu ra và bạn tiếp tục nó với câu trả lời của con người. ToolGuardrail cắm vào đó thay vì phát minh ra cơ chế thứ hai, có nghĩa là sự chấp thuận mà người bảo vệ yêu cầu và các công cụ được đánh dấu requires_approval=True đến cùng một nơi.

from pydantic_ai import Agent, DeferredToolRequests, DeferredToolResults, ToolDenied
from pydantic_ai_harness import GuardrailResult, ToolGuardrail
from pydantic_ai_harness.guardrails import ToolCallInfo


def confirm_production(call: ToolCallInfo) -> GuardrailResult:
    if call.args.get('env') == 'prod':
        return GuardrailResult.approve()
    return GuardrailResult.allow()


agent = Agent(
    'openai:gpt-5.4',
    capabilities=[ToolGuardrail(guard=confirm_production)],
    output_type=[str, DeferredToolRequests],
)


@agent.tool_plain
def deploy(env: str) -> str:
    return f'deployed to {env}'


deferred = await agent.run('deploy the new build')
if isinstance(deferred.output, DeferredToolRequests):
    approvals = {
        call.tool_call_id: True if operator_says_yes(call) else ToolDenied('not on a Friday')
        for call in deferred.output.approvals
    }
    final = await agent.run(
        message_history=deferred.all_messages(),
        deferred_tool_results=DeferredToolResults(approvals=approvals),
    )

Việc từ chối tiếp cận mô hình dưới dạng kết quả của công cụ, vì vậy agent có thể tự giải thích hoặc thử điều gì đó khác. Khi tiếp tục chạy, phần bảo vệ được đánh giá lại và approve trở thành không hoạt động đối với cuộc gọi mà người đó đã xóa -- mọi phán quyết khác vẫn được áp dụng, do đó, chính sách đã thay đổi ý định vẫn có thể chặn cuộc gọi được phê duyệt.

Hai hình thức phê duyệt và cần đạt được:

Trì hoãn (GuardrailResult.approve())Đang xử lý (bảo vệ không đồng bộ)
Cuộc chạykết thúc rồi tiếp tục từ message_historyvẫn mở; cuộc gọi công cụ đang chờ
Phù hợpAPI HTTP, hàng đợi, thực thi lâu bền, bất kỳ thứ gì không thể giữ quy trình mởCLI, TUI, ứng dụng dành cho máy tính để bàn, websocket cho nhà điều hành
Câu trả lời của con ngườiDeferredToolResults (True, ToolDeniedhoặc ToolApproved(override_args=...))bất cứ điều gì người bảo vệ trả lại

Hình dạng trong quá trình không cần thêm gì -- một người bảo vệ có thể không đồng bộ, vì vậy nó có thể trực tiếp chờ đợi con người:

async def ask_the_operator(call: ToolCallInfo) -> GuardrailResult:
    if await operator_approves(call.name, call.args):
        return GuardrailResult.allow()
    return GuardrailResult.block('The operator declined this action.')


ToolGuardrail(guard=ask_the_operator)

Pydantic AI cũng đưa ra sự chấp thuận mà không cần người bảo vệ: requires_approval=True trên một công cụ, hoặc ApprovalRequiredToolset cho một vị từ đồng bộ trên toàn bộ bộ công cụ. Tiếp cận với ToolGuardrail khi quyết định không đồng bộ, cần deps, hoặc nên ngồi cạnh các phán quyết khác.

Hai lĩnh vực thu hẹp những gì một người bảo vệ nhìn thấy:

ToolGuardrail(
    guard=stay_in_the_workspace,
    tools=['write_file', 'run_shell'],  # guard only these; None (default) guards every tool
    hidden=['delete_everything'],       # withhold these from the model entirely
)

hidden không phải là một danh sách chặn có tên đẹp hơn. Một công cụ ẩn được loại bỏ khỏi các định nghĩa được gửi tới mô hình, do đó, nó không tốn token và mô hình không bao giờ thử nó; một công cụ bị chặn vẫn hiển thị và mô hình biết rằng nó đã bị từ chối. Việc ẩn lấy một danh sách tên tĩnh -- đối với chính sách phụ thuộc vào deps hoặc trên các đối số, sử dụng guard.

Đã định cấu hình hidden tên được kiểm tra khi quá trình chạy hoàn tất thành công. Đã định cấu hình tools tên chỉ được kiểm tra khi guard hoặc result_guard được thiết lập. Một năng động bộ công cụ có thể bỏ qua một công cụ ở một bước và cung cấp nó sau, vì vậy một cái tên không bao giờ xuất hiện là tín hiệu lỗi đánh máy duy nhất. Lời cảnh báo đó bắt được tools=['send_monye'], nếu không thì không bảo vệ công cụ dự định và viết sai chính tả hidden tên, nếu không thì vẫn hiển thị với mô hình.

Những gì một người bảo vệ công cụ không nhìn thấy

Ba loại lệnh gọi không bao giờ đến được móc thực thi, vì vậy cả hai đều không guard cũng không result_guard được tư vấn cho họ:

  • Công cụ đầu ra, tạo ra đầu ra có cấu trúc của agent. Màn hình đó với OutputGuardrail.
  • Các công cụ bên ngoài và trì hoãn, được áp dụng cho ứng dụng của bạn DeferredToolRequests thay vì thực thi. AI Pydantic từ chối chúng trước khi bất kỳ móc thực thi nào chạy, do đó, người bảo vệ không thể kiểm tra các đối số -- ứng dụng của bạn là thứ thực thi chúng và séc thuộc về nơi đó. hidden có bao gồm chúng vì nó hoạt động dựa trên các định nghĩa công cụ.
  • Các công cụ dựng sẵn phía nhà cung cấp chẳng hạn như tìm kiếm trên web, chạy bên trong nhà cung cấp và quay trở lại dưới dạng các phần gọi/trả lại dựng sẵn thay vì thực thi công cụ.

A ToolGuardrail là quyền kiểm soát các công cụ mà lần chạy này thực thi. Đối với những người không có, hidden là đòn bẩy vẫn được áp dụng.

Truyền phát

OutputGuardrail chỉ kiểm tra đầu ra cuối cùng -- trong khi run_stream() các đoạn một phần đến được với người gọi trước khi người bảo vệ chạy, vì vậy block hoặc replace phán quyết không thể hủy gửi nội dung đã được phát trực tuyến. sử dụng run() / run_sync() khi đầu ra phải được sàng lọc trước khi bất kỳ thông tin nào bị lộ. GuardrailResult.retry() không được hỗ trợ theo run_stream() và bề mặt ở đó như UnexpectedModelBehavior. InputGuardrail (bao gồm cả parallel=True) hoạt động giống nhau trong các lần chạy được truyền trực tuyến và không phát trực tuyến.

Truy tìm

replace và block được ghi lại dưới dạng các khoảng thời gian trên trình theo dõi OpenTelemetry đang hoạt động, do đó việc chỉnh sửa hoặc từ chối sẽ hiển thị trong Logfire dấu vết (guardrail redacted input, guardrail blocked output, v.v.) với guardrail.* thuộc tính. Thuộc tính nội dung -- giá trị ban đầu/thay thế cho việc biên tập và từ chối message cho một khối -- được đính kèm chỉ khi RunContext.trace_include_content được bật, vì những thứ này có thể trích dẫn chính nội dung mà người bảo vệ tồn tại để tránh dấu vết.

Công cụ kéo dài thêm một guardrail.tool thuộc tính đặt tên cho công cụ. approve hồ sơ guardrail deferred tool args, luôn mang theo guardrail.tool_call_id vì vậy khoảng thời gian có thể tương quan với DeferredToolRequests ứng dụng trả lời và mang theo guardrail.arguments dưới cùng trace_include_content quy tắc như các thuộc tính nội dung khác. Cuộc gọi hoãn lại không bao giờ được thực hiện, do đó không có execute_tool ghi lại những gì được yêu cầu.

OutputGuardrail định vị các khoảng khối/làm lại của nó để chúng luôn bị bắt bởi một khung bao quanh Instrumentation trải dài bất kể thứ tự khả năng, trong khi InputGuardrail chạy trong cùng nên bất kỳ khả năng nào biến đổi thông báo (trình viết lại lời nhắc, trình quản lý bối cảnh) sẽ chạy trước và người bảo vệ sẽ nhìn thấy lời nhắc cuối cùng mà mô hình sẽ nhận được. ToolGuardrail cũng chạy trong cùng, đặt nó ở vị trí cuối cùng trong số các móc đối số (nó nhìn thấy các đối số mà mọi khả năng khác đã sửa đổi xong) và đầu tiên trong số các móc kết quả (nó nhìn thấy kết quả của công cụ thô, trước một khả năng như ToolOutputLimits cắt bớt hoặc giảm tải nó).

Mối quan hệ với pydantic-ai-shields

pydantic-ai-shields vận chuyển từng máy dò theo khả năng riêng của nó. Thay vào đó, những thứ tương đương ở đây là các hàm, cho phép một số chạy như một chuỗi, chia sẻ một bản chỉnh sửa và ngồi bên cạnh người bảo vệ mà bạn đã viết.

Có hai điều mà điều này cố tình không làm. của nó PromptInjection khớp với các cụm từ như "bỏ qua các hướng dẫn trước đó": chèn là ngôn ngữ thông thường, do đó, danh sách mẫu sẽ nắm bắt các ví dụ và bỏ lỡ cuộc tấn công trong khi gắn cờ nhật ký đã dán và việc kiểm tra đọc là bảo vệ mà không bị coi là tệ hơn là không có. Của nó NoRefusals chặn mô hình từ chối, đó là quyết định về những gì một agent có thể nói thay vì một rào cản bảo vệ dữ liệu và không phải là một quyết định để đưa ra mặc định.

API

InputGuardrail(
    guard,              # one guard, or a sequence run in order
    parallel=False,     # run concurrently with the model call
)

OutputGuardrail(
    guard,              # one guard, or a sequence run in order
)

ToolGuardrail(
    guard=None,         # inspects a ToolCallInfo before the tool runs
    result_guard=None,  # inspects a ToolResultInfo after it runs
    tools=None,         # Sequence[str] | None -- restrict both guards to these tool names
    hidden=(),          # Sequence[str] -- withhold these tools from the model entirely
)

Guard có thể gọi được sẽ nhận giá trị được kiểm tra -- lời nhắc cho InputGuardrail, đầu ra cho OutputGuardrail, a ToolCallInfo hoặc ToolResultInfo cho ToolGuardrail -- tùy ý đứng trước bởi một RunContext. InputGuardrailFunc, OutputGuardrailFunc, ToolGuardrailFuncvà ToolResultGuardrailFunc là các bí danh chữ ký được xuất; GuardrailError là cơ sở cho InputBlocked, OutputBlockedvà ToolBlocked.

Nguồn: pydantic_ai_harness/guardrails/.

Mục lục nguồn