| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- package securityaudit
- import (
- "context"
- "errors"
- "net/http"
- "sync"
- )
- type LegacyEngine interface {
- Check(ctx context.Context, req Request) (*LegacyDecision, error)
- }
- type PromptEngine interface {
- EffectiveMode() Mode
- Enqueue(ctx context.Context, req Request) error
- Evaluate(ctx context.Context, req Request) (*PromptDecision, error)
- }
- type Coordinator struct {
- legacy LegacyEngine
- prompt PromptEngine
- }
- func NewCoordinator(legacy LegacyEngine, prompt PromptEngine) *Coordinator {
- return &Coordinator{legacy: legacy, prompt: prompt}
- }
- func (c *Coordinator) Check(ctx context.Context, req Request) Decision {
- if c == nil {
- return allowDecision(nil, nil)
- }
- mode := ModeOff
- if c.prompt != nil {
- mode = c.prompt.EffectiveMode()
- }
- switch mode {
- case ModeAsync:
- // Enqueue is deliberately best-effort. The implementation owns a bounded
- // context and copies request memory before it can outlive the Handler.
- _ = c.prompt.Enqueue(ctx, req.Clone())
- legacy, _ := c.checkLegacy(ctx, req)
- return prioritize(legacy, nil)
- case ModeBlocking:
- return c.checkBlocking(ctx, req)
- default:
- legacy, _ := c.checkLegacy(ctx, req)
- return prioritize(legacy, nil)
- }
- }
- func (c *Coordinator) checkBlocking(ctx context.Context, req Request) Decision {
- var wg sync.WaitGroup
- wg.Add(2)
- var legacy *LegacyDecision
- var prompt *PromptDecision
- go func() {
- defer wg.Done()
- legacy, _ = c.checkLegacy(ctx, req)
- }()
- go func() {
- defer wg.Done()
- if c.prompt == nil {
- prompt = unavailablePromptDecision(ErrorCodeUnavailable)
- return
- }
- result, err := c.prompt.Evaluate(ctx, req.Clone())
- if err != nil {
- var guardErr *GuardError
- if errors.As(err, &guardErr) && guardErr.Code == ErrorCodeInvalidResponse {
- prompt = unavailablePromptDecision(ErrorCodeInvalidResponse)
- return
- }
- prompt = unavailablePromptDecision(ErrorCodeUnavailable)
- return
- }
- if result == nil {
- prompt = unavailablePromptDecision(ErrorCodeUnavailable)
- return
- }
- prompt = result
- }()
- wg.Wait()
- return prioritize(legacy, prompt)
- }
- func (c *Coordinator) checkLegacy(ctx context.Context, req Request) (*LegacyDecision, error) {
- if c.legacy == nil {
- return nil, nil
- }
- return c.legacy.Check(ctx, req)
- }
- func prioritize(legacy *LegacyDecision, prompt *PromptDecision) Decision {
- if legacy != nil && legacy.Blocked {
- status := legacy.StatusCode
- if status < 400 || status > 599 {
- status = http.StatusForbidden
- }
- code := legacy.ErrorCode
- if code == "" {
- code = "content_policy_violation"
- }
- return Decision{
- Kind: DecisionBlock, HTTPStatus: status, ErrorCode: code, ClientMessage: legacy.Message,
- Legacy: legacy, Prompt: prompt, AllowNextStage: false,
- }
- }
- if prompt == nil {
- return allowDecision(legacy, nil)
- }
- switch prompt.Kind {
- case DecisionBlock:
- return Decision{Kind: DecisionBlock, HTTPStatus: http.StatusForbidden, ErrorCode: ErrorCodeBlocked,
- ClientMessage: "提示词安全审计拒绝了该请求,请调整输入后重试", Legacy: legacy, Prompt: prompt}
- case DecisionInvalid:
- return Decision{Kind: DecisionInvalid, HTTPStatus: http.StatusServiceUnavailable, ErrorCode: ErrorCodeInvalidResponse,
- ClientMessage: "提示词安全审计暂时不可用,请稍后重试", Legacy: legacy, Prompt: prompt}
- case DecisionUnavailable:
- return Decision{Kind: DecisionUnavailable, HTTPStatus: http.StatusServiceUnavailable, ErrorCode: ErrorCodeUnavailable,
- ClientMessage: "提示词安全审计暂时不可用,请稍后重试", Legacy: legacy, Prompt: prompt}
- case DecisionFlag:
- return Decision{Kind: DecisionFlag, HTTPStatus: http.StatusOK, Legacy: legacy, Prompt: prompt, AllowNextStage: true}
- default:
- return allowDecision(legacy, prompt)
- }
- }
- func allowDecision(legacy *LegacyDecision, prompt *PromptDecision) Decision {
- return Decision{Kind: DecisionAllow, HTTPStatus: http.StatusOK, Legacy: legacy, Prompt: prompt, AllowNextStage: true}
- }
- func unavailablePromptDecision(code string) *PromptDecision {
- kind := DecisionUnavailable
- if code == ErrorCodeInvalidResponse {
- kind = DecisionInvalid
- }
- return &PromptDecision{Kind: kind, ErrorCode: code, AllowNextStage: false}
- }
|