最近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需要做几件事:
- 查行情,看看现在的价格和趋势。
- 查持仓,看看用户目前的仓位。
- 查链上数据,看看资金流向。
- 查新闻,看看有没有潜在风险。
所以它不像之前的例子那么简单。
不过只要我们的代码支持多轮对话,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让它选,会有两个问题:
- 准确率会下降。选项太多很容易选错。
- 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当神。工具描述写清楚、边界情况处理好、安全措施做到位,这些工程化的事情还是得认真做好。