七、自定义工具与中间件:能力可插拔

七、自定义工具与中间件:能力可插拔

Agent Framework 工程实战系列

本篇定位

这一篇解决的问题是:建立可扩展治理层和执行层。

我按真实项目的推进顺序来写,尽量把“为什么这么做”和“怎么落地”都讲清楚。

学习目标

  1. 把 Middleware 这块的职责边界说清楚。
  2. 给出能直接照着改的 C# 示例。
  3. 把上线前最容易漏掉的点提前补上。
  4. 补齐可复用的测试样例,方便放进 CI 做回归。

场景约束

  1. 团队技术栈:.NET 8 + ASP.NET Core + xUnit。
  2. 运行环境:容器化部署,日志走标准输出。
  3. 交付目标:先稳定,再追求花哨能力。
  4. 质量底线:错误可定位、行为可解释、风险可回滚。

架构设计

先把边界划清楚,再谈实现细节。这里用四层结构来组织:

  1. Application:业务编排与用例入口。
  2. Domain:规则和核心模型。
  3. Infrastructure:外部依赖、存储、网络访问。
  4. Observability:日志、指标、追踪和告警。

实际执行时,建议按“先校验输入,再执行业务,最后统一返回和兜底”来走,排查问题会轻松很多。

目录建议

src/
  Middleware/
    Api/
    Application/
    Domain/
    Infrastructure/
    Observability/
    Program.cs
tests/
  Middleware.Tests/

核心实现(C#)

using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Middleware;

public sealed record AgentRequest(
    string SessionId,
    string UserId,
    string Query,
    Dictionary<string, string>? Metadata = null);

public sealed record AgentResponse(
    bool Success,
    string Message,
    string? ErrorCode = null,
    Dictionary<string, object>? Data = null,
    string? TraceId = null);

public interface IRequestValidator
{
    void EnsureAllowed(AgentRequest request);
}

public interface IExecutor
{
    Task<Dictionary<string, object>> ExecuteAsync(AgentRequest request, CancellationToken ct = default);
}

public interface IResponseMapper
{
    AgentResponse Compose(AgentRequest request, Dictionary<string, object> result, string traceId);
}

public sealed class RequestValidator : IRequestValidator
{
    public void EnsureAllowed(AgentRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.SessionId))
            throw new ArgumentException("sessionId 不能为空");

        if (string.IsNullOrWhiteSpace(request.UserId))
            throw new ArgumentException("userId 不能为空");

        if (string.IsNullOrWhiteSpace(request.Query))
            throw new ArgumentException("query 不能为空");

        if (request.Query.Length > 2000)
            throw new ArgumentException("query 长度超出限制");
    }
}

public sealed class MiddlewareExecutor : IExecutor
{
    private readonly ILogger<MiddlewareExecutor> _logger;

    public MiddlewareExecutor(ILogger<MiddlewareExecutor> logger)
    {
        _logger = logger;
    }

    public async Task<Dictionary<string, object>> ExecuteAsync(AgentRequest request, CancellationToken ct = default)
    {
        var sw = Stopwatch.StartNew();

        // 示例里先用 Task.Delay 模拟一次 I/O,接入真实系统时换成 API/仓储调用。
        await Task.Delay(25, ct);

        var output = new Dictionary<string, object>
        {
            ["topic"] = "Middleware",
            ["query"] = request.Query,
            ["status"] = "ok",
            ["handler"] = "MiddlewareExecutor",
            ["timestamp"] = DateTimeOffset.UtcNow,
            ["hints"] = new[]
            {
                "先给可执行结论",
                "把关键前置条件写清楚",
                "失败时给下一步处理建议"
            }
        };

        sw.Stop();
        _logger.LogInformation("execute finished topic={Topic} costMs={CostMs}", "Middleware", sw.ElapsedMilliseconds);

        output["costMs"] = sw.ElapsedMilliseconds;
        return output;
    }
}

public sealed class ResponseMapper : IResponseMapper
{
    public AgentResponse Compose(AgentRequest request, Dictionary<string, object> result, string traceId)
    {
        var summary = $"这次请求已经处理完成(主题:Middleware),原始问题:{request.Query}";

        return new AgentResponse(
            Success: true,
            Message: summary,
            Data: result,
            TraceId: traceId
        );
    }
}

public sealed class AgentOrchestrator
{
    private readonly IRequestValidator _validator;
    private readonly IExecutor _executor;
    private readonly IResponseMapper _mapper;
    private readonly ILogger<AgentOrchestrator> _logger;

    public AgentOrchestrator(
        IRequestValidator validator,
        IExecutor executor,
        IResponseMapper mapper,
        ILogger<AgentOrchestrator> logger)
    {
        _validator = validator;
        _executor = executor;
        _mapper = mapper;
        _logger = logger;
    }

    public async Task<AgentResponse> HandleAsync(AgentRequest request, CancellationToken ct = default)
    {
        var traceId = Activity.Current?.TraceId.ToString() ?? Guid.NewGuid().ToString("N");

        try
        {
            _validator.EnsureAllowed(request);
            var result = await _executor.ExecuteAsync(request, ct);
            return _mapper.Compose(request, result, traceId);
        }
        catch (ArgumentException ex)
        {
            _logger.LogWarning(ex, "validation failed");
            return new AgentResponse(false, "请求参数不合法", "BAD_REQUEST", null, traceId);
        }
        catch (OperationCanceledException)
        {
            _logger.LogWarning("request canceled");
            return new AgentResponse(false, "请求已取消", "CANCELED", null, traceId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "unexpected error");
            return new AgentResponse(false, "服务暂时不可用", "INTERNAL_ERROR", null, traceId);
        }
    }
}

public static class Bootstrap
{
    public static WebApplication BuildApp(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        builder.Services.AddLogging();
        builder.Services.AddSingleton<IRequestValidator, RequestValidator>();
        builder.Services.AddSingleton<IExecutor, MiddlewareExecutor>();
        builder.Services.AddSingleton<IResponseMapper, ResponseMapper>();
        builder.Services.AddSingleton<AgentOrchestrator>();

        var app = builder.Build();

        app.MapPost("/agent/run", async (AgentRequest req, AgentOrchestrator orchestrator, CancellationToken ct) =>
        {
            var res = await orchestrator.HandleAsync(req, ct);
            return Results.Json(res);
        });

        app.MapGet("/health", () => Results.Ok(new { status = "ok", topic = "Middleware" }));

        return app;
    }
}

配置示例(appsettings.json)

{
  "App": {
    "Name": "MiddlewareAgent",
    "Environment": "Development",
    "MaxQueryLength": 2000,
    "DefaultTimeoutMs": 10000
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning"
    }
  }
}

启动入口(Program.cs)

using Middleware;

var app = Bootstrap.BuildApp(args);
app.Run();

测试代码(xUnit)

using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

namespace Middleware.Tests;

public class MiddlewareTests
{
    [Fact]
    public async Task HandleAsync_WithValidRequest_ShouldReturnSuccess()
    {
        var validator = new RequestValidator();
        var executor = new MiddlewareExecutor(new NullLogger<MiddlewareExecutor>());
        var mapper = new ResponseMapper();
        var orchestrator = new AgentOrchestrator(
            validator,
            executor,
            mapper,
            new NullLogger<AgentOrchestrator>());

        var req = new AgentRequest(
            SessionId: "s-001",
            UserId: "u-001",
            Query: "请执行Middleware流程");

        var res = await orchestrator.HandleAsync(req);

        Assert.True(res.Success);
        Assert.NotNull(res.Data);
        Assert.True(res.Data!.ContainsKey("topic"));
        Assert.Equal("Middleware", res.Data["topic"]?.ToString());
    }

    [Fact]
    public async Task HandleAsync_WithEmptyQuery_ShouldReturnBadRequest()
    {
        var validator = new RequestValidator();
        var executor = new MiddlewareExecutor(new NullLogger<MiddlewareExecutor>());
        var mapper = new ResponseMapper();
        var orchestrator = new AgentOrchestrator(
            validator,
            executor,
            mapper,
            new NullLogger<AgentOrchestrator>());

        var req = new AgentRequest(
            SessionId: "s-002",
            UserId: "u-002",
            Query: "");

        var res = await orchestrator.HandleAsync(req);

        Assert.False(res.Success);
        Assert.Equal("BAD_REQUEST", res.ErrorCode);
    }

    [Fact]
    public async Task HandleAsync_WhenCanceled_ShouldReturnCanceled()
    {
        var validator = new RequestValidator();
        var executor = new MiddlewareExecutor(new NullLogger<MiddlewareExecutor>());
        var mapper = new ResponseMapper();
        var orchestrator = new AgentOrchestrator(
            validator,
            executor,
            mapper,
            new NullLogger<AgentOrchestrator>());

        using var cts = new CancellationTokenSource();
        cts.Cancel();

        var req = new AgentRequest(
            SessionId: "s-003",
            UserId: "u-003",
            Query: "测试取消");

        var res = await orchestrator.HandleAsync(req, cts.Token);

        Assert.False(res.Success);
        Assert.Equal("CANCELED", res.ErrorCode);
    }
}

运行步骤

  1. 安装依赖:dotnet restore
  2. 启动服务:dotnet run --project src/Middleware
  3. 调用接口:
curl -X POST http://localhost:5000/agent/run \
  -H 'Content-Type: application/json' \
  -d '{
    "sessionId":"s-100",
    "userId":"u-200",
    "query":"请处理 Middleware 请求"
  }'
  1. 执行测试:dotnet test

常见问题与排查

1. 返回 BAD_REQUEST

检查点:

  1. 是否传了空 query。
  2. sessionId 和 userId 是否为空。
  3. 请求体字段命名是否与模型一致。

2. 返回 INTERNAL_ERROR

检查点:

  1. 执行器里是否抛了未处理异常。
  2. 依赖是否注入完整。
  3. 日志级别是否过滤了错误日志。

3. 偶发超时

检查点:

  1. 默认超时是否过短。
  2. 外部依赖是否慢查询。
  3. 是否需要缓存或并发拆分。

生产化建议

  1. 每个执行器都加超时、重试和断路策略。
  2. 审计日志记录 userId + tool + traceId。
  3. 错误码与文案分离,避免泄露内部细节。
  4. 对外统一响应结构,便于前端和调用方处理。
  5. 把核心规则放在 Domain 层,减少横向耦合。
  6. 高风险动作默认二次确认。
  7. 先做灰度发布,再全量切换。
  8. 关键指标至少包含:成功率、P95、P99、成本。
  9. 对关键流程做回归评测集。
  10. 预留降级路径,避免全链路故障。

小结

Middleware 的核心不是“把功能写出来”,而是把输入、执行、输出、治理和运营放到同一条工程链路里。

当你把这篇里的结构固定下来,后续扩展能力时,稳定性和可维护性会明显提升。

下一篇

第08篇:监控与可观测性。

posted @ 2026-03-17 14:42  初久的私房菜  阅读(5494)  评论(0)    收藏  举报
作者:初久的私房菜
好好学习,天天向上
返回顶部小火箭
好友榜:
如果愿意,把你的博客地址放这里
张弛:https://blog.zhangchi.fun/