AI交易,怎么让LLM自己挑选数据源?

最近Noah在研究AI交易。碰到一个挺简单的需求:用户问什么,系统需要去对应的地方查数据,然后回答。

比如用户问当前持仓情况,需要调用交易所API查数据,然后通过LLM进行整理后输出成容易理解的回答。再比如定时任务让LLM根据行情对交易进行决策,需要调用预言机API拿到最新价格数据和资讯API拿到最新的非结构化相关数据。

过去的思路,可能是会写很多if else。然后匹配关键词,通过关键词的关联关系去调用相关数据源。

但是当业务越来越复杂时,就行不通了。因为可能有多种完全不同的需求,会碰到类似的关键词。

这个事的本质问题是:在拿到用户问题或者某项任务后,LLM要自己决定应该使用哪些数据或者使用哪个工具。

这件事在业界已经有非常成熟的方案了。

Tool/Function Calling 是什么

这两个东西虽然名字不同,但本质上都是一回事。目前主流的大模型比如ChatGPT、Claude、Gemini都支持Function Calling。

原理是这样:你把数据源封装成一个个工具或函数,给每个函数取名字、描述,定义好参数和返回格式;然后把这些定义告诉LLM;LLM在回答问题时,自己来决定要不要调用它们。

简单理解就是你把系统的数据/功能列成列表,LLM负责按照需求访问数据或执行功能,你的后端系统负责提供数据或功能。

不过需要注意一点,LLM并不能精准地保证执行是绝对正确的,它只是建议我们应该去调用什么,实际执行是由我们的后端代码完成。

以下是一个可以完整运行的例子:

typescript 复制代码
import Anthropic from '@anthropic-ai/sdk';

// 记得在环境里配置:ANTHROPIC_API_KEY
const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY!,
});

// 为了方便写类型
type MessageParam = Anthropic.Messages.MessageParam;
type Tool = Anthropic.Messages.Tool;

// ========== 第一步:定义你有哪些"工具"(相当于原来的 functions) ==========

const tools: Tool[] = [
  {
    name: 'get_market_data',
    description: `查询加密货币实时行情和历史价格。
      用户问"BTC多少钱"、"ETH今天涨了多少"、"SOL这周走势怎么样"这类用这个。
      能查到:当前价格、24h涨跌幅、成交量、K线数据等。
      注意:这个只查价格行情,链上数据和账户信息用别的工具。`,
    input_schema: {
      type: 'object',
      properties: {
        symbol: {
          type: 'string',
          description: '交易对,比如 BTC、ETH、SOL',
        },
        data_type: {
          type: 'string',
          enum: ['realtime', 'kline', 'depth'],
          description: '数据类型:实时行情/K线/深度',
        },
        interval: {
          type: 'string',
          enum: ['1h', '4h', '1d', '1w'],
          description: 'K线周期,查K线时要传',
        },
      },
      required: ['symbol', 'data_type'],
    },
  },
  {
    name: 'query_portfolio',
    description: `查询用户的持仓和账户信息。
      用户问"我现在仓位怎么样"、"我的BTC成本价多少"、"账户余额还有多少"这类用这个。
      能查到:各币种持仓数量、成本价、盈亏情况、可用余额等。
      注意:这是用户私人数据,查大盘行情别用这个。`,
    input_schema: {
      type: 'object',
      properties: {
        query_type: {
          type: 'string',
          enum: ['positions', 'balance', 'pnl', 'history'],
          description: '查什么:持仓/余额/盈亏/交易记录',
        },
        symbol: {
          type: 'string',
          description: '具体币种,不传就查全部',
        },
        time_range: {
          type: 'string',
          enum: ['today', 'week', 'month', 'all'],
          description: '时间范围,查交易记录时用',
        },
      },
      required: ['query_type'],
    },
  },
  {
    name: 'get_onchain_data',
    description: `查询链上数据和巨鲸动向。
      用户问"ETH巨鲸最近在买还是卖"、"交易所净流入多少"、"链上活跃地址数"这类用这个。
      能查到:巨鲸转账、交易所流入流出、活跃地址、Gas费、DeFi TVL等。
      适合分析市场情绪和资金动向。`,
    input_schema: {
      type: 'object',
      properties: {
        chain: {
          type: 'string',
          enum: ['ethereum', 'solana', 'bitcoin', 'base'],
          description: '哪条链',
        },
        metric: {
          type: 'string',
          enum: [
            'whale_moves',
            'exchange_flow',
            'active_addresses',
            'gas',
            'defi_tvl',
          ],
          description: '查什么指标',
        },
        time_range: {
          type: 'string',
          enum: ['24h', '7d', '30d'],
          description: '时间范围',
        },
      },
      required: ['chain', 'metric'],
    },
  },
  {
    name: 'search_crypto_news',
    description: `搜索加密货币相关新闻和分析。
      用户问"最近有什么利好"、"SEC那边有什么消息"、"为什么今天大跌"这类用这个。
      能查到:新闻快讯、监管动态、项目公告、KOL观点等。
      适合了解消息面和市场情绪。`,
    input_schema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: '搜什么,比如"ETH ETF"、"Solana生态"',
        },
        news_type: {
          type: 'string',
          enum: ['breaking', 'analysis', 'regulatory', 'project'],
          description: '新闻类型',
        },
        time_range: {
          type: 'string',
          enum: ['today', 'week', 'month'],
          description: '时间范围',
        },
      },
      required: ['query'],
    },
  },
];

// ========== 第二步:实现这些工具的具体逻辑(用模拟数据) ==========

const toolImplementations: Record<string, (args: any) => Promise<any>> = {
  async get_market_data({
    symbol,
    data_type,
    interval,
  }: {
    symbol: string;
    data_type: 'realtime' | 'kline' | 'depth';
    interval?: '1h' | '4h' | '1d' | '1w';
  }) {
    console.log(`[Market] 查询 ${symbol} 的 ${data_type}`);

    if (data_type === 'realtime') {
      return {
        symbol,
        price: 67234.5,
        change_24h: '+2.35%',
        volume_24h: '28.5B',
        high_24h: 68100,
        low_24h: 65800,
        updated_at: new Date().toISOString(),
      };
    }

    if (data_type === 'kline') {
      return {
        symbol,
        interval,
        data: [
          {
            time: '2024-03-10',
            open: 65000,
            high: 67500,
            low: 64800,
            close: 67200,
          },
          {
            time: '2024-03-11',
            open: 67200,
            high: 68100,
            low: 66500,
            close: 67800,
          },
          {
            time: '2024-03-12',
            open: 67800,
            high: 69000,
            low: 67000,
            close: 67234,
          },
        ],
        trend: 'bullish',
        support: 65000,
        resistance: 69000,
      };
    }

    if (data_type === 'depth') {
      // 简单模拟一份深度
      return {
        symbol,
        bids: [
          { price: 67200, size: 0.5 },
          { price: 67150, size: 1.2 },
        ],
        asks: [
          { price: 67300, size: 0.4 },
          { price: 67400, size: 1.0 },
        ],
        ts: new Date().toISOString(),
      };
    }

    return { error: 'unknown data_type' };
  },

  async query_portfolio({
    query_type,
    symbol,
    time_range,
  }: {
    query_type: 'positions' | 'balance' | 'pnl' | 'history';
    symbol?: string;
    time_range?: 'today' | 'week' | 'month' | 'all';
  }) {
    console.log(`[Portfolio] 查询 ${query_type},币种: ${symbol || '全部'}`);

    if (query_type === 'positions') {
      return {
        total_value_usd: 125680,
        positions: [
          {
            symbol: 'BTC',
            amount: 1.5,
            avg_cost: 62000,
            current_price: 67234,
            pnl: '+8.44%',
          },
          {
            symbol: 'ETH',
            amount: 10,
            avg_cost: 3200,
            current_price: 3456,
            pnl: '+8.00%',
          },
          {
            symbol: 'SOL',
            amount: 100,
            avg_cost: 95,
            current_price: 142,
            pnl: '+49.47%',
          },
        ],
      };
    }

    if (query_type === 'pnl') {
      return {
        today: '+$2,350 (+1.9%)',
        week: '+$8,900 (+7.6%)',
        month: '+$15,600 (+14.2%)',
        total: '+$35,680 (+39.6%)',
      };
    }

    if (query_type === 'balance') {
      return {
        total_equity_usd: 130000,
        available_usd: 20000,
        margin_used_usd: 110000,
      };
    }

    if (query_type === 'history') {
      return {
        time_range: time_range ?? 'month',
        trades: [
          {
            time: '2024-03-11 13:20',
            symbol: 'BTC',
            side: 'buy',
            price: 65000,
            size: 0.5,
            fee: 10,
          },
          {
            time: '2024-03-12 09:45',
            symbol: 'ETH',
            side: 'sell',
            price: 3400,
            size: 2,
            fee: 5,
          },
        ],
      };
    }

    return { error: 'unknown query_type' };
  },

  async get_onchain_data({
    chain,
    metric,
    time_range,
  }: {
    chain: 'ethereum' | 'solana' | 'bitcoin' | 'base';
    metric:
      | 'whale_moves'
      | 'exchange_flow'
      | 'active_addresses'
      | 'gas'
      | 'defi_tvl';
    time_range: '24h' | '7d' | '30d';
  }) {
    console.log(`[OnChain] 查询 ${chain} 的 ${metric}`);

    if (metric === 'whale_moves') {
      return {
        chain,
        time_range,
        summary: '过去24小时巨鲸净买入',
        large_txs: [
          {
            type: 'buy',
            amount: '500 BTC',
            value: '$33.5M',
            from: 'Binance',
          },
          {
            type: 'buy',
            amount: '2000 ETH',
            value: '$6.9M',
            from: 'Unknown Wallet',
          },
          {
            type: 'sell',
            amount: '100 BTC',
            value: '$6.7M',
            to: 'Coinbase',
          },
        ],
        net_flow: '+$33.7M',
        sentiment: 'accumulation',
      };
    }

    if (metric === 'exchange_flow') {
      return {
        chain,
        time_range,
        inflow: '$245M',
        outflow: '$312M',
        net_flow: '-$67M (净流出)',
        interpretation: '资金从交易所流出,通常是看涨信号',
      };
    }

    if (metric === 'active_addresses') {
      return {
        chain,
        time_range,
        active_addresses: 987654,
        change_pct: '+12.3%',
      };
    }

    if (metric === 'gas') {
      return {
        chain,
        time_range,
        avg_gwei: 28,
        max_gwei: 80,
        min_gwei: 5,
        comment: '当前 Gas 处于中等水平,适合正常链上操作',
      };
    }

    if (metric === 'defi_tvl') {
      return {
        chain,
        time_range,
        tvl_usd: '$21.3B',
        change_pct: '+4.5%',
      };
    }

    return { error: 'unknown metric' };
  },

  async search_crypto_news({
    query,
    news_type,
    time_range,
  }: {
    query: string;
    news_type?: 'breaking' | 'analysis' | 'regulatory' | 'project';
    time_range?: 'today' | 'week' | 'month';
  }) {
    console.log(`[News] 搜索: ${query}`);

    return {
      query,
      time_range: time_range ?? 'week',
      news_type: news_type ?? 'analysis',
      results: [
        {
          title: 'BlackRock ETF 单日流入创新高',
          source: 'The Block',
          time: '2小时前',
          summary: 'IBIT 单日净流入超过 $500M,创上市以来新高...',
          sentiment: 'bullish',
        },
        {
          title: 'SEC 主席暗示可能批准更多加密ETF',
          source: 'CoinDesk',
          time: '5小时前',
          summary: 'Gary Gensler 在听证会上表示正在评估多个ETF申请...',
          sentiment: 'bullish',
        },
      ],
      overall_sentiment: 'positive',
    };
  },
};

// ========== 第三步:核心逻辑,处理用户消息(Anthropic tool_use 循环) ==========

const SYSTEM_PROMPT = `你是一个专业的加密货币交易助手。
根据用户问题选择合适的工具查询数据,然后给出分析和建议。
回答要简洁专业,可以用一些币圈术语,但核心观点要清晰。
涉及投资建议时要提醒风险,强调"本回答不构成投资建议,市场有风险,入市需谨慎"。`;

async function chat(userMessage: string): Promise<string> {
  const messages: MessageParam[] = [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: userMessage,
        },
      ],
    },
  ];

  while (true) {
    const response = await client.messages.create({
      model: 'claude-sonnet-4-5',
      max_tokens: 2048,
      system: SYSTEM_PROMPT,
      tools,
      messages,
    });

    // 把这轮的 assistant 内容(可能含 tool_use)加入对话历史
    messages.push({
      role: 'assistant',
      content: response.content,
    });

    if (response.stop_reason === 'tool_use') {
      // 提取所有 tool_use block
      const toolUses = response.content.filter(
        (b) => b.type === 'tool_use',
      ) as Anthropic.Messages.ToolUseBlock[];

      const toolResultBlocks: Anthropic.Messages.ToolResultBlockParam[] = [];

      for (const toolUse of toolUses) {
        const { id, name, input } = toolUse;
        console.log(`\n>>> 调用工具: ${name}`);
        console.log('    参数:', input);

        const impl = toolImplementations[name];
        if (!impl) {
          console.warn(`未找到工具实现: ${name}`);
          toolResultBlocks.push({
            type: 'tool_result',
            tool_use_id: id,
            content: `工具 ${name} 未实现`,
            is_error: true,
          });
          continue;
        }

        let result: any;
        try {
          result = await impl(input);
        } catch (err: any) {
          console.error('工具执行异常:', err);
          result = { error: String(err?.message ?? err) };
        }

        console.log(
          '    结果:',
          JSON.stringify(result).slice(0, 120) + '...',
        );

        toolResultBlocks.push({
          type: 'tool_result',
          tool_use_id: id,
          content: JSON.stringify(result),
        });
      }

      // tool_result 要作为下一条 user 消息发回给 Claude
      messages.push({
        role: 'user',
        content: toolResultBlocks,
      });

      // 继续下一轮循环,让 Claude 基于工具结果再想想(要不要再调工具/给最终回答)
      continue;
    }

    // 没有新的 tool_use 了,拿到最终文本回复
    const text = response.content
      .filter((b) => b.type === 'text')
      .map((b) => (b as Anthropic.Messages.TextBlock).text)
      .join('\n');

    return text || '[没有生成文本回复]';
  }
}

// ========== 测试一下 ==========

async function main() {
  const questions = [
    '大饼现在多少钱',
    '我的仓位现在盈亏怎么样',
    'ETH 巨鲸最近在干嘛',
    '最近有什么利好消息',
    'SOL 能不能继续加仓(从行情、链上和新闻综合分析一下)',
  ];

  for (const q of questions) {
    console.log('\n' + '='.repeat(50));
    console.log(`用户: ${q}`);
    const answer = await chat(q);
    console.log(`助手: ${answer}`);
  }
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

然后我们可以跑一下代码看看效果:

bash 复制代码
# 1. 初始化项目(如果没有的话)
npm init -y

# 2. 安装 Anthropic SDK + TS 依赖
npm install @anthropic-ai/sdk
npm install -D typescript ts-node @types/node
npx tsc --init
# 将 ts.config.json 中的 verbatimModuleSyntax 关掉
# "verbatimModuleSyntax": false

# 3. 设置环境变量(根据你的 shell 来)
export ANTHROPIC_API_KEY=你的key

# 4. 运行
npx ts-node ai-crypto-trader.ts

现在可以看到LLM可以自己根据问题需求来查数据,不需要写任何判断。

bash 复制代码
==================================================
用户: 大饼现在多少钱

>>> 调用工具: get_market_data
    参数: { symbol: 'BTC', data_type: 'realtime' }
[Market] 查询 BTC 的 realtime
    结果: {"symbol":"BTC","price":67234.5,"change_24h":"+2.35%","volume_24h":"28.5B","high_24h":68100,"low_24h":65800,"updated_at"...
助手: 大饼(BTC)现在价格在 **$67,234.5** 左右。

📊 **24h行情**:
- 涨幅:+2.35% ✅ 
- 24h最高:$68,100
- 24h最低:$65,800
- 成交量:285亿美元

今天走势还不错,在$65.8k-$68.1k区间震荡,量能也比较充足。目前处于近期相对强势的位置。

==================================================
用户: 我的仓位现在盈亏怎么样

>>> 调用工具: query_portfolio
    参数: { query_type: 'pnl' }
[Portfolio] 查询 pnl,币种: 全部
    结果: {"today":"+$2,350 (+1.9%)","week":"+$8,900 (+7.6%)","month":"+$15,600 (+14.2%)","total":"+$35,680 (+39.6%)"}...
助手: 不错啊老铁!你的账户盈利情况很健康:

📊 **盈亏数据:**
- **今日:** +$2,350 (↑1.9%)
- **本周:** +$8,900 (↑7.6%)
- **本月:** +$15,600 (↑14.2%)
- **总计:** +$35,680 (↑39.6%)

💡 **简析:**
- 总体收益近40%,这个回报率相当可以了
- 最近一周涨势明显,可能赶上了这波行情
- 今天继续维持正收益,趋势向好

⚠️ **建议:**
- 盈利可观的情况下,可以考虑适当止盈锁定利润
- 建议设置好止损位,保护现有收益
- 如果想了解具体是哪个币种贡献最多,我可以帮你查看详细持仓

需要看下具体持仓明细吗?

==================================================
用户: ETH 巨鲸最近在干嘛

>>> 调用工具: get_onchain_data
    参数: { chain: 'ethereum', metric: 'whale_moves', time_range: '7d' }
[OnChain] 查询 ethereum 的 whale_moves
    结果: {"chain":"ethereum","time_range":"7d","summary":"过去24小时巨鲸净买入","large_txs":[{"type":"buy","amount":"500 BTC","value":"$33...

>>> 调用工具: get_onchain_data
    参数: { chain: 'ethereum', metric: 'exchange_flow', time_range: '7d' }
[OnChain] 查询 ethereum 的 exchange_flow
    结果: {"chain":"ethereum","time_range":"7d","inflow":"$245M","outflow":"$312M","net_flow":"-$67M (净流出)","interpretation":"资金从交...
助手: 根据链上数据,ETH 巨鲸最近的动向挺有意思的:

## 巨鲸行为分析

**过去7天表现:**
- **整体态势**:处于**囤币模式**(accumulation)
- **24小时净买入**:约 $33.7M
- **主要动作**:
  - 从 Binance 提走 500 BTC($33.5M)
  - 未知钱包买入 2000 ETH($6.9M)
  - 少量卖出 100 BTC 到 Coinbase($6.7M)

**交易所流动性:**
- 7天净流出 **$67M**
- 流入:$245M / 流出:$312M
- 💡 **资金正在离开交易所,这通常是看涨信号**

## 解读

巨鲸正在从交易所**提币到冷钱包**,这种行为一般意味着:
1. **长期看好**,不打算短期卖出
2. **减少市场抛压**,流通盘收紧
3. **可能为下一波拉升做准备**

不过要注意,巨鲸也可能是在转移资产进行 OTC 交易或其他操作,建议结合宏观面和技术面综合判断。

---
*本回答不构成投资建议,市场有风险,入市需谨慎。*

==================================================
用户: 最近有什么利好消息

>>> 调用工具: search_crypto_news
    参数: { query: '利好', news_type: 'breaking', time_range: 'week' }
[News] 搜索: 利好
    结果: {"query":"利好","time_range":"week","news_type":"breaking","results":[{"title":"BlackRock ETF 单日流入创新高","source":"The Block...
助手: 好消息!最近确实有不少利好:

**🚀 重磅利好:**

1. **BlackRock ETF创纪录流入**(2小时前)
   - IBIT单日净流入超过5亿美元,创上市以来新高
   - 说明机构资金持续进场,对后市比较看涨

2. **SEC态度松动**(5小时前)
   - SEC主席Gary Gensler在听证会上暗示可能批准更多加密ETF申请
   - 这对整个加密市场是重大利好,可能带来新一轮资金入场

**📊 市场情绪:**
整体偏正面(Positive),机构资金流入+监管态度转向,都是积极信号

**💡 个人观点:**
- ETF持续吸金说明传统资金认可度在提升
- 如果更多ETF获批,可能打开新的上涨空间
- 但短期要注意获利盘回吐风险

⚠️ **风险提示**:本回答不构成投资建议,市场有风险,入市需谨慎。利好消息虽多,但加密市场波动大,建议合理配置仓位。

需要我帮你查看具体哪个币种的行情吗?

==================================================
用户: SOL 能不能继续加仓(从行情、链上和新闻综合分析一下)

>>> 调用工具: get_market_data
    参数: { symbol: 'SOL', data_type: 'realtime' }
[Market] 查询 SOL 的 realtime
    结果: {"symbol":"SOL","price":67234.5,"change_24h":"+2.35%","volume_24h":"28.5B","high_24h":68100,"low_24h":65800,"updated_at"...

>>> 调用工具: get_onchain_data
    参数: { chain: 'solana', metric: 'whale_moves', time_range: '7d' }
[OnChain] 查询 solana 的 whale_moves
    结果: {"chain":"solana","time_range":"7d","summary":"过去24小时巨鲸净买入","large_txs":[{"type":"buy","amount":"500 BTC","value":"$33.5...

>>> 调用工具: get_onchain_data
    参数: { chain: 'solana', metric: 'active_addresses', time_range: '7d' }
[OnChain] 查询 solana 的 active_addresses
    结果: {"chain":"solana","time_range":"7d","active_addresses":987654,"change_pct":"+12.3%"}...

>>> 调用工具: get_onchain_data
    参数: { chain: 'solana', metric: 'defi_tvl', time_range: '30d' }
[OnChain] 查询 solana 的 defi_tvl
    结果: {"chain":"solana","time_range":"30d","tvl_usd":"$21.3B","change_pct":"+4.5%"}...

>>> 调用工具: search_crypto_news
    参数: { query: 'Solana SOL', time_range: 'week' }
[News] 搜索: Solana SOL
    结果: {"query":"Solana SOL","time_range":"week","news_type":"analysis","results":[{"title":"BlackRock ETF 单日流入创新高","source":"T...
助手: ## SOL 综合分析报告

### 📊 **行情面分析**
- **当前价格**:$67,234.5
- **24h涨幅**:+2.35%(处于上涨趋势)
- **24h成交量**:$28.5B(成交活跃)
- **价格区间**:$65,800 - $68,100

**技术面解读**:价格在近期高位震荡,成交量维持高位,说明市场参与度较高,短期动能尚可。

---

### ⛓️ **链上数据分析**
✅ **积极信号**:
1. **巨鲸动向**:过去7天巨鲸净流入+$33.7M,呈现**accumulation(囤币)**状态,大户在持续吸筹
2. **活跃地址**:7天内活跃地址达98.7万,环比增长+12.3%,生态活跃度明显提升
3. **DeFi TVL**:$21.3B,30天增长+4.5%,资金持续流入Solana生态

**链上数据偏多头**,资金和用户都在流入。

---

### 📰 **消息面分析**
- **利好消息**:
  - BlackRock ETF单日流入创新高($500M+)
  - SEC主席暗示可能批准更多加密ETF
  - 整体市场情绪:**Positive/Bullish**

虽然新闻主要是关于BTC/ETH ETF,但对整个加密市场形成利好辐射效应。

---

## 💡 **加仓建议**

### 支持加仓的理由:
1. ✅ 链上数据强劲(巨鲸囤币+活跃度提升+TVL增长)
2. ✅ 短期技术面健康(价格+成交量双升)
3. ✅ 市场整体情绪偏多

### ⚠️ 风险提示:
1. 价格已在相对高位,短期可能面临回调压力
2. 加密市场波动剧烈,需控制仓位
3. 建议采用**分批加仓**策略,避免一次性重仓

### 操作建议:
- **如果看好中长期**:可以分2-3批加仓,回调到$65,000-$66,000区间可考虑分批进入
- **激进策略**:当前价位可小幅加仓30%仓位,回调再补剩余
- **止损位**:建议设在$63,000以下(约5%止损)

---

**⚠️ 免责声明**:本回答不构成投资建议,市场有风险,入市需谨慎。请根据自身风险承受能力和投资策略做决策,切勿盲目跟风。

重点:工具描述太重要了

很多人刚开始写都容易把工具描述(description)写得过于简单。这会直接导致LLM选择错误。

这是一个非常典型的错误案例:

css 复制代码
{
  name: 'get_data',
  description: '获取数据'
}

这种描述写了等于没写。

最佳实践是:description写得越详细、越贴近用户的说法,准确率就越高。

写描述实际上和写prompt是类似的,有几点原则和最佳实践需要遵循。

第一是既要写清楚能干嘛,也要写清楚不能干嘛。

makefile 复制代码
description: `查询用户的持仓和账户信息。
  用户问"我现在仓位怎么样"、"我的BTC成本价多少"这类用这个。
  注意:这是用户私人数据,查大盘行情别用这个。`

最后这句"别用这个"非常重要,因为它可以帮LLM在几个相似的工具之间做区分。

第二是要用用户的语言来写,不要用技术术语来写。

arduino 复制代码
// 别这么写
description: '调用 REST API 获取 OHLCV 数据'

// 这么写
description: '查行情,比如现在多少钱、今天涨了多少、K线怎么走'

描述是针对用户的话来匹配的,用户可能会说大饼,而不是Bitcoin。

第三是给出几个场景例子,帮助LLM更好地建立映射关系。

makefile 复制代码
description: `搜索加密货币新闻。
  比如用户问"最近有什么利好"、"为什么今天跌了"、"SEC有什么动作"这类`

更复杂的场景:需要查询多个数据源

在实际场景中会遇到更多这种情况,比如用户问:"SOL现在可以加仓吗?"

要回答这个问题,LLM需要做几件事:

  1. 查行情,看看现在的价格和趋势。
  2. 查持仓,看看用户目前的仓位。
  3. 查链上数据,看看资金流向。
  4. 查新闻,看看有没有潜在风险。

所以它不像之前的例子那么简单。

不过只要我们的代码支持多轮对话,LLM就会自己来安排好这个流程。它首先会调用一个工具,拿到结果之后去判断需不需要调用别的数据,直到信息足够充分再给出综合分析。

一般来说都会用一个while循环来处理这件事。

arduino 复制代码
while (assistantMsg.tool_calls && assistantMsg.tool_calls.length > 0) {
  // 执行工具
  // 把结果喂回去
  // 继续问 LLM 还要不要调别的
}

这个模式有一个名字叫Agentic Loop,基本上所有复杂场景都是这个套路。

当我们实际去跑"SOL能不能加仓"这个问题的时候,LLM会连续调用N个工具,最后给出一个综合判断:

python 复制代码
用户: SOL能不能继续加仓

>>> 调用工具: get_market_data
>>> 调用工具: query_portfolio  
>>> 调用工具: get_onchain_data
>>> 调用工具: search_crypto_news

助手: 从技术面看 SOL 还在上升趋势,链上数据也显示资金在流入。
但你现在 SOL 仓位已经占了 12%,而且已经浮盈 49%。
如果要加,建议等回调到 $130 附近再考虑,别追高。
当然这只是建议,投资有风险,最终还是你自己决定。

工具实在太多了怎么办

我们只有三五个工具的时候,上面这种做法还是能够应付的,但如果我们有十几个甚至更多数据源的时候,该怎么办呢?比如有行情、K线、深度、资金费率、期权数据、链上指标、新闻快讯、社交媒体......全部塞给LLM让它选,会有两个问题:

  1. 准确率会下降。选项太多很容易选错。
  2. token费用会提高, 每个工具的描述都要算token。

常见的做法是加一个路由层,先对需求做一个粗分类,再进行精选择:

javascript 复制代码
// 先快速分个类
function routeIntent(message) {
  if (/多少钱|价格|行情|涨|跌|K线/.test(message)) return 'market';
  if (/仓位|持仓|余额|盈亏|成本/.test(message)) return 'portfolio';
  if (/链上|巨鲸|流入|流出|Gas/.test(message)) return 'onchain';
  if (/新闻|消息|利好|利空|SEC|监管/.test(message)) return 'news';
  return 'general';
}

// 每个领域只暴露相关的工具
const domainTools = {
  market: [getRealtimePrice, getKline, getDepth, getFundingRate],
  portfolio: [getPositions, getBalance, getPnL, getTradeHistory],
  onchain: [getWhaleMovements, getExchangeFlow, getActiveAddresses, getDeFiTVL],
  news: [searchNews, getSentiment, getKOLOpinions],
  general: [...allTools]
};

async function smartChat(userMessage) {
  const domain = routeIntent(userMessage);
  const relevantTools = domainTools[domain];
  
  return await chat(userMessage, { tools: relevantTools });
}

这种做法的好处是,LLM只需要在最可能的几个工具里面挑选,准确率会变高,同时token的成本也会降低。

例子里面的路由层是用关键词来区分的,另一种做法是训练一个小的分类模型,或者直接用LLM也可以。路由层不需要过度复杂,只需要分一个大类就够了。

另一条路:MCP协议

如果场景更复杂,我们需要同时支持多个LLM、数据源经常变动、或者想做成平台,让其他人也可以接入,这个时候可以尝试使用MCP。

MCP的核心思路是把数据源进行标准化封装,不管是什么LLM客户端,只要支持MCP协议就能够接入。

举个例子:MCP Server就像是一个带有USB接口的电脑,MCP Client就像是各种带USB接口的设备,不管是键盘、鼠标还是硬盘,只要有USB接口就可以插上用。

php 复制代码
// 一个简单的 MCP Server 示例
import { McpServer } from '@anthropic-ai/sdk';

const server = new McpServer('crypto-trading-tools');

server.registerTool({
  name: 'get_btc_price',
  description: '获取 BTC 实时价格',
  parameters: {},
  async handler() {
    const price = await binanceAPI.getPrice('BTCUSDT');
    return { 
      price: price.last,
      change_24h: price.priceChangePercent
    };
  }
});

server.registerTool({
  name: 'get_whale_alerts',
  description: '获取巨鲸转账警报',
  parameters: {
    min_value: { type: 'number', description: '最小金额(USD)' }
  },
  async handler({ min_value }) {
    const alerts = await whaleAlert.getRecent({ min_value });
    return alerts;
  }
});

server.start({ port: 3000 });

MCP的好处是足够标准化,只要写一次数据源就可以到处用。坏处是多了一层抽象,简单的项目没有必要这么做。

实际项目里还会遇到更多问题

举几个例子。

LLM不支持tool/function calling

目前主流LLM大多数都支持了,如果不支持,又不能换模型,也有一些办法,比如直接在system prompt里面让它选择:

arduino 复制代码
const systemPrompt = `你是加密货币交易助手。下面是一些工具选择的例子:

用户问"BTC多少钱" → 用 get_market_data
用户问"我现在亏了多少" → 用 query_portfolio  
用户问"巨鲸在干嘛" → 用 get_onchain_data
用户问"有什么新闻" → 用 search_crypto_news

请根据用户的实际问题选择合适的工具。`;

响应慢

工具调用可以并行:

ini 复制代码
const results = await Promise.all(
  toolCalls.map(call => executeToolCall(call))
);

有一些数据也可以做本地缓存,提升访问速度。比如行情数据,可以设置5秒缓存,没必要每次都调用交易所的API。新闻数据的过期时间可以设置更久,比如24小时。

安全问题

在交易场景中有些关键操作非常需要人工二次确认来保证安全。LLM生成的参数不能无条件信任。

javascript 复制代码
async function executeToolSafely(toolCall, userContext) {
  const args = JSON.parse(toolCall.function.arguments);
  
  // 1. 只能查自己的数据
  if (toolCall.function.name === 'query_portfolio') {
    args.user_id = userContext.userId;  // 强制覆盖,防止查别人的
  }
  
  // 2. 如果是交易相关操作(下单、撤单),要二次确认
  if (['place_order', 'cancel_order'].includes(toolCall.function.name)) {
    return { 
      needsConfirmation: true, 
      message: `确定要${args.side === 'buy' ? '买入' : '卖出'} ${args.amount} ${args.symbol}?` 
    };
  }
  
  return await execute(toolCall);
}

涉及到钱的操作尽量要人工二次确认,别让AI随意执行交易。

总结

本文列举了多种不同的方案。可以根据不同场景选择不同方案,这里列一个场景和建议的表格:

场景 建议
个人用,数据源不多 直接Function Calling
数据源超过10个 加一层路由
要整合多个链的数据 考虑用MCP封装
做成产品给别人用 MCP + 权限控制
复杂交易策略执行 上Agent框架

Agent框架非常适合需要严格编排步骤的业务场景,比如"假如价格到了xx就买入"这种自动化策略。不过大多数问答类的业务场景,用Agentic Loop就够了。目前主流的Agent框架有LangGraph、CrewAI等。

FunctionCalling的优势在于非常容易扩展,如果要加数据,只需要增加一个工具的定义就可以。

如果你想要简报,也不需要去各大网站查行情异动、查链上大额转账、查新闻快讯,你只需要问它一句:"今天有什么需要特别关注的",它就可以给你一份非常全面的简报,可以帮你节省大量的时间。如果你很严谨,可以要求LLM在提供简报的同时提供相应的出处链接,来做二次核验。

讲了这么多,这个技术的核心思想其实也挺简单:别试图用规则穷举所有情况,而是让LLM去理解意图。理解自然语言是LLM的强项,选哪个工具对它来说不难。

当然也别把LLM当神。工具描述写清楚、边界情况处理好、安全措施做到位,这些工程化的事情还是得认真做好。

相关推荐
子兮曰6 小时前
OpenClaw入门:从零开始搭建你的私有化AI助手
前端·架构·github
Victor3566 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
吴仰晖6 小时前
使用github copliot chat的源码学习之Chromium Compositor
前端
1024小神6 小时前
github发布pages的几种状态记录
前端
Victor3566 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端
灰子学技术8 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
不像程序员的程序媛9 小时前
Nginx日志切分
服务器·前端·nginx
Daniel李华9 小时前
echarts使用案例
android·javascript·echarts
北原_春希9 小时前
如何在Vue3项目中引入并使用Echarts图表
前端·javascript·echarts
JY-HPS9 小时前
echarts天气折线图
javascript·vue.js·echarts