端到端追踪至LLM智能体流水线 | AI生成和翻译

Home 2025.03

你的方向很明确:AOP(AspectJ)、Servlet过滤器和Log4j2各自在不同层级发挥着作用。以下是一个简洁的全链路方案——首先用于采集执行轨迹和HTTP输入输出(以便自动生成Python测试),随后将丰富上下文信息提供给Spring中的LLM“智能体”接口


1) 捕获所需信息(AOP + 过滤器 + Log4j2)

a) 使用AOP进行方法拦截(记录耗时、参数、返回结果/异常)。

通过@Around增强切面包装服务方法,将traceId存入MDC实现全链路日志关联。

@Aspect
@Component
public class TraceAspect {
  @Around("execution(* com.example.service..*(..))")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    String traceId = java.util.UUID.randomUUID().toString();
    org.apache.logging.log4j.ThreadContext.put("traceId", traceId);
    long t0 = System.nanoTime();
    try {
      Object out = pjp.proceed();
      org.apache.logging.log4j.LogManager.getLogger(pjp.getTarget().getClass())
        .info("AOP", "method={},args={},ret={},ms={},traceId={}",
              pjp.getSignature().toShortString(),
              java.util.Arrays.toString(pjp.getArgs()), out,
              (System.nanoTime()-t0)/1_000_000.0, traceId);
      return out;
    } catch (Throwable e) {
      org.apache.logging.log4j.LogManager.getLogger(pjp.getTarget().getClass())
        .error("AOP_ERR", "method={},args={},err={},traceId={}",
               pjp.getSignature().toShortString(),
               java.util.Arrays.toString(pjp.getArgs()),
               e.toString(), traceId);
      throw e;
    } finally {
      org.apache.logging.log4j.ThreadContext.remove("traceId");
    }
  }
}

(当需要控制方法调用并实现前后增强逻辑时,”环绕增强”是最佳选择。(Spring官方文档, DigitalOcean教程))

b) 使用过滤器记录HTTP日志(请求/响应体及状态码)。

通过OncePerRequestFilter模式包装请求/响应,关联相同traceId(从请求头获取或自动生成)。优质教程会展示如何安全缓存请求体并避免重复消费。(Baeldung Kotlin教程, SigNoz指南

c) 使用Log4j2路由附加器按用户/租户/测试分流日志。

通过该功能可将匹配的日志(如根据MDC中的traceIdtenantId)写入独立文件,后续用于生成测试用例。

<Appenders>
  <Routing name="ByTrace">
    <Routes pattern="${ctx:traceId}">
      <Route ref="RollingTemplate" key="${ctx:traceId}"/>
    </Routes>
  </Routing>

  <RollingFile name="RollingTemplate"
               fileName="logs/${ctx:traceId}.log"
               filePattern="logs/${ctx:traceId}-%d{yyyy-MM-dd}.gz">
    <PatternLayout pattern="%d %p %c %X{traceId} - %m%n"/>
    <Policies><TimeBasedTriggeringPolicy/></Policies>
  </RollingFile>
</Appenders>
<Loggers>
  <Root level="info">
    <AppenderRef ref="ByTrace"/>
  </Root>
</Loggers>

(Log4j2路由附加器配合MDC是实现日志按关键字分流的标准方案。(Apache日志文档, Roy教程, Stack Overflow解答))


2) 将日志转化为Python测试(pytest + requests)

当日志转为JSON格式或可解析文本后,可通过轻量生成器输出确定性测试:

# gen_tests_from_logs.py
import json, re, pathlib

def extract_calls(log_text):
    calls = []
    for line in log_text.splitlines():
        if '"HTTP_IN"' in line or 'HTTP_IN' in line:
            d = json.loads(re.search(r'({.*})', line).group(1))
            calls.append({
              "method": d["method"],
              "url": d["path"],
              "headers": d.get("headers", {}),
              "body": d.get("body", None),
              "expect_status": d.get("status", 200)
            })
    return calls

def emit_pytest(calls):
    lines = [
      "import requests",
      "import pytest",
      "",
      "@pytest.mark.parametrize('call', ["
    ]
    for c in calls:
      lines.append(f"  {json.dumps(c)},")
    lines += ["])",
      "def test_replay(call):",
      "    resp = requests.request(call['method'], 'http://localhost:8080'+call['url'],",
      "                             headers=call.get('headers'),",
      "                             json=call.get('body'))",
      "    assert resp.status_code == call['expect_status']",
      "    # 可选:对响应JSON中的结构化字段进行断言",
    ]
    return "\n".join(lines)

def main():
    all_calls = []
    for p in pathlib.Path('logs').glob('*.log'):
      all_calls += extract_calls(p.read_text(encoding='utf-8'))
    pathlib.Path('tests/test_replay_generated.py').write_text(emit_pytest(all_calls), encoding='utf-8')

if __name__ == "__main__":
    main()

注意事项:


3) 通过Spring为LLM“智能体”提供上下文

若已使用Spring Boot,最快捷的方案是Spring AI

a) 配置ChatClient和提示词模板。 Spring AI提供ChatClient/PromptTemplate抽象和工具调用能力,使模型能请求应用获取数据。(官网, 工具调用文档

@Configuration
public class AiConfig {
  @Bean ChatClient chatClient(org.springframework.ai.chat.ChatModel model) {
    return ChatClient.builder(model).defaultSystem("You are a helpful banking agent.").build();
  }
}

b) 通过Spring Bean(服务)和“工具”提供上下文。 将领域查询暴露为工具,使模型在对话过程中可动态调用。(Spring AI支持工具调用——由模型决定调用时机,结果将作为附加上下文返回。)(工具调用文档

@Component
public class AccountTools {
  @org.springframework.ai.tool.annotation.Tool
  public String fetchBalance(String userId){
    // 查询数据库或下游服务
    return "{\"balance\": 1234.56, \"currency\": \"HKD\"}";
  }
}

c) 为文档/代码添加检索功能(RAG)。 配置VectorStore(如PGVector)并注入知识嵌入向量。运行时检索最相关的文本块并附加到提示词中。现有实用教程详细演示了基于Spring Boot + PGVector构建完整RAG方案。(sohamkamani.com

d) 通过拦截器传递用户/会话上下文。 使用HandlerInterceptor(或自定义过滤器)解析userIdtenantId、角色、区域设置、近期操作等——将其注入:

e) 统一接入的HTTP端点。

@RestController
@RequestMapping("/agent")
public class AgentController {
  private final ChatClient chat;
  private final UserContextProvider ctx;
  private final Retriever retriever;

  public AgentController(ChatClient chat, UserContextProvider ctx, Retriever retriever) {
    this.chat = chat; this.ctx = ctx; this.retriever = retriever;
  }

  @PostMapping
  public String chat(@RequestBody Map<String,Object> req, Principal principal) {
    var userCtx = ctx.fromPrincipal(principal);          // 身份信息、角色、偏好设置
    var docs   = retriever.findRelevant((String)req.get("message")); // RAG检索
    return chat.prompt()
      .system("Use tools if needed. Respect tenant="+userCtx.tenant()+" and role="+userCtx.role()+".")
      .user("Context:\n"+docs+"\n\nUser says: "+req.get("message"))
      .call().content();                                  // 工具调用自动执行
  }
}

f) 可选:通过Spring Cloud Function部署“技能”。 若需将每个智能体技能作为独立函数部署(适用于无服务器架构和按请求头路由),Spring Cloud Function支持编写纯函数Function<T,R>并动态路由——便于接入AWS Lambda/Azure,也可作为内部“智能体工具”端点。(云平台文档, 官方文档, Baeldung Kotlin教程


4) 全链路整合(流程)

  1. 过滤器添加traceId,记录HTTP输入输出
  2. AOP包装服务方法并输出结构化事件
  3. Log4j2路由附加器按跟踪标识写入独立文件
  4. Python生成器读取日志文件 → 输出可回放真实调用的pytest
  5. Spring AI接口使用:

    • 来自拦截器/过滤器的请求/租户/用户上下文
    • 领域“工具”获取实时数据
    • 向量存储(RAG)提供文档/代码上下文
    • ChatClient处理对话编排

该方案既可生成可复现的测试用例,又能提供具备丰富上下文的智能体接口——两者均由同一套遥测数据驱动。

如需实现,我可以提供包含Spring Boot组件、Log4j2配置和Python生成器脚本的最小化仓库结构,并集成到make testgen && pytest工作流中。


Back Donate