九:Text-to-SQL 智能数据查询与 Human-in-the-Loop 人机协作

从零构建智能体:核心架构、实践指南与主流框架对比 智能体(Agent)作为人工智能领域的关键概念,其核心在于模拟自主决策与目标导向行为。其工作原理通常基于感知-规划-行动循环,并借助大语言模型(LLM)进行推理与决策。这一架构的技术价值在于将软件从被动响应的工具,转变为能主动处理复杂任务的智能实体,极大地拓展了自动化边界。在实际应用场景中,智能体广泛落地于自动化办公、智能客服、数据分析与流程编排等领域,成为构建下一代人机交互系统的核心组件。本文以构建“智能会议纪要助手”这一具体任务型智能体为例,深入剖析了其技术选型、核心循环实现与关键避坑点,并系统对比了L 阅读详情

系列第九篇:Text-to-SQL 智能数据查询与 Human-in-the-Loop 人机协作

前言

前八篇文章覆盖了 Agent 的构建、协作、评估、记忆、运维、检索、安全和推理。但在企业实践中,有两个高频需求始终被反复提及:

需求一:“用户能不能用自然语言直接查数据库?不用写 SQL、不用看报表,直接问’A 栋上个月每天用电量是多少’就能出结果?”

需求二:“Agent 自主执行一些操作(发通知、调参数)太危险了,能不能让它做到关键步骤时停下来等人确认?”

这两个需求分别对应 Text-to-SQL 和 Human-in-the-Loop——本文将深入实现。


一、Text-to-SQL:让 Agent 直接查数据库

1.1 为什么需要 Text-to-SQL?

在能源管理系统中,大量的分析需求是 临时性的、探索性的:

  • “A 栋上个月每天用电量是多少?”
  • “本月能耗最高的三个区域是哪些?”
  • “C 栋数据中心近半年每月的电费趋势?”
  • “哪个区域的空调能耗占总能耗比例最高?”

这些查询无法通过预定义的 API 或固定的 @Tool 方法覆盖——因为用户的查询维度是无限的。传统的做法是开发几十上百个查询接口,而 Text-to-SQL 提供了一种更优雅的解法:让 Agent 自主生成 SQL,直接查询数据库。

1.2 Text-to-SQL 的核心挑战

Text-to-SQL 看似简单(“把中文翻译成 SQL”),实际挑战重重:

┌──────────────────────────────────────────────────────────┐
│              Text-to-SQL 四大挑战                         │
│                                                          │
│  挑战1: Schema 理解                                      │
│  → 模型必须理解表结构、字段含义、表间关系                  │
│  → 能源系统的表可能有几十张,字段命名不一定直观           │
│                                                          │
│  挑战2: 复杂查询                                         │
│  → 多表 JOIN、子查询、窗口函数、聚合分析                  │
│  → "每月同比" 需要自连接 + 日期计算                       │
│                                                          │
│  挑战3: SQL 方言                                         │
│  → 项目使用达梦数据库(DM),语法与 MySQL 有差异          │
│  → 还有 ClickHouse 做时序分析,语法差异更大              │
│                                                          │
│  挑战4: 安全性                                           │
│  → 绝不能生成 DELETE/UPDATE/DROP 等写入语句               │
│  → 必须限制查询范围(只允许特定表、必须有 LIMIT)         │
│                                                          │
└──────────────────────────────────────────────────────────┘

1.3 架构设计:三层 Text-to-SQL Pipeline

用户问题: "A栋上个月每天的用电量"
    │
    ▼
┌──────────────────────────────────────┐
│  Layer 1: Schema Linking             │
│  确定相关表和字段                     │
│  → 表: energy_daily_consumption      │
│  → 字段: area_name, consume_date,    │
│          electricity_kwh             │
└──────────────┬───────────────────────┘
               │
               ▼
┌──────────────────────────────────────┐
│  Layer 2: SQL Generation             │
│  生成 SQL                            │
│  → SELECT consume_date,              │
│      electricity_kwh                 │
│    FROM energy_daily_consumption     │
│    WHERE area_name = 'A栋办公楼'     │
│      AND consume_date >= '2026-07-01'│
│      AND consume_date < '2026-08-01' │
│    ORDER BY consume_date             │
└──────────────┬───────────────────────┘
               │
               ▼
┌──────────────────────────────────────┐
│  Layer 3: Validation + Execution     │
│  安全校验 → 执行 → 结果格式化        │
│  → 校验通过 (SELECT only, 有 LIMIT) │
│  → 执行并返回 31 行数据             │
│  → 格式化为 Markdown 表格/图表描述   │
└──────────────────────────────────────┘

二、Schema 管理:让模型"看懂"你的数据库

2.1 Schema 信息提取

模型要生成正确的 SQL,首先必须理解数据库的结构。我们需要将 Schema 信息提取出来,以一种模型友好的方式注入 Prompt。

/**
 * Schema 信息管理器
 * 
 * 核心设计:
 * 不是把所有表的 DDL 一股脑塞给模型(那样 Prompt 会很长,
 * 而且大量无关表会干扰模型判断),而是:
 * 
 * 1. 预构建 Schema 摘要(表名、字段、类型、注释、示例数据)
 * 2. 根据用户问题动态选择相关表(Schema Linking)
 * 3. 只将相关表的 Schema 注入 Prompt
 */
@Service
public class SchemaManager {

    private final DataSource dataSource;  // 达梦数据库
    private final ClickHouseDataSource ckDataSource;  // ClickHouse
    private final Map<String, TableSchema> schemaCache =
        new ConcurrentHashMap<>();

    /**
     * 启动时加载所有表的 Schema 信息
     * 
     * 为什么用 JDBC 元数据而非硬编码?
     * 因为数据库 Schema 会随业务迭代变化,
     * 硬编码容易过期,从元数据动态读取最可靠。
     */
    @PostConstruct
    public void loadSchemas() {
        try (Connection conn = dataSource.getConnection()) {
            DatabaseMetaData meta = conn.getMetaData();

            // 获取所有业务表(排除系统表)
            ResultSet tables = meta.getTables(
                null, "ENERGY", "%",
                new String[]{"TABLE"});

            while (tables.next()) {
                String tableName = tables.getString("TABLE_NAME");
                String tableComment = tables.getString("REMARKS");

                // 获取字段信息
                List<ColumnSchema> columns = loadColumns(
                    meta, tableName);

                // 获取示例数据(帮助模型理解字段的实际值)
                List<Map<String, Object>> sampleData =
                    loadSampleData(conn, tableName, 3);

                schemaCache.put(tableName, new TableSchema(
                    tableName, tableComment, columns, sampleData
                ));
            }

            log.info("Schema 加载完成: {} 张表", schemaCache.size());
        } catch (SQLException e) {
            log.error("Schema 加载失败", e);
        }
    }

    /**
     * 加载表的字段信息(包含注释)
     * 
     * 达梦数据库的字段注释通过 REMARKS 字段获取。
     * 注释的质量直接影响 SQL 生成的准确性——
     * 如果字段没有注释,建议手动补充。
     */
    private List<ColumnSchema> loadColumns(DatabaseMetaData meta,
                                            String tableName)
            throws SQLException {
        List<ColumnSchema> columns = new ArrayList<>();

        ResultSet cols = meta.getColumns(
            null, "ENERGY", tableName, "%");

        while (cols.next()) {
            columns.add(new ColumnSchema(
                cols.getString("COLUMN_NAME"),
                cols.getString("TYPE_NAME"),
                cols.getInt("COLUMN_SIZE"),
                cols.getString("REMARKS"),       // 字段注释
                cols.getInt("NULLABLE") == 1      // 是否可空
            ));
        }

        return columns;
    }

    /**
     * 加载示例数据
     * 
     * 为什么需要示例数据?
     * 因为光看字段名和类型,模型可能无法理解字段的实际含义。
     * 
     * 例如:area_code 字段,光看类型是 VARCHAR(32),
     * 但示例值是 "A-BUILDING-01"、"B-BUILDING-02",
     * 模型就能理解这是区域编码而非自由文本。
     */
    private List<Map<String, Object>> loadSampleData(
            Connection conn, String tableName, int limit) {
        List<Map<String, Object>> samples = new ArrayList<>();
        try {
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(
                "SELECT * FROM " + tableName
                + " WHERE ROWNUM <= " + limit);
            ResultSetMetaData rsMeta = rs.getMetaData();

            while (rs.next()) {
                Map<String, Object> row = new LinkedHashMap<>();
                for (int i = 1; i <= rsMeta.getColumnCount(); i++) {
                    row.put(rsMeta.getColumnName(i), rs.getObject(i));
                }
                samples.add(row);
            }
        } catch (SQLException e) {
            // 示例数据加载失败不影响主流程
            log.debug("加载示例数据失败: {}", tableName);
        }
        return samples;
    }

    /**
     * 生成模型可读的 Schema 描述
     * 
     * 格式设计原则:
     * 1. 用 CREATE TABLE DDL 格式(模型训练数据中大量存在,最熟悉)
     * 2. 包含字段注释(COMMENT 子句)
     * 3. 附带 2~3 行示例数据
     * 4. 标注表间关系(外键或业务关联)
     */
    public String formatSchemaForPrompt(TableSchema schema) {
        StringBuilder sb = new StringBuilder();

        // DDL 格式描述
        sb.append(String.format("-- %s\n",
            schema.tableComment() != null
                ? schema.tableComment() : ""));
        sb.append(String.format("CREATE TABLE %s (\n",
            schema.tableName()));

        List<ColumnSchema> cols = schema.columns();
        for (int i = 0; i < cols.size(); i++) {
            ColumnSchema col = cols.get(i);
            String comma = i < cols.size() - 1 ? "," : "";
            sb.append(String.format("  %s %s%s",
                col.name(), col.type(), comma));
            if (col.comment() != null && !col.comment().isEmpty()) {
                sb.append(String.format("  -- %s", col.comment()));
            }
            sb.append("\n");
        }
        sb.append(");\n");

        // 示例数据
        if (!schema.sampleData().isEmpty()) {
            sb.append("\n-- 示例数据:\n");
            for (Map<String, Object> row : schema.sampleData()) {
                sb.append("-- ").append(row).append("\n");
            }
        }

        return sb.toString();
    }
}

public record TableSchema(
    String tableName,
    String tableComment,
    List<ColumnSchema> columns,
    List<Map<String, Object>> sampleData
) {}

public record ColumnSchema(
    String name,
    String type,
    int size,
    String comment,
    boolean nullable
) {}

2.2 Schema Linking:动态选择相关表

数据库可能有几十张表,但一个查询通常只涉及 1~3 张。把所有表的 Schema 都塞进 Prompt 既浪费 Token 又干扰模型。Schema Linking 就是 智能筛选相关表。

/**
 * Schema Linking 服务
 * 
 * 两阶段筛选:
 * 
 * 阶段1: 关键词匹配(快且免费)
 *   从用户问题中提取关键词,匹配表名和字段名
 *   例如提到"能耗" → 匹配 energy_* 相关表
 * 
 * 阶段2: LLM 语义匹配(慢但精准)
 *   将候选表的描述交给 LLM 判断是否相关
 */
@Service
public class SchemaLinkingService {

    private final SchemaManager schemaManager;
    private final ChatLanguageModel linkModel;

    /**
     * 为用户问题选择相关的表
     * 
     * @param userQuery 用户问题
     * @param maxTables 最多返回的表数量(通常 3~5 张)
     * @return 相关表的 Schema 列表
     */
    public List<TableSchema> linkTables(String userQuery,
                                         int maxTables) {

        // 阶段1: 关键词预筛选
        List<TableSchema> candidates = keywordFilter(userQuery);

        if (candidates.size() <= maxTables) {
            return candidates;  // 候选数已经够少,直接返回
        }

        // 阶段2: LLM 语义筛选
        return llmFilter(userQuery, candidates, maxTables);
    }

    /**
     * 关键词预筛选
     * 
     * 维护一个"业务关键词 → 表名"的映射表。
     * 这比让 LLM 做初筛快得多,也便宜得多。
     */
    private List<TableSchema> keywordFilter(String query) {
        // 业务关键词映射(可通过配置文件维护)
        Map<String, List<String>> keywordTableMap = Map.of(
            "能耗,用电,电量,电费,kwh,千瓦时",
                List.of("energy_daily_consumption",
                        "energy_monthly_summary"),
            "告警,预警,异常,报警",
                List.of("energy_alert_log",
                        "energy_alert_config"),
            "设备,空调,照明,电梯,变压器",
                List.of("device_info",
                        "device_energy_consumption"),
            "区域,楼栋,楼层,房间",
                List.of("area_info",
                        "area_energy_quota"),
            "工单,维修,巡检,保养",
                List.of("maintenance_order",
                        "inspection_record"),
            "趋势,同比,环比,对比",
                List.of("energy_daily_consumption",
                        "energy_monthly_summary",
                        "energy_yearly_summary")
        );

        Set<String> matchedTables = new LinkedHashSet<>();
        String lowerQuery = query.toLowerCase();

        for (var entry : keywordTableMap.entrySet()) {
            String[] keywords = entry.getKey().split(",");
            for (String keyword : keywords) {
                if (lowerQuery.contains(keyword.trim())) {
                    matchedTables.addAll(entry.getValue());
                    break;
                }
            }
        }

        // 如果没有匹配到任何表,返回最常用的核心表
        if (matchedTables.isEmpty()) {
            matchedTables.add("energy_daily_consumption");
            matchedTables.add("area_info");
        }

        return matchedTables.stream()
            .map(name -> schemaManager.getSchema(name))
            .filter(Objects::nonNull)
            .toList();
    }

    private List<TableSchema> llmFilter(String query,
                                         List<TableSchema> candidates,
                                         int maxTables) {
        String tableDescriptions = candidates.stream()
            .map(t -> String.format("- %s: %s (字段: %s)",
                t.tableName(),
                t.tableComment(),
                t.columns().stream()
                    .map(c -> c.name() + "(" +
                        (c.comment() != null ? c.comment() : "") + ")")
                    .collect(Collectors.joining(", "))))
            .collect(Collectors.joining("\n"));

        String prompt = String.format("""
            根据用户的问题,从以下数据库表中选择最相关的 %d 张表。
            只返回表名,每行一个。
            
            可用表:
            %s
            
            用户问题:%s
            """, maxTables, tableDescriptions, query);

        String result = linkModel.generate(
            UserMessage.from(prompt)).content().text().trim();

        return Arrays.stream(result.split("\n"))
            .map(String::trim)
            .map(schemaManager::getSchema)
            .filter(Objects::nonNull)
            .limit(maxTables)
            .toList();
    }
}

三、SQL 生成与安全校验

3.1 SQL 生成引擎

/**
 * Text-to-SQL 生成服务
 * 
 * 核心策略:
 * 1. 提供完整的 Schema 上下文(DDL + 示例 + 表关系)
 * 2. 明确指定 SQL 方言(达梦 DM SQL)
 * 3. 提供 few-shot 示例(常见查询的 SQL 模板)
 * 4. 要求模型只输出 SQL,不输出解释
 */
@Service
public class SqlGenerationService {

    private final ChatLanguageModel sqlModel;
    private final SchemaLinkingService schemaLinker;

    /**
     * 根据自然语言问题生成 SQL
     * 
     * @param userQuery  用户的自然语言问题
     * @param timeContext 时间上下文("上个月" → 具体日期范围)
     * @return 生成的 SQL 和元数据
     */
    public GeneratedSql generateSql(String userQuery,
                                     TimeContext timeContext) {

        // 1. Schema Linking:选择相关表
        List<TableSchema> relevantSchemas =
            schemaLinker.linkTables(userQuery, 4);

        // 2. 构建 Schema 上下文
        String schemaContext = relevantSchemas.stream()
            .map(s -> schemaManager.formatSchemaForPrompt(s))
            .collect(Collectors.joining("\n"));

        // 3. 构建表关系说明
        String relationships = buildRelationships(relevantSchemas);

        // 4. 解析时间引用("上个月" → 2026-07-01 ~ 2026-07-31)
        String resolvedQuery = resolveTimeReferences(
            userQuery, timeContext);

        // 5. 生成 SQL
        String prompt = String.format("""
            你是一个达梦数据库(DM SQL)专家。请根据用户的问题生成 SQL 查询。
            
            ## 数据库 Schema
            %s
            
            ## 表间关系
            %s
            
            ## SQL 方言要求
            - 使用达梦 DM SQL 语法
            - 日期函数使用: 
              · 当前日期: CURRENT_DATE
              · 日期加减: DATEADD(day/month, -N, date)
              · 月份提取: EXTRACT(MONTH FROM date)
              · 日期格式化: TO_CHAR(date, 'YYYY-MM-DD')
            - 分页使用: FETCH FIRST N ROWS ONLY(不用 LIMIT)
            - 字符串拼接使用: ||(不用 CONCAT)
            
            ## 生成规则
            1. 只生成 SELECT 查询,禁止 INSERT/UPDATE/DELETE/DROP
            2. 必须包含合理的 WHERE 条件缩小数据范围
            3. 结果集必须有 FETCH FIRST 限制(最大 1000 行)
            4. 使用有意义的别名(AS chinese_name)
            5. 对于"本月"、"上月"等时间词,今天日期是 %s
            
            ## Few-Shot 示例
            
            问题: "A栋今天的用电量"
            SQL: SELECT area_name AS 区域, electricity_kwh AS 用电量
                 FROM energy_daily_consumption
                 WHERE area_name = 'A栋办公楼'
                   AND consume_date = CURRENT_DATE
                 FETCH FIRST 100 ROWS ONLY;
            
            问题: "本月能耗最高的5个区域"
            SQL: SELECT a.area_name AS 区域,
                        SUM(d.electricity_kwh) AS 本月总用电量
                 FROM energy_daily_consumption d
                 JOIN area_info a ON d.area_code = a.area_code
                 WHERE d.consume_date >= TO_CHAR(
                         DATEADD(month, 0, CURRENT_DATE), 'YYYY-MM') || '-01'
                 GROUP BY a.area_name
                 ORDER BY 本月总用电量 DESC
                 FETCH FIRST 5 ROWS ONLY;
            
            ## 用户问题
            %s
            
            只返回 SQL,不要返回任何解释或其他内容。
            """,
            schemaContext,
            relationships,
            timeContext.currentDate(),
            resolvedQuery);

        String sql = sqlModel.generate(
            UserMessage.from(prompt)).content().text().trim();

        // 清理可能的 markdown 代码块标记
        sql = sql.replaceAll("```
sql\\s*", "")
                  .replaceAll("```\\s*", "")
                  .trim();

        return new GeneratedSql(userQuery, sql, relevantSchemas);
    }

    /**
     * 构建表间关系描述
     * 
     * 这些信息无法从 DDL 中自动提取(达梦可能没有外键约束),
     * 需要通过配置文件维护。
     */
    private String buildRelationships(List<TableSchema> schemas) {
        // 从配置文件加载表关系
        Map<String, String> relationships = Map.of(
            "energy_daily_consumption.area_code → area_info.area_code",
                "每日能耗表通过区域编码关联区域信息表",
            "device_energy_consumption.device_id → device_info.device_id",
                "设备能耗表通过设备ID关联设备信息表",
            "energy_alert_log.area_code → area_info.area_code",
                "告警日志通过区域编码关联区域信息表",
            "maintenance_order.area_code → area_info.area_code",
                "维修工单通过区域编码关联区域信息表"
        );

        Set<String> tableNames = schemas.stream()
            .map(TableSchema::tableName)
            .collect(Collectors.toSet());

        return relationships.entrySet().stream()
            .filter(e -> {
                String key = e.getKey();
                // 只显示与当前查询相关的关系
                return tableNames.stream().anyMatch(key::contains);
            })
            .map(e -> "- " + e.getKey() + " (" + e.getValue() + ")")
            .collect(Collectors.joining("\n"));
    }
}

3.2 SQL 安全校验器

这是 Text-to-SQL 最关键的安全防线。 生成的 SQL 在执行前必须经过严格的安全检查。

java
/**
 * SQL 安全校验器
 * 
 * 多层安全防线:
 * 1. 白名单:只允许 SELECT 语句
 * 2. 黑名单:禁止所有写入/修改/删除操作
 * 3. 表权限:只允许查询指定的业务表
 * 4. 资源限制:强制添加 FETCH FIRST 限制
 * 5. 语法校验:检测常见注入模式
 */
@Component
public class SqlSecurityValidator {

    /** 允许查询的表白名单 */
    private static final Set<String> ALLOWED_TABLES = Set.of(
        "energy_daily_consumption",
        "energy_monthly_summary",
        "energy_yearly_summary",
        "area_info",
        "device_info",
        "device_energy_consumption",
        "energy_alert_log",
        "energy_alert_config",
        "maintenance_order",
        "inspection_record"
    );

    /** 禁止的 SQL 关键词(写入操作) */
    private static final Set<String> FORBIDDEN_KEYWORDS = Set.of(
        "INSERT", "UPDATE", "DELETE", "DROP", "ALTER",
        "CREATE", "TRUNCATE", "GRANT", "REVOKE",
        "MERGE", "EXECUTE", "EXEC", "CALL"
    );

    /**
     * 校验 SQL 安全性
     * 
     * @return 校验结果(通过/拒绝 + 原因)
     */
    public ValidationResult validate(String sql) {
        String upperSql = sql.toUpperCase().trim();

        // 检查1: 必须是 SELECT 语句
        if (!upperSql.startsWith("SELECT")
                && !upperSql.startsWith("WITH")) {
            return ValidationResult.rejected(
                "只允许 SELECT 查询,当前语句类型: "
                + upperSql.split("\\s+")[0]);
        }

        // 检查2: 不包含写入操作关键词
        for (String keyword : FORBIDDEN_KEYWORDS) {
            // 使用词边界匹配,避免误伤(如 "update_time" 字段名)
            if (Pattern.compile("\\b" + keyword + "\\b",
                    Pattern.CASE_INSENSITIVE)
                    .matcher(upperSql).find()) {
                // 排除字段名中包含关键词的情况
                if (!isColumnName(keyword, sql)) {
                    return ValidationResult.rejected(
                        "禁止使用 " + keyword + " 操作");
                }
            }
        }

        // 检查3: 表名必须在白名单内
        Set<String> referencedTables = extractTableNames(sql);
        for (String table : referencedTables) {
            if (!ALLOWED_TABLES.contains(table.toLowerCase())) {
                return ValidationResult.rejected(
                    "不允许查询表: " + table
                    + "(不在白名单中)");
            }
        }

        // 检查4: 必须有结果集限制
        if (!upperSql.contains("FETCH FIRST")
                && !upperSql.contains("ROWNUM")
                && !upperSql.contains("TOP ")) {
            // 自动添加限制
            sql = sql.replaceAll(";\\s*$", "")
                + "\nFETCH FIRST 1000 ROWS ONLY";
        }

        // 检查5: 禁止子查询中的写入操作
        if (containsSubqueryWrite(upperSql)) {
            return ValidationResult.rejected(
                "子查询中包含写入操作");
        }

        return ValidationResult.passed(sql);
    }

    /**
     * 从 SQL 中提取引用的表名
     * 
     * 使用正则解析 FROM/JOIN 子句中的表名。
     * 注意:这是一个简化的解析器,复杂 SQL 建议使用 SQL Parser 库。
     */
    private Set<String> extractTableNames(String sql) {
        Set<String> tables = new HashSet<>();

        // 匹配 FROM table 和 JOIN table 模式
        Pattern fromPattern = Pattern.compile(
            "(?:FROM|JOIN)\\s+([a-zA-Z_][a-zA-Z0-9_]*)",
            Pattern.CASE_INSENSITIVE);

        Matcher matcher = fromPattern.matcher(sql);
        while (matcher.find()) {
            tables.add(matcher.group(1));
        }

        return tables;
    }
}

3.3 结果执行与格式化

java
/**
 * SQL 查询执行器
 * 
 * 执行生成的 SQL 并将结果格式化为模型可理解的文本
 */
@Service
public class SqlQueryExecutor {

    private final DataSource dataSource;
    private final SqlSecurityValidator validator;

    /**
     * 执行 SQL 并返回格式化结果
     */
    public QueryResult execute(GeneratedSql generatedSql) {
        // 1. 安全校验
        ValidationResult validation = validator.validate(
            generatedSql.sql());
        if (!validation.passed()) {
            return QueryResult.error(validation.reason());
        }

        String safeSql = validation.normalizedSql();

        // 2. 执行查询(带超时保护)
        try (Connection conn = dataSource.getConnection()) {
            Statement stmt = conn.createStatement();
            stmt.setQueryTimeout(30);  // 30 秒超时

            long startTime = System.currentTimeMillis();
            ResultSet rs = stmt.executeQuery(safeSql);
            long duration = System.currentTimeMillis() - startTime;

            // 3. 解析结果集
            ResultSetMetaData meta = rs.getMetaData();
            int columnCount = meta.getColumnCount();
            List<String> columnNames = new ArrayList<>();
            for (int i = 1; i <= columnCount; i++) {
                // 优先使用别名(中文别名更友好)
                String label = meta.getColumnLabel(i);
                columnNames.add(label != null ? label
                    : meta.getColumnName(i));
            }

            List<Map<String, Object>> rows = new ArrayList<>();
            while (rs.next() && rows.size() < 1000) {
                Map<String, Object> row = new LinkedHashMap<>();
                for (int i = 1; i <= columnCount; i++) {
                    row.put(columnNames.get(i - 1), rs.getObject(i));
                }
                rows.add(row);
            }

            // 4. 格式化结果
            String formatted = formatResult(columnNames, rows);

            log.info("SQL 查询完成: {} 行, {}ms", rows.size(), duration);

            return QueryResult.success(formatted, rows.size(), duration);

        } catch (SQLTimeoutException e) {
            return QueryResult.error("查询超时(30秒),请缩小查询范围");
        } catch (SQLException e) {
            log.error("SQL 执行失败: {}", e.getMessage());
            return QueryResult.error("SQL 执行错误: " + e.getMessage());
        }
    }

    /**
     * 将查询结果格式化为 Markdown 表格
     * 
     * 为什么用 Markdown 表格?
     * 因为 LLM 对 Markdown 表格的理解和引用能力最好。
     * Agent 在后续生成回答时可以方便地引用表格中的数据。
     */
    private String formatResult(List<String> columns,
                                 List<Map<String, Object>> rows) {
        if (rows.isEmpty()) {
            return "查询结果为空(未找到匹配数据)";
        }

        StringBuilder sb = new StringBuilder();

        // 表头
        sb.append("| ").append(String.join(" | ", columns))
          .append(" |\n");
        sb.append("| ").append(columns.stream()
            .map(c -> "---")
            .collect(Collectors.joining(" | ")))
          .append(" |\n");

        // 数据行(最多显示 50 行,太多会超出上下文)
        int displayRows = Math.min(rows.size(), 50);
        for (int i = 0; i < displayRows; i++) {
            sb.append("| ");
            for (String col : columns) {
                Object value = rows.get(i).get(col);
                sb.append(value != null ? value : "NULL")
                  .append(" | ");
            }
            sb.append("\n");
        }

        if (rows.size() > displayRows) {
            sb.append(String.format(
                "\n(共 %d 行,已显示前 %d 行)\n",
                rows.size(), displayRows));
        }

        return sb.toString();
    }
}

3.4 Text-to-SQL 集成到 Agent

java
/**
 * Text-to-SQL Agent 工具
 * 
 * 将整个 Text-to-SQL Pipeline 封装为一个 @Tool,
 * Agent 可以自主决定何时使用自然语言查询数据库。
 */
@Component
public class TextToSqlTool {

    private final SqlGenerationService sqlGenerator;
    private final SqlQueryExecutor queryExecutor;
    private final TimeContextService timeContextService;

    @Tool("使用自然语言查询能源数据库。" +
          "当用户需要查询能耗数据、统计分析、排名对比等" +
          "需要数据库支持的问题时使用此工具。" +
          "输入自然语言问题,返回查询结果表格。" +
          "示例输入:'A栋上月每天用电量'、'本月能耗Top5区域'")
    public String queryDatabase(String naturalLanguageQuestion) {
        TimeContext timeContext = timeContextService.getCurrent();

        // 1. 生成 SQL
        GeneratedSql generated = sqlGenerator.generateSql(
            naturalLanguageQuestion, timeContext);

        log.info("Text-to-SQL 生成: '{}' → {}",
            naturalLanguageQuestion, generated.sql());

        // 2. 执行查询(含安全校验)
        QueryResult result = queryExecutor.execute(generated);

        if (!result.success()) {
            // SQL 生成或执行失败时,返回错误信息
            // Agent 可以根据错误信息决定重试或告知用户
            return String.format("查询失败: %s\n生成的SQL: %s",
                result.error(), generated.sql());
        }

        return String.format("""
            查询问题: %s
            生成的SQL: %s
            查询结果 (%d 行, 耗时 %dms):
            
            %s
            """,
            naturalLanguageQuestion,
            generated.sql(),
            result.rowCount(),
            result.durationMs(),
            result.formattedResult()
        );
    }
}

调用示例:

bash
# 用户: "A栋上个月每天的用电量是多少?排个序"

# Agent 内部执行:
# 1. 调用 TextToSqlTool.queryDatabase("A栋上个月每天的用电量,按日期排序")
# 2. 生成 SQL:
#    SELECT consume_date AS 日期, electricity_kwh AS 用电量
#    FROM energy_daily_consumption
#    WHERE area_name = 'A栋办公楼'
#      AND consume_date >= '2026-07-01'
#      AND consume_date < '2026-08-01'
#    ORDER BY consume_date
#    FETCH FIRST 1000 ROWS ONLY
# 3. 安全校验通过
# 4. 执行并返回 Markdown 表格
# 5. Agent 基于表格数据生成自然语言回答

四、Human-in-the-Loop:关键决策让人类把关

4.1 为什么不能完全自动化?

Agent 的自主能力越强,潜在的风险也越大。在企业环境中,以下操作 不应该 由 Agent 全自动执行:


┌──────────────────────────────────────────────────────────┐
│           需要人工确认的操作类型                           │
│                                                          │
│  高风险操作                                               │
│  · 发送告警通知给领导/客户(发出去就收不回来)             │
│  · 调整设备运行参数(可能影响生产安全)                   │
│  · 提交采购申请(涉及资金)                              │
│                                                          │
│  不可逆操作                                               │
│  · 删除历史数据                                          │
│  · 修改告警规则                                          │
│  · 归档/关闭工单                                         │
│                                                          │
│  模糊判断                                                 │
│  · Agent 对自身回答不确信时(置信度低)                   │
│  · 多个方案需要业务判断时                                │
│  · 涉及主观评价的决策                                    │
│                                                          │
└──────────────────────────────────────────────────────────┘

4.2 HITL 交互模式设计

Human-in-the-Loop 有三种主要的交互模式:


模式1: 审批门控(Approval Gate)
  Agent 准备好操作方案 → 暂停 → 等人审批 → 通过则执行 / 拒绝则跳过

  Agent: "我准备向张工发送告警通知:
          'C栋能耗8734kWh,超出均值84%'
          [确认发送] [修改内容] [取消]"

模式2: 主动求助(Escalation)
  Agent 遇到超出能力范围的问题 → 转交人类处理

  Agent: "这个问题涉及设备硬件故障判断,
          超出了我的能力范围,正在转接给运维工程师..."

模式3: 人类纠错(Correction)
  Agent 给出回答 → 人类发现问题并纠正 → Agent 学习纠正

  用户: "不对,B栋的负责人是李工不是张工"
  Agent: "明白了,已更正。B栋负责人是李工,
          我这就通知李工。"

4.3 HITL 框架实现

java
/**
 * 人工确认请求
 * 
 * 当 Agent 需要人工确认时,创建一个 PendingConfirmation
 * 并暂停当前执行流程,等待人类响应。
 */
public record PendingConfirmation(
    String confirmationId,
    String sessionId,
    String agentMessage,        // Agent 展示给用户的说明
    String operationDescription, // 即将执行的操作描述
    ConfirmationType type,       // APPROVAL / SELECTION / CORRECTION
    Map<String, Object> context, // 操作上下文数据
    Instant createdAt,
    Instant expiresAt           // 超时时间(超时则自动取消)
) {
    public enum ConfirmationType {
        APPROVAL,    // 审批:确认 / 拒绝
        SELECTION,   // 选择:从多个方案中选一个
        CORRECTION   // 纠错:用户修改 Agent 的输出
    }
}

/**
 * Human-in-the-Loop 管理器
 * 
 * 核心设计:
 * 1. Agent 执行到需要人工确认的步骤时,创建 Confirmation 请求
 * 2. 通过 WebSocket/SSE 推送给用户前端
 * 3. 用户响应后,将结果注入 Agent 的执行流
 * 4. 超时未响应:根据策略自动处理(取消/降级/通知管理员)
 */
@Service
public class HumanInTheLoopManager {

    private final ConfirmationRepository confirmRepo;
    private final SimpMessagingTemplate messaging;  // WebSocket
    private final ScheduledExecutorService scheduler;

    /**
     * 请求人工确认(Agent 端调用)
     * 
     * 这个方法会阻塞 Agent 的执行线程,直到:
     * - 用户响应(确认/拒绝/修改)
     * - 超时自动处理
     */
    public ConfirmationResponse requestConfirmation(
            String sessionId,
            String agentMessage,
            String operationDescription,
            ConfirmationType type,
            Map<String, Object> context,
            Duration timeout) {

        // 1. 创建确认请求
        PendingConfirmation confirmation = new PendingConfirmation(
            UUID.randomUUID().toString(),
            sessionId,
            agentMessage,
            operationDescription,
            type,
            context,
            Instant.now(),
            Instant.now().plus(timeout)
        );
        confirmRepo.save(confirmation);

        // 2. 推送给前端(WebSocket)
        messaging.convertAndSend(
            "/topic/confirmation/" + sessionId,
            confirmation);

        log.info("等待人工确认: id={}, type={}, timeout={}",
            confirmation.confirmationId(), type, timeout);

        // 3. 阻塞等待用户响应
        CompletableFuture<ConfirmationResponse> future =
            new CompletableFuture<>();
        pendingFutures.put(confirmation.confirmationId(), future);

        // 4. 设置超时处理
        scheduler.schedule(() -> {
            if (!future.isDone()) {
                log.warn("人工确认超时: id={}",
                    confirmation.confirmationId());
                future.complete(
                    ConfirmationResponse.timeout(
                        confirmation.confirmationId()));
                confirmRepo.updateStatus(
                    confirmation.confirmationId(), "TIMEOUT");
            }
        }, timeout.toMillis(), TimeUnit.MILLISECONDS);

        try {
            return future.get(timeout.toMillis() + 1000,
                TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            return ConfirmationResponse.timeout(
                confirmation.confirmationId());
        }
    }

    /**
     * 用户响应确认(前端回调)
     * 
     * 前端通过 REST API 提交用户的确认/拒绝/修改操作,
     * 这里唤醒等待中的 Agent 执行线程。
     */
    @Transactional
    public void submitConfirmationResponse(
            String confirmationId,
            ConfirmationResponse response) {

        confirmRepo.updateStatus(confirmationId,
            response.action().name());

        CompletableFuture<ConfirmationResponse> future =
            pendingFutures.remove(confirmationId);
        if (future != null) {
            future.complete(response);
        }
    }
}

/**
 * 用户响应
 */
public record ConfirmationResponse(
    String confirmationId,
    Action action,
    String userComment,         // 用户备注(如修改意见)
    Map<String, Object> modifiedContext  // 修改后的上下文
) {
    public enum Action {
        APPROVED,   // 批准执行
        REJECTED,   // 拒绝执行
        MODIFIED,   // 修改后执行
        TIMEOUT     // 超时
    }

    public static ConfirmationResponse timeout(String id) {
        return new ConfirmationResponse(
            id, Action.TIMEOUT, null, null);
    }
}

4.4 在 Agent 工具中集成 HITL

java
@Component
public class EnergyToolsWithHITL {

    private final HumanInTheLoopManager hitlManager;
    private final NotificationService notificationService;

    /**
     * 发送告警通知(需要人工确认)
     * 
     * 流程:
     * 1. Agent 准备好通知内容
     * 2. 暂停,向用户展示通知预览
     * 3. 用户确认 → 实际发送
     * 4. 用户修改 → 用修改后的内容发送
     * 5. 用户拒绝 → 不发送
     * 6. 超时 → 不发送(安全默认行为)
     */
    @Tool("向指定负责人发送能耗告警通知。" +
          "此操作会实际发送通知,因此需要用户确认后才能执行。")
    public String sendAlertWithApproval(String recipient,
                                         String alertLevel,
                                         String message) {
        // 获取当前会话 ID
        String sessionId = SessionContext.getCurrentSessionId();

        // 请求人工确认
        ConfirmationResponse response =
            hitlManager.requestConfirmation(
                sessionId,
                String.format("""
                    我准备发送一条%s告警通知,请确认:
                    
                    接收人:%s
                    告警级别:%s
                    通知内容:%s
                    """, alertLevel, recipient, alertLevel, message),
                "发送告警通知",
                PendingConfirmation.ConfirmationType.APPROVAL,
                Map.of("recipient", recipient,
                       "alertLevel", alertLevel,
                       "message", message),
                Duration.ofMinutes(5)  // 5 分钟超时
            );

        return switch (response.action()) {
            case APPROVED -> {
                boolean sent = notificationService.sendAlert(
                    recipient, alertLevel, message);
                yield sent ? "告警通知已发送" : "通知发送失败";
            }
            case MODIFIED -> {
                // 用户修改了通知内容
                String modifiedMsg = (String)
                    response.modifiedContext().get("message");
                boolean sent = notificationService.sendAlert(
                    recipient, alertLevel, modifiedMsg);
                yield sent
                    ? "已按修改后的内容发送告警通知"
                    : "通知发送失败";
            }
            case REJECTED -> "已取消发送告警通知";
            case TIMEOUT -> "确认超时,已取消发送(如需发送请重试)";
        };
    }
}

4.5 前端交互组件(WebSocket 推送)

java
/**
 * 确认请求的 WebSocket 推送 + REST 回调
 */
@RestController
@RequestMapping("/api/hitl")
public class HITLController {

    @Autowired
    private HumanInTheLoopManager hitlManager;

    /**
     * 用户提交确认响应
     */
    @PostMapping("/confirmations/{confirmationId}/respond")
    public ResponseEntity<String> respond(
            @PathVariable String confirmationId,
            @RequestBody ConfirmationResponse response) {
        hitlManager.submitConfirmationResponse(
            confirmationId, response);
        return ResponseEntity.ok("确认已提交");
    }
}

前端 WebSocket 监听示例(Vue/React 同理):

javascript
// 前端:监听确认请求并展示给用户
const ws = new WebSocket('ws://localhost:8080/ws');

ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    
    if (data.type === 'CONFIRMATION_REQUEST') {
        // 弹出确认对话框
        showConfirmationDialog({
            message: data.agentMessage,
            operation: data.operationDescription,
            type: data.type,  // APPROVAL / SELECTION / CORRECTION
            
            onApprove: () => {
                fetch(`/api/hitl/confirmations/${data.confirmationId}/respond`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        confirmationId: data.confirmationId,
                        action: 'APPROVED'
                    })
                });
            },
            
            onReject: () => {
                fetch(`/api/hitl/confirmations/${data.confirmationId}/respond`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        confirmationId: data.confirmationId,
                        action: 'REJECTED',
                        userComment: '不需要发送'
                    })
                });
            },
            
            onModify: (modifiedContent) => {
                fetch(`/api/hitl/confirmations/${data.confirmationId}/respond`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        confirmationId: data.confirmationId,
                        action: 'MODIFIED',
                        modifiedContext: { message: modifiedContent }
                    })
                });
            }
        });
    }
};

4.6 超时策略设计

java
/**
 * 超时策略配置
 * 
 * 不同操作类型的超时处理策略不同:
 * 
 * 通知类操作:超时 → 取消(安全默认)
 * 数据查询类:超时 → 自动执行(查询无风险)
 * 定时任务类:超时 → 通知管理员代为决策
 */
@Component
public class TimeoutPolicyConfig {

    public TimeoutAction getPolicy(String operationType) {
        return switch (operationType) {
            // 高风险操作:超时取消
            case "send_alert", "adjust_parameter",
                 "submit_purchase_order" ->
                TimeoutAction.CANCEL;

            // 只读操作:超时自动执行
            case "query_data", "generate_preview" ->
                TimeoutAction.AUTO_EXECUTE;

            // 定时任务:超时通知管理员
            case "scheduled_report", "batch_operation" ->
                TimeoutAction.ESCALATE_TO_ADMIN;

            default -> TimeoutAction.CANCEL;  // 默认取消
        };
    }
}

五、Text-to-SQL + HITL 的完整协作流程


用户: "帮我分析A栋上个月用电情况,如果偏高就通知张工"

┌─── Agent 推理链 ──────────────────────────────────────────────┐
│                                                               │
│  Step 1: 调用 TextToSqlTool                                   │
│  → "A栋上月每天用电量" → SQL → 查询 → 返回31行数据            │
│                                                               │
│  Step 2: LLM 分析数据                                         │
│  → "A栋7月日均用电1523kWh,较6月增长18%,7月15日峰值2100kWh"  │
│                                                               │
│  Step 3: 判断是否偏高                                         │
│  → "18%增长超过10%阈值,判定为偏高"                           │
│                                                               │
│  Step 4: 准备告警通知                                         │
│  → "张工您好,A栋7月日均用电1523kWh,较6月增长18%,           │
│     7月15日出现峰值2100kWh,建议排查空调系统"                  │
│                                                               │
│  Step 5: [HITL] 请求用户确认 ← 暂停执行                      │
│  → 推送确认请求到前端                                         │
│  → 用户看到: "准备发送通知给张工,内容如下...                  │
│               [确认发送] [修改内容] [取消]"                    │
│                                                               │
│  Step 6: 用户点击 [确认发送]                                   │
│  → Agent 继续执行 → 通知发送成功                              │
│                                                               │
│  Step 7: 最终回复                                             │
│  → "A栋7月用电分析完成,能耗偏高18%%,已通知张工排查。"       │
│                                                               │
└───────────────────────────────────────────────────────────────┘

六、达梦数据库的 SQL 方言适配要点

由于项目使用达梦数据库,这里特别总结一些 Text-to-SQL 中需要注意的语法差异:

java
/**
 * 达梦 SQL 方言注意事项
 * 
 * 在 Prompt 中必须明确告知模型以下差异点,
 * 否则模型默认会生成 MySQL 语法。
 */
public class DmSqlDialectNotes {

    public static final String DIALECT_NOTES = """
        ## 达梦数据库(DM)与 MySQL 的关键差异
        
        1. 分页:
           - MySQL: LIMIT 10 OFFSET 20
           - DM:    FETCH FIRST 10 ROWS ONLY
                    或 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
        
        2. 日期函数:
           - 当前时间: CURRENT_DATE / CURRENT_TIMESTAMP
           - 日期加减: DATEADD(day, -7, CURRENT_DATE)
           - 月份差:   DATEDIFF(month, date1, date2)
           - 格式化:   TO_CHAR(date, 'YYYY-MM-DD')
           - 字符串转日期: TO_DATE('2026-08-01', 'YYYY-MM-DD')
        
        3. 字符串:
           - 拼接:  str1 || str2(不是 CONCAT)
           - 子串:  SUBSTR(str, start, length)
           - 长度:  LENGTH(str)
        
        4. 条件表达式:
           - IF:    CASE WHEN ... THEN ... ELSE ... END
           - DM 不支持 IF() 函数
        
        5. 分组:
           - GROUP BY 中的列必须在 SELECT 中出现
           - 或使用分析函数: ROW_NUMBER() OVER(PARTITION BY ... ORDER BY ...)
        
        6. 空值处理:
           - NVL(column, default)(类似 MySQL 的 IFNULL)
        """;
}

七、九篇文章完整知识图谱


┌────────────────────────────────────────────────────────────────────┐
│                Java AI Agent 实战系列 · 完整路线图                  │
│                                                                    │
│  第一篇 ── 单体 Agent 构建                                        │
│  │        Tool Calling + RAG + Memory                             │
│  │                                                                │
│  第二篇 ── Multi-Agent 协作 + MCP 协议                            │
│  │        LangGraph4j 编排 + MCP 工具标准化                       │
│  │                                                                │
│  第三篇 ── 评估体系 + A2A 协议                                    │
│  │        四维评估 + LLM-as-Judge + 跨组织通信                    │
│  │                                                                │
│  第四篇 ── 长期记忆 + 用户画像                                    │
│  │        三层记忆架构 + 画像自动提取                            │
│  │                                                                │
│  第五篇 ── 可观测性 + 成本优化                                    │
│  │        链路追踪 + 会话回放 + Token 精算                        │
│  │                                                                │
│  第六篇 ── RAG 深度优化                                          │
│  │        语义切分 + 混合检索 + 重排序                            │
│  │                                                                │
│  第七篇 ── Agent 安全攻防                                        │
│  │        Prompt 注入防御 + 工具权限沙箱 + 幻觉检测              │
│  │                                                                │
│  第八篇 ── 国产大模型 + 高级推理模式                              │
│  │        四大模型实战 + Plan-Execute + Reflection + S-C           │
│  │                                                                │
│  第九篇 ── Text-to-SQL + Human-in-the-Loop (本篇)                 │
│           自然语言查数据库 + 关键操作人工确认                      │
│                                                                    │
│  能力进阶路线:                                                    │
│  能构建 → 能协作 → 能评估 → 能记忆 → 能运维 → 能检索            │
│  → 能防御 → 能落地 → 能查数据 + 能人机协作                       │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘
企业级Agentic AI实战:从智能体概念到多智能体系统构建 最近和不少技术负责人、架构师交流,发现大家聊到 AI 落地,话题已经从“要不要用大模型”转向了“如何构建能自主行动的智能体(Agent)”。很多企业都在内部立项探索 Agentic AI,但具体要做什么、怎么做、会遇到哪些坑,似乎还没有一个清晰的路线图。本文将结合行业实践和主流框架,为你系统拆解企业级 Agentic AI 项目的核心工作、技术选型与实施路径,无论你是想评估技术可行性,还是已经着手搭建,都能从中找到参考。 1. 什么是 Agentic AI?从概念到价值澄清 在深入技术细节之前,我们必须先统 阅读详情

相关推荐

从零掌握AI智能体技能开发:Codex技能系统实战指南

在AI智能体开发中,大型语言模型(LLM)作为核心推理引擎,需要通过模块化扩展来执行具体任务。其原理在于通过工具调用机制,将自然语言指令转化为可执行的操作,从而突破纯对话的限制。这一技术的核心价值在于将AI的认知能力与实际行动相结合,实现从“思考”到“执行”的闭环。在应用场景上,它广泛适用于自动化数据处理、API集成、跨系统工作流串联等任务。本文聚焦于Codex智能体的技能(Skills)系统,详细解析如何通过LangChain等框架,为智能体开发如天气查询、数学计算等自定义工具,并构建可复用的自动化流程,

cucanqi9387的博客 387

宝塔面板内网离线部署:断网环境完整安装实操

一台完全无法联网的内网服务器,跑 [宝塔面板](https://link.gitcode.com/i/df5eb246c81304424c0e5fe8f4632d80) 官方安装脚本会在两处卡死:先是 "Selected download node..." 逐个探测远程节点,断网环境里这步会拖很久甚至直接超时;后面所有 `wget -O` 远程拉取也必然失败退出。本文的离线安装方案只做一件事——把

gitblog_00795的博客 729

Grok 4.6 长时运行智能体开发实战:解决AI失忆与状态持久化难题

AI智能体作为能够感知环境、自主决策并执行行动的软件实体,其核心原理在于结合规划、工具使用、记忆与执行等组件,模拟人类的目标导向行为。这一技术价值在于将大语言模型的推理能力转化为可完成复杂、多步骤任务的自动化系统,广泛应用于自动化业务流程、数据分析、跨系统集成等场景。然而,智能体在长时运行任务中常面临上下文遗忘、状态丢失与目标漂移等工程挑战。本文以xAI最新发布的Grok 4.6版本为切入点,深入探讨如何通过**分层记忆管理**与**任务状态检查点**等关键技术,构建具备持续性与稳定性的可靠智能体系统,为开

weixin_30565327的博客 395

2026 流行 AI 数据框架深度研究报告:LlamaIndex 架构演进、核心机制与企业级智能体生态剖析

报告深入剖析了 LlamaIndex 从早期的“RAG 优先”索引库向生产级、事件驱动型智能体工作流(Agentic Workflows)框架的全面范式转移。其核心变革在于摒弃了传统链式有向无环图(DAG)的刚性局限,全面引入基于发布-订阅模式的事件驱动架构(Workflows API),使系统能够天然支持反思、重试与自纠错等复杂自治智能体逻辑,并实现控制流与全局状态管理的解耦。

greenspan的专栏 875

LangChain+LlamaIndex+AutoGen+LangGraph框架对比

大模型框架对比

usa_washington的博客 1601

LangChain Agent与ReAct实战:构建可调试、可审计的智能体系统

智能体(Agent)是大模型从被动响应走向目标驱动决策的关键范式,其核心在于将推理(Reasoning)与行动(Acting)闭环耦合。ReAct方法通过Thought-Action-Observation-Answer四步循环,为LLM赋予可观察、可干预、可审计的执行能力,有效缓解幻觉、工具误调与黑盒决策等工程痛点。它不依赖更强的模型,而是通过结构化提示、强约束Tool设计和可观测执行流,在真实业务场景中实现高鲁棒性与低维护成本。本文聚焦LangChain Agent落地中的ReAct架构设计、效率瓶颈识

dianan0938的博客 374

智能体项目实战:持久化、HITL、流式、MCP与安全五大核心模块演进

在构建现代智能体系统时,理解其核心架构模块是工程落地的关键。从基础概念出发,智能体需要具备记忆与状态管理能力,这通常通过数据库持久化与缓存策略实现,以确保上下文连贯与高效访问。其工作原理涉及数据分层存储与智能上下文管理,以平衡性能与成本。在技术价值层面,引入人在回路机制为高风险决策提供了必要的人工监督安全阀,而流式输出技术则显著提升了交互体验的实时感。进一步地,采用模型上下文协议能标准化工具集成,极大扩展智能体能力边界。这些技术共同服务于智能体在客服、数据分析、自动化流程等复杂业务场景中的稳定应用。本文聚焦

weixin_30216561的博客 410

AI Agent工程师实战路线图:从Function Calling到生产级RAG与多Agent编排

AI Agent是构建智能业务系统的核心范式,其本质是LLM驱动的可编程决策流,依赖Function Calling实现工具协同、RAG增强知识可信度、多Agent架构分解复杂任务,并通过状态管理与可观测性保障生产稳定性。当前主流技术栈中,LangChain提供函数调用基础能力,LlamaIndex+Chroma降低RAG冷启动门槛,CrewAI封装协作协议,LangGraph解决状态持久化与人工干预,Spring AI则填补Java生态的事务一致性空白。本文聚焦真实工程落地中的Schema校验、向量检索优

weixin_33957648的博客 428

AI智能体规划执行新范式:原子任务图框架的设计与实战

在软件工程与AI智能体领域,有向无环图(DAG)是一种用于编排任务依赖关系的经典模型,它通过节点和边清晰地定义了工作流的执行顺序与数据流向。其核心原理是将复杂流程分解为独立的单元,并通过拓扑排序确保无环依赖下的正确调度。这一技术价值在于为动态、多变的计算过程提供了结构化的表达与控制能力,尤其适用于自动化运维、数据分析流水线等应用场景。本文聚焦于将DAG思想与AI智能体(Agent)的规划-执行循环深度融合,提出了“原子任务图”这一统一框架。该框架旨在解决传统智能体架构中规划与执行脱节的痛点,通过将目标拆解为

weixin_34007020的博客 321

AI Agent开发实战:从Prompt、RAG到LangGraph与Agent Skills的完整落地路线

AI Agent并非简单的聊天机器人,而是一套集感知、决策、行动于一体的循环系统。在大模型应用开发中,开发者常面临概念繁多、落地路径不清的困境。构建一个生产级Agent,需要从最基础的Prompt Engineering出发,掌握System Prompt结构化设计、约束边界与异常兜底策略;在此基础上引入RAG(检索增强生成)补齐私域知识短板,并借助Dense Vector Search、混合召回与Agentic RAG实现按需多轮检索;继而通过LangGraph的StateGraph机制编排有状态、可循环

weixin_34150830的博客 560

LangChain接入MCP协议:AI Agent标准化连接外部系统的技术跃升

在AI Agent开发中,如何让大模型连接外部系统一直是个核心挑战。传统方法需要为每个数据源和工具编写大量胶水代码,导致开发效率低下、维护困难。Model Context Protocol(MCP)通过定义标准化的Tools、Resources和Prompts接口,实现了AI Agent与外部系统的解耦连接。这一协议让Agent能够动态发现和调用工具,主动获取外部系统结构信息,从根本上改变了Agent获取和利用外部世界信息的方式。结合LangChain框架,开发者可以从繁琐的工具开发转向智能工作流编排,将C

cmff98425的博客 495

从工具到伙伴:Agentic Software的范式革命与架构实践

在人工智能技术快速发展的背景下,大语言模型(LLM)正推动软件架构从传统的确定性逻辑向涌现性智能演进。这一转变的核心在于软件能够理解自然语言意图、进行自主规划与决策,从而从被动工具升级为主动执行者。其技术价值在于通过模拟人类认知过程,实现复杂任务的自动化分解与执行,显著提升人机交互的效率和自然度。应用场景广泛覆盖智能数据分析、自动化工作流编排及个性化教育辅导等领域。本文聚焦于Agentic Software的架构设计,深入探讨了以LLM为核心的认知与规划层、工具执行层、记忆管理层及安全管控层的构建方法,并结

weixin_33946605的博客 418

Cadence Allegro 17.4学习记录开始02-原理图Capture CIS 17.4

Allegro的第一方网表与第三方网表有以下几个区别点;Ø 与Allegro实现交互式操作的是第一方网表,第三方网表时不可以实现交互式操作;Ø 第三方网表不能将器件的Value属性导入到PCB中,输出时以封装属性来代替Value属性,第一方网表是可以的;Ø 网表导入到PCB中时,第三方的网表需要指定事先指定好PCB封装库文件,并产生Device文件,才可以将网表导入到PCB中,第一方网表则可以直接导入。

qq_31444421的博客 1万+

基于LangChain构建安全可控AI Agent:Guardrail与HITL实战指南

在构建AI Agent时,如何在自动化与安全可控之间取得平衡是核心挑战。AI Agent作为能够自主规划、调用工具并执行任务的智能系统,其核心原理在于结合大型语言模型(LLM)的推理能力与外部工具的操作能力。然而,纯自动化的Agent在实际应用中可能因行为不可预测或执行高风险操作而引发信任问题,这限制了其技术价值。为解决此问题,工程实践中引入了Guardrail(安全护栏)与HITL(人在回路)两种关键技术。Guardrail通过在代码层面设定硬性规则(如输入验证、输出解析)来定义Agent的行为边界,防范

weixin_34239592的博客 364

CST-Python(Pycharm如何与CST联合仿真,获取S参数)

我们想使用Python与CST联合仿真,其实是不需要自己下载Python的,因为CST在下载的时候就自带了Python3.6,我们只需要找到并将软件的环境设置到相应位置,即可联合仿真。(我的保存路径为D:\Program Files (x86)\CST Studio Suite 2020,在寻找时可以将前面切换成自己的保存路径,后面不需要修改)在CST帮助文档(打开CST软件,按F11即可直接访问帮助文档)里面,进行如下操作,便可以看到CST适配的Python版本为Python3.6!

weixin_52696285的博客 5648

手把手教你:在openEuler 22.03上离线搞定OpenSSH 9.8升级(CentOS 7.9通用)

本文详细介绍了在openEuler 22.03和CentOS 7.9系统上离线升级OpenSSH 9.8的完整流程,包括准备工作、离线包获取、安装步骤、服务管理及回滚方案。特别针对无外网环境的企业服务器,提供安全可靠的离线升级解决方案,帮助运维人员有效提升系统安全性。

weixin_30682415的博客 192

企业级Agent平台落地指南:多智能体编排与知识治理实践

智能体(Agent)正从单点工具走向组织级生产力,但跨部门协作与私有知识利用成为落地瓶颈。多智能体编排通过任务拆解、角色分配与人在环审批,实现复杂流程的自动化协同;RAG技术则让智能体基于企业知识库检索增强生成,确保回答有据可依。这些能力叠加统一权限管控与审计追溯,构成了企业级Agent平台的底座价值。在客服问答、研发知识检索、数据分析等场景中,平台可将智能体转化为团队效率引擎。腾讯云WorkBuddy Enterprise正是这样一套从协同到管控的完整基础设施,为评估和选型企业级智能体平台提供了实践参考。

weixin_33768481的博客 383

PCL 计算点云AABB包围盒的体积【2026最新版】

AABB包围盒又称了 轴对齐包围盒,是点云包围盒里最简单的一种,其计算方法也极其简单。获取包围盒之后,根据包围盒的长宽高进行体积计算即可!博客长期更新,本文最近一次更新时间为:2026年5月3日。

点云侠的博客 2295

基于LangGraph实现自进化AI智能体的学习闭环架构设计

在人工智能领域,智能体(Agent)作为能够感知环境并自主决策的软件实体,其核心价值在于从静态规则执行向动态自我优化的范式演进。这一演进的技术原理在于构建一个包含规划、执行、评估、优化的结构化学习闭环,使智能体能够基于环境反馈持续改进策略。从工程实践角度看,实现这一闭环需要强大的工作流编排与状态管理能力,这正是LangGraph这类框架的技术价值所在——它通过图计算模型将抽象的认知过程转化为可观测、可调试的模块化组件。在实际应用场景中,这种架构可广泛应用于自动化客服、代码审查、决策支持等复杂任务自动化领域。

weixin_34204722的博客 259

Cesium功能集(十二):Cesium中流体仿真

【代码】Cesium功能集(十二):Cesium中流体仿真。

GIS肆月 600

BlockUI专栏目录

BlockUI是一个设计NX对话框的工具,是官方推荐使用的对话框制作方法,能够与NX自身风格相统一,并且在实际开发中经常被使用。BlockUI可以帮助用户快速创建NX对话框,支持多种控件类型,如按钮、文本框、标签等。通过使用BlockUI,用户可以轻松地自定义对话框的布局、样式和交互行为。此外,BlockUI还提供了一些高级功能,如动态更新、条件显示和事件处理等,以帮助用户创建更加复杂和灵活的对话框。本专栏讲述了使用BlockUI制作NX对话框的方法,介绍了每个控件的功能,并且分享一些常用操作的源码。

王牌飞行员_里海的博客 1630

2026大模型Agent面试题8题精解:从KV Cache到RAG优化,含数学推导与工程Trade-off

> **定位:** 面向大模型算法工程师/Agent开发工程师,难度偏高,适合区分"用过"和"真正理解"的候选人。 > **覆盖维度:** 推理优化、模型微调、Agent架构、RAG、记忆机制、Prompt工程、评估体系、多模态。

微学AI的博客 1662

54、生物神经网络建模全解析

本文系统解析了生物神经网络的建模方法,涵盖从单个神经元到神经网络的多个时空尺度。首先介绍神经元层面的生物物理模型(如霍奇金-赫胥黎模型和多隔室模型)与多种简化模型(如菲茨休-纳古莫、莫里斯-勒卡尔、积分发放模型等),并分析其适用场景。随后探讨突触层面的信号传递机制,对比电突触与化学突触在结构、传输速度和方向上的差异,阐述其在神经网络建模中的理论意义。文章旨在为理解神经系统的信息处理机制、构建受生物启发的人工智能模型提供理论基础。

app77的博客 76

LangChain实战指南:从RAG到Agent,构建企业级大模型应用

在人工智能应用开发领域,围绕大语言模型构建复杂业务系统已成为必然趋势。开发者要解决的核心问题包括:如何让模型获取私有知识、如何自主调用外部工具,以及如何设计可控的多步骤推理流程。检索增强生成(RAG)通过向量检索与生成结合,解决了模型知识时效性和私有化需求;Agent技术以ReAct范式驱动模型进行“思考-行动-观察”循环,实现任务规划与工具调用;而模型上下文协议MCP则统一了Agent与外部工具的数据交互标准。这些技术共同构成了现代大模型应用开发的基础设施,并逐步融入企业知识库问答、智能客服、自动化办公等

weixin_33847182的博客 304

【本地部署实战】LM Studio深度使用指南:从环境搭建到模型调优

LM Studio通过极简的操作流程和强大的生态兼容性,让本地部署大模型成为可能。随着M4芯片的发布和CUDA 12.0的优化,未来本地化AI开发将迎来新的爆发点。建议开发者关注官方Discord社区(https://discord.gg/lmstudio)获取最新模型支持信息。

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 8749

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

视频隐私脱敏工程化:CSRT/KCF追踪算法在本地推理中的实现与边界

在短视频内容合规审查中,动态遮挡(Dynamic Masking)是高频且高耗时的环节。传统手动关键帧打码不仅效率低下,且在目标快速移动或遮挡时极易出现遮罩漂移。本文从计算机视觉工程角度,解析基于CSRT与KCF算法的目标追踪方案在视频隐私处理中的技术路径、性能权衡及落地实践。

Mysssone的博客 194

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

上一篇: 八:国产大模型 Agent 实战与高级推理模式
下一篇: 十:Agent 数据分析可视化与自动化 Workflow 编排
yychen_java
博客等级 码龄8年 1451粉丝 77原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值