当前位置: 首页 > news >正文

OpenAI Function Calling 实战:构建稳定股票查询AI助手

1. 项目概述:为什么你需要一个“会自己查行情、读新闻”的AI助手?

你有没有过这样的经历:早上打开电脑,第一件事是点开 Yahoo Finance 查苹果股价,再切到 Google News 看一眼最近的科技动态,最后在 Excel 里手动填上数字、复制粘贴几条标题——整个过程耗时 3 分钟,但真正有价值的决策时间可能只有 10 秒。更麻烦的是,当同事突然在 Slack 里问“特斯拉今天跌了多少?出什么事了?”,你得重新切窗口、输 ticker、翻 RSS、确认链接有效性……这种重复性信息搬运,正在 silently 消耗你每天最清醒的 20 分钟。

这就是我们做这个项目的出发点:不是为了炫技,而是把“查数据”这件事从人身上彻底卸下来,交给 AI 做成一件呼吸般自然的事。这里说的“AI”,不是那个只会复述维基百科的聊天机器人,而是一个能听懂你模糊口语(比如“帮我看看英伟达最近咋样”)、能自动拆解意图(既要价格,也要新闻)、能精准调用两个不同工具、还能把结果揉成一句人话回复你的智能体。它背后的核心能力,就是 OpenAI 当前最稳定、最可控的结构化输出机制——function calling。

很多人误以为 function calling 是个高阶技巧,只适合做“AI Agent”或“自动化工作流”。其实恰恰相反,它最早、最扎实的应用场景,就是解决“LLM 输出不稳定”这个最原始的痛点。你让 GPT 直接回答“苹果股价多少”,它可能编造一个 $192.45;但如果你强制它必须调用get_stock_price("AAPL")这个函数,那返回值就永远来自 Yahoo Finance 的实时接口——误差归零,可信度拉满。这不是在教 AI 做事,而是在给它装上一把带刻度的游标卡尺,让它所有输出都落在物理世界的真实坐标上。

我过去三年带过 17 个金融类 AI 项目,其中 12 个在初期都栽在同一个坑里:过度依赖 prompt engineering。有人花两周时间打磨“请严格按 JSON 格式返回,字段名必须小写,price 字段保留两位小数……”,结果上线后第三天,模型突然在某个长尾 ticker(比如“$TSLA”带美元符号)上多加了个空格,整个下游解析器就崩了。后来我们全量切换到 function calling,故障率直接从月均 4.2 次降到 0。这不是玄学,是工程确定性的胜利——当你把“生成”和“执行”彻底分离,就把不可控的黑箱,变成了可验证的白盒流水线。

所以这篇教程不讲虚的。它面向三类人:想快速落地一个股票查询工具的开发者、需要向老板证明 AI 能干实事的业务方、以及刚接触 function calling 想亲手跑通第一个 demo 的新手。你会看到的不是 API 文档的翻译,而是我压箱底的实操细节:为什么选 yahooquery 而不是 yfinance,为什么 RSS feed 要加region=US&lang=en-US参数,为什么strict=True这个开关必须打开,甚至包括如何用 3 行代码绕过 Yahoo Finance 的反爬策略。所有内容,都来自我在真实生产环境里踩过的坑、记下的日志、和凌晨三点改完的第 11 版代码。

2. 核心设计思路:为什么是“双函数协同”,而不是单点突破?

2.1 单函数调用的天花板与陷阱

很多初学者会先尝试“一个函数搞定所有事”,比如写一个get_stock_info(ticker),内部同时调用股价接口和新闻 RSS,最后拼成字符串返回。这看似省事,但实际埋下了三个致命隐患:

第一,职责混淆导致调试地狱。当用户问“谷歌股价多少”,函数返回了错误价格,你得先排查是 yahooquery 请求失败、还是 RSS 解析出错、或是拼接逻辑有 bug。而真正的故障点可能只是 Yahoo Finance 临时封了你的 IP,但你却花了两小时重写新闻解析模块。

第二,模型无法做细粒度决策。GPT-4.5 的 function calling 本质是“意图识别+参数提取”,它擅长判断“用户要查价格”,但不擅长判断“当前股价接口超时,该降级到缓存数据”。如果所有逻辑塞进一个函数,你就剥夺了模型在链路中动态选择的能力。

第三,扩展性为零。今天加个“财报摘要”功能,明天加个“竞品对比”,后天加个“技术指标图”,你只能不停往get_stock_info里塞 if-else,最终变成没人敢动的意大利面条代码。

我见过最典型的案例,是某券商内部的一个“投研助手”。他们最初用单函数实现,半年后函数长达 487 行,包含 19 个 try-except 块,每次发布新版本都要全量回归测试 2 小时。后来我们帮他们重构为 7 个独立函数(价格、新闻、PE、股息、机构持仓、同业对比、风险提示),每个函数平均 62 行,CI/CD 流水线跑完只要 4 分钟。这不是代码洁癖,而是工程效率的硬指标。

2.2 双函数架构的底层逻辑:让 AI 做判断,让代码做执行

我们选择get_stock_priceget_stock_news作为两个独立函数,核心是遵循“单一职责 + 显式契约”的设计哲学。这里的关键不是“数量”,而是“边界”。

  • get_stock_price的契约非常苛刻:输入必须是标准 ticker(如 AAPL、GOOGL),输出必须是“{ticker} is currently trading at ${price:.2f}”。它不处理任何异常语义(比如用户输错成“AAPPL”),也不提供历史数据,更不解释涨跌原因。它的唯一使命,就是把 Yahoo Finance 的regularMarketPrice字段,原封不动、零失真地搬运出来。

  • get_stock_news的契约同样清晰:输入是 ticker,输出是格式化的新闻列表,每条包含标题和可点击链接。它不判断新闻重要性(那是 LLM 的事),不清洗标题里的广告词(那是前端的事),甚至不验证链接是否 200 OK(那是监控系统的事)。它只做一件事:从 Yahoo Finance RSS feed 中取前三条entry.titleentry.link,用\n拼接。

这两个函数之间,通过 OpenAI 的 tool calling 机制形成松耦合。当用户问“苹果股价和最新消息”,模型不是自己去算,而是发出两条并行指令:

[ {"name": "get_stock_price", "arguments": {"ticker": "AAPL"}}, {"name": "get_stock_news", "arguments": {"ticker": "AAPL"}} ]

注意,这里没有“先后顺序”,没有“依赖关系”,完全是异步的。这意味着你可以轻松替换任一函数——比如把get_stock_news换成调用 Bloomberg Terminal API 的版本,只要输入输出契约不变,整个系统无需修改一行代码。

这种设计带来的最大好处,是让 LLM 从“执行者”回归到“指挥官”。它不再需要记住“新闻链接要带 .html?tsrc=rss 后缀”,也不用纠结“股价小数点后该保留几位”,它只需要理解用户意图,并把任务分派给最合适的工具。就像一个经验丰富的项目经理,他不需要会写 SQL,但必须知道什么时候该叫 DBA,什么时候该叫前端工程师。

2.3 为什么是 Yahoo Finance 而非其他数据源?

选型不是拍脑袋决定的。我们对比了 5 个主流免费数据源,最终锁定 Yahoo Finance,基于三个硬性指标:

数据源实时性免费额度RSS 稳定性反爬强度我们的实测结论
Yahoo Finance延迟 15-20 秒无限制★★★★☆ (URL 结构固定)中等(需 User-Agent)首选:RSS feed 地址可预测,s=AAPL参数直传,解析稳定
Alpha Vantage延迟 1-2 分钟500 次/天无 RSS高(IP 封禁频繁)不适用:免费版延迟过大,且无新闻源
Polygon.io延迟 < 1 秒500 次/天无 RSS高(需 API Key)成本过高:新闻需额外订阅,不适合 demo
Google Finance延迟 30 秒无限制无 RSS极高(JS 渲染,需 Puppeteer)放弃:爬取成本远超收益,且无结构化新闻接口
Seeking Alpha延迟 1 小时仅摘要免费无 RSS中等(需登录)不适用:深度分析需付费,基础新闻质量不如 Yahoo

特别说明 RSS 的关键细节:Yahoo Finance 的 RSS URL 是https://feeds.finance.yahoo.com/rss/2.0/headline?s={ticker}&region=US&lang=en-US。很多人忽略regionlang参数,导致某些 ticker(如日本公司“7203.T”)返回空 feed。我们实测发现,加上这两个参数后,覆盖率达 99.2%,且 feed 条目数稳定在 20-30 条,足够取前三条。

另外,yahooquery库比yfinance更适合此场景。yfinance在获取regularMarketPrice时,会触发完整的 ticker 初始化(下载所有历史数据),耗时 1.2 秒;而yahooquery.Ticker(ticker).price是轻量级请求,平均耗时 320ms,且对网络抖动更鲁棒。我们在 AWS us-east-1 区域做了 1000 次压测,yahooquery的成功率是 99.8%,yfinance是 97.3%——这 2.5% 的差距,在高频查询场景下就是 SLA 的生死线。

3. 实操细节解析:从零搭建双函数系统的完整链路

3.1 环境准备与依赖安装:避开 pip 的隐藏陷阱

别跳过这一步。很多人的 demo 卡在第一步,不是代码问题,而是环境配置的坑。我们用最精简、最稳定的组合:

# 创建干净虚拟环境(强烈推荐,避免包冲突) python -m venv stock_ai_env source stock_ai_env/bin/activate # macOS/Linux # stock_ai_env\Scripts\activate # Windows # 安装核心依赖(注意版本号!) pip install openai==1.42.0 yahooquery==2.2.21 feedparser==6.0.10

为什么锁死这些版本?

  • openai==1.42.0:这是目前兼容gpt-4.5-preview最稳定的 SDK 版本。新版 1.45.0 引入了response_format参数,但会与tool_choice="auto"冲突,导致函数调用失效。
  • yahooquery==2.2.21:这是最后一个支持 Python 3.8+ 且未移除Ticker.price属性的版本。新版 3.x 已改为异步接口,需要重写整个函数。
  • feedparser==6.0.10:这是最后一个默认启用resolve_relative_uris=True的版本,能自动补全 RSS 中的相对链接(如<link>/news/abc.html),避免手动拼接域名。

提示:如果你用 Jupyter Notebook,安装后务必重启内核。曾有用户反馈yahooquery在 notebook 中首次导入失败,重启后正常——这是其内部 lazy import 机制导致的已知问题。

3.2 股价函数get_stock_price:如何让数据“零失真”抵达用户

这个函数表面简单,但藏着三个关键防御点。我们逐行拆解:

from yahooquery import Ticker import json def get_stock_price(ticker: str) -> str: # 防御点1:ticker 标准化(大写 + 去空格 + 去特殊字符) clean_ticker = ticker.strip().upper().replace("$", "").replace(".", "-") try: # 防御点2:设置超时和重试(yahooquery 默认无重试) t = Ticker(clean_ticker, timeout=5, max_workers=1) # 关键:直接访问 price 属性,而非 full_quote 或 summary_detail # 因为 price 是最轻量、最稳定的 endpoint price_data = t.price # 防御点3:多重校验,拒绝任何可疑数据 if not isinstance(price_data, dict): return f"Invalid response format for {clean_ticker}." if clean_ticker not in price_data: return f"Ticker {clean_ticker} not found on Yahoo Finance." ticker_data = price_data[clean_ticker] if not isinstance(ticker_data, dict): return f"Unexpected data type for {clean_ticker}." # 优先取 regularMarketPrice(盘中价),fallback 到 previousClose price = ticker_data.get("regularMarketPrice") or \ ticker_data.get("previousClose") if price is None: return f"No valid price data for {clean_ticker}." # 强制格式化:确保两位小数,避免 192.0 变成 192 return f"{clean_ticker} is currently trading at ${price:.2f}" except Exception as e: # 记录详细错误(生产环境应发到 Sentry) error_msg = str(e).lower() if "timeout" in error_msg or "connection" in error_msg: return f"Network timeout fetching {clean_ticker} price. Please try again." elif "404" in error_msg: return f"Ticker {clean_ticker} not found. Please check the symbol." else: return f"Failed to retrieve {clean_ticker} price: {error_msg[:50]}..."

这段代码的精髓在于“防御性编程”:

  • 标准化处理:用户可能输入$AAPLaaplAAPL.,统一转为AAPL,避免yahooquery内部解析失败。
  • 超时控制timeout=5是经过压测的黄金值。设太短(如 2 秒)会导致美股盘中波动期大量超时;设太长(如 10 秒)会让用户等待感强烈。
  • 数据校验链:不是拿到price_data就完事,而是层层检查dict类型、key 存在性、value 有效性。我们曾在线上环境捕获过一次 Yahoo Finance 返回空{"AAPL": null}的 case,正是这个校验链及时兜底。

注意:t.price返回的是一个嵌套字典,结构类似{"AAPL": {"regularMarketPrice": 192.45, "currency": "USD", ...}}。不要试图用t.summary_detail,它会触发额外请求,且字段名不一致(如currentPricevsregularMarketPrice)。

3.3 新闻函数get_stock_news:RSS 解析的稳定性秘籍

Yahoo Finance 的 RSS feed 看似简单,但实际有 4 个易被忽略的坑:

  1. Feed 条目数不稳定:有时返回 5 条,有时 25 条。我们取[:3]是安全的,但必须加空值保护。
  2. 标题含 HTML 实体:如&amp;&quot;,需用html.unescape()解码。
  3. 链接可能是相对路径:如/news/abc.html,需补全为https://finance.yahoo.com/news/abc.html
  4. 部分条目缺失titlelink字段:必须用.get()并提供默认值。

修正后的函数如下:

import feedparser import html from urllib.parse import urljoin def get_stock_news(ticker: str) -> str: # 构建 RSS URL(region 和 lang 是稳定性的关键) rss_url = f"https://feeds.finance.yahoo.com/rss/2.0/headline?s={ticker}&region=US&lang=en-US" try: # 设置 User-Agent,模拟浏览器(绕过基础反爬) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } feed = feedparser.parse(rss_url, agent=headers['User-Agent']) # 检查 feed 是否有效 if feed.bozo and feed.bozo_exception: return f"RSS feed error for {ticker}: {feed.bozo_exception}" if not feed.entries: return f"No news found for {ticker}." # 提取前三条,带完整错误处理 news_items = [] base_url = "https://finance.yahoo.com" for entry in feed.entries[:3]: # 解码 HTML 实体 title = html.unescape(entry.get("title", "No Title")) # 补全相对链接 link = urljoin(base_url, entry.get("link", "")) # 过滤明显无效链接(如 javascript:void(0)) if "javascript:" in link or "mailto:" in link: continue news_items.append(f"{title} ({link})") if not news_items: return f"Valid news entries not found for {ticker}." news_str = "\n".join(news_items) return f"Latest news for {ticker}:\n{news_str}" except Exception as e: error_msg = str(e)[:60] return f"Failed to retrieve news for {ticker}: {error_msg}..."

关键点说明:

  • feedparser.parse(..., agent=...)中的agent参数,是绕过 Yahoo Finance 基础反爬的最小成本方案。不加的话,约 30% 的请求会返回空 feed。
  • urljoin(base_url, entry.link)确保所有链接都是绝对路径,避免前端点击 404。
  • html.unescape()处理&amp;等实体,否则用户看到的是 “Nvidia&AI Server” 而非 “Nvidia&AI Server”。

我们实测过,这个函数在连续 1000 次调用中,失败率仅为 0.7%,且 92% 的失败是因 Yahoo Finance 临时维护,而非代码问题。

3.4 OpenAI 工具定义:strict=True是稳定性的基石

很多人复制粘贴官方文档的 tools 定义,却忽略了strict=True这个开关。它的作用,是让 OpenAI 模型在参数校验上变得“铁面无私”:

tools = [ { "type": "function", "function": { "name": "get_stock_price", "description": "Get current stock price for a provided ticker symbol from Yahoo Finance.", "parameters": { "type": "object", "properties": { "ticker": { "type": "string", "description": "The stock ticker symbol, e.g., AAPL, GOOGL, TSLA" } }, "required": ["ticker"], "additionalProperties": False, "strict": True # ← 这是关键! } } }, { "type": "function", "function": { "name": "get_stock_news", "description": "Get the latest news headlines for a stock ticker from Yahoo Finance RSS feed.", "parameters": { "type": "object", "properties": { "ticker": { "type": "string", "description": "The stock ticker symbol, e.g., AAPL, GOOGL, TSLA" } }, "required": ["ticker"], "additionalProperties": False, "strict": True # ← 同样关键! } } } ]

strict=True的效果是什么?它强制模型:

  • 必须只传ticker字段,不能多传exchangecountry
  • ticker值必须是 string,不能是 number(如 123);
  • 不能传空字符串""或纯空格" "
  • 如果用户说“查苹果和谷歌”,模型会生成两个独立调用,而不是一个{"ticker": ["AAPL", "GOOGL"]}

没有strict=True时,模型可能生成{"ticker": "AAPL ", "extra": "ignore"},你的 Python 函数会收到带空格的"AAPL ",然后yahooquery报错。开了之后,模型要么生成合规 JSON,要么干脆不调用函数——把错误扼杀在源头。

注意:strict=True是 OpenAI 2024 年 3 月后新增的特性,旧版 SDK 不支持。这也是我们锁死openai==1.42.0的原因之一。

4. 完整实操流程:从第一次调用到生产级部署

4.1 第一次调用:单函数验证(5 分钟跑通)

这是建立信心的关键一步。我们用最简代码验证get_stock_price

from openai import OpenAI import json client = OpenAI(api_key="your-api-key-here") # 替换为你的 key # 构建消息(注意:role 必须是 user) messages = [ {"role": "user", "content": "What's the current price of Apple stock?"} ] # 发起调用(关键参数:tool_choice="auto") completion = client.chat.completions.create( model="gpt-4.5-preview", messages=messages, tools=tools, # 上节定义的 tools 列表 tool_choice="auto" # 让模型自主决定是否调用 ) # 检查模型是否调用了函数 if completion.choices[0].message.tool_calls: print("✅ Model decided to call function!") tool_call = completion.choices[0].message.tool_calls[0] print(f"Function name: {tool_call.function.name}") print(f"Arguments: {tool_call.function.arguments}") # 解析参数并执行 args = json.loads(tool_call.function.arguments) result = get_stock_price(args["ticker"]) print(f"Function result: {result}") else: print("❌ Model did not call any function.")

运行后,你应该看到:

✅ Model decided to call function! Function name: get_stock_price Arguments: {"ticker": "AAPL"} Function result: AAPL is currently trading at $192.45

如果没看到,检查三点:

  1. API Key 是否正确(在 OpenAI Platform 生成);
  2. model="gpt-4.5-preview"是否拼写正确(注意是preview,不是preview-0418);
  3. 用户消息中是否明确包含“price”、“how much”、“cost”等触发词(模型不会为“tell me about Apple”调用价格函数)。

4.2 双函数协同:处理复合请求的完整链路

当用户问“谷歌股价和最新消息”,我们需要四步闭环:

# 步骤1:初始请求 messages = [ {"role": "user", "content": "What's the current price of Google stock and can you show me the latest news about it?"} ] completion = client.chat.completions.create( model="gpt-4.5-preview", messages=messages, tools=tools, tool_choice="auto" ) # 步骤2:收集所有函数调用结果 tool_calls = completion.choices[0].message.tool_calls if not tool_calls: print("No functions called.") exit() # 执行所有调用,并存储结果 tool_results = {} for tool_call in tool_calls: func_name = tool_call.function.name args = json.loads(tool_call.function.arguments) if func_name == "get_stock_price": tool_results["price"] = get_stock_price(args["ticker"]) elif func_name == "get_stock_news": tool_results["news"] = get_stock_news(args["ticker"]) # 步骤3:构造第二轮消息(必须包含 tool_call_id) messages.append(completion.choices[0].message) # 模型的 function_call 消息 for tool_call in tool_calls: func_name = tool_call.function.name result_content = tool_results.get("price") if func_name == "get_stock_price" else tool_results.get("news") messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result_content }) # 步骤4:发起第二轮调用,获取最终自然语言回复 final_completion = client.chat.completions.create( model="gpt-4.5-preview", messages=messages, tools=tools # 仍需传入,否则报错 ) print("🎯 Final answer:") print(final_completion.choices[0].message.content)

预期输出:

🎯 Final answer: The current price of Google stock (GOOGL) is $142.35. Latest news for GOOGL: Google Cloud’s New AI Tools Aim to Simplify Enterprise Adoption (https://finance.yahoo.com/news/google-clouds-new-ai-tools-075011863.html?.tsrc=rss) Alphabet Inc. (GOOGL) Q1 Earnings Beat Estimates Amid AI Investment Surge (https://finance.yahoo.com/news/alphabet-inc-googl-q1-earnings-074433615.html?.tsrc=rss) Google’s Gemini Models Now Available for Developers via Vertex AI (https://finance.yahoo.com/news/googles-gemini-models-now-available-051548210.html?.tsrc=rss)

这里的关键细节:

  • tool_call_id必须严格匹配:第二轮messages中的tool_call_id,必须和第一轮tool_calls[0].id完全一致,否则 OpenAI 会报错tool_call_id not found
  • role: "tool"是固定写法:不能写成"assistant""system",这是 OpenAI 的协议约定。
  • 第二轮仍需传tools:即使不打算再调用,也必须传入,否则 SDK 报错。

4.3 生产级封装:一个函数搞定所有(附错误处理)

把上述流程封装成可复用的函数,是走向生产的第一步:

def run_stock_query(user_query: str) -> str: """ 主入口函数:接收用户自然语言查询,返回结构化结果 """ messages = [{"role": "user", "content": user_query}] try: # 第一轮:获取函数调用指令 completion = client.chat.completions.create( model="gpt-4.5-preview", messages=messages, tools=tools, tool_choice="auto", timeout=30 # 整体超时 ) # 如果没调用函数,直接返回模型回复 if not completion.choices[0].message.tool_calls: return completion.choices[0].message.content # 执行所有函数调用 tool_calls = completion.choices[0].message.tool_calls tool_results = {} for tool_call in tool_calls: try: func_name = tool_call.function.name args = json.loads(tool_call.function.arguments) if func_name == "get_stock_price": tool_results["price"] = get_stock_price(args["ticker"]) elif func_name == "get_stock_news": tool_results["news"] = get_stock_news(args["ticker"]) except Exception as e: # 单个函数失败不影响整体 tool_results["error"] = f"Function {func_name} failed: {str(e)[:50]}" # 构造第二轮消息 messages.append(completion.choices[0].message) for tool_call in tool_calls: func_name = tool_call.function.name result_content = ( tool_results.get("price") if func_name == "get_stock_price" else tool_results.get("news", tool_results.get("error", "Unknown error")) ) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": str(result_content) }) # 第二轮:生成最终回复 final_completion = client.chat.completions.create( model="gpt-4.5-preview", messages=messages, tools=tools, timeout=30 ) return final_completion.choices[0].message.content except Exception as e: return f"System error: {str(e)[:100]}" # 使用示例 print(run_stock_query("What's Tesla's stock price and latest news?"))

这个封装函数已具备生产可用性:

  • 超时控制:两轮调用都设timeout=30,避免单次请求卡死;
  • 错误隔离:一个函数失败(如新闻 RSS 不可用),另一个(股价)仍可返回;
  • 降级策略:当tool_results为空时,用兜底错误信息填充,保证messages不出现None

4.4 本地测试与调试技巧:如何快速定位问题

在开发阶段,90% 的时间花在调试。分享几个我常用的技巧:

技巧1:打印完整调用链

# 在每次 create() 后,打印 raw response print("=== RAW COMPLETION ===") print(completion.model_dump_json(indent=2))

这会输出完整的 JSON,你能看到tool_callsidfunction.namefunction.arguments,以及finish_reason(是tool_calls还是stop)。

技巧2:Mock 函数进行单元测试

# 测试 get_stock_price 的逻辑,不依赖网络 def test_get_stock_price(): # Mock yahooquery 的返回 import unittest.mock as mock with mock.patch('yahooquery.Ticker') as mock_ticker: mock_instance = mock.Mock() mock_instance.price = {"AAPL": {"regularMarketPrice": 192.45}} mock_ticker.return_value = mock_instance result = get_stock_price("AAPL") assert "AAPL is currently trading at $192.45" in result

技巧3:用 curl 手动触发 OpenAI API当 Python SDK 报错时,用 curl 绕过 SDK 直接调用,确认是代码问题还是 API 问题:

curl https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-api-key" \ -d '{ "model": "gpt-4.5-preview", "messages": [{"role": "user", "content": "What is AAPL price?"}], "tools": [{"type": "function", "function": {"name": "get_stock_price", "description": "...", "parameters": {...}}}] }'

5. 常见问题与实战排障:那些文档里不会写的坑

5.1 模型不调用函数?先检查这 5 个点

这是新手最高频的问题。按优先级排查:

排查点检查方法修复方案
1. 用户消息缺乏触发词检查content是否含“price”、“cost”、“how much”、“news”、“headlines”等关键词改为:“What’s the current price of Microsoft stock?” 而非 “Tell me about MSFT”
2. tools 定义格式错误打印tools[0]["function"]["parameters"],确认required是 list,strict是 bool确保"required": ["ticker"],不是"required": "ticker"
3. model 名称错误gpt-4.5-preview是唯一支持 function calling 的 4.5 模型,gpt-4.5不存在严格使用model="gpt-4.5-preview"
4. API Key 权限不足登录 OpenAI Platform ,查看gpt-4.5-preview的调用量确保组织有访问权限,联系管理员开通
5. 消息 role 错误messages中第一条必须是{"role": "user", ...},不能是"system"删除所有system消息,或确保user是第一条

实测案例:一位用户卡了两天,最后发现他的messages[{"role": "system", "content": "You are a stock assistant..."}, {"role": "user", "content": "..."}]。OpenAI 的 function calling 要求user消息必须是第一条,否则忽略 tools。删掉 system 消息后立即生效。

5.2 “Failed to retrieve data for XXX” 错误的根因分析

这个错误通常来自yahooquery,但背后有 4 种完全不同的原因:

错误现象根本原因解决方案
Failed to retrieve data for AAPL: 'NoneType' object is not subscriptablet.price返回None,因为 Yahoo Finance 临时返回空响应加重试逻辑:for i in range(3): try: ... except: time.sleep(1)
Failed to retrieve data for TSLA: HTTP Error 404ticker 拼写错误(如TSLAvsTSLAQ),或 Yahoo Finance 未收录在函数开头加 ticker 校验:if clean_ticker not in ["AAPL","GOOGL","MSFT"]: return "Unsupported ticker"
Failed to retrieve data for 7203.T: 'regularMarketPrice'日股 ticker 需要region=JP,但yahooquery不支持改用yfinance获取日股,或放弃支持非美股
Failed to retrieve data for BTC-USD: 'currency'加密货币 ticker 在t.price中结构不同单独处理:if "USD" in clean_ticker: use yfinance

我们的生产环境解决方案是:对yahooquery失败的 ticker,自动 fallback

http://www.jsqmd.com/news/1083694/

相关文章:

  • 崩铁尘灵游乐园玩法介绍
  • CUA:让大模型操控电脑的开放框架——从原理到 Python 实战
  • DLSS Swapper终极指南:三步掌握游戏性能优化的秘密武器
  • 国内荧光增白剂厂集中在哪些产区?主要分布梳理
  • 树莓派系统与固件更新全攻略:从基础命令到硬件维护
  • 基于DDS求解器的最大割问题建模、求解与性能优化实践
  • Docker 一键部署 MySQL 8.0
  • 2026年还在找低价 ChatGPT Plus?最近被封和失效变多后,我更建议你先看稳定
  • 让PPT演示时间掌控自如:PPTTimer智能计时器全面解析
  • 爬虫反爬进阶——IP代理池、请求指纹、字体反爬实战
  • 淮南装修公司排名大全
  • HarmonyOS7 网络卡顿别只会重试:QUIC、持久连接和预建链优化
  • Navicat重置教程:macOS上无限试用Navicat Premium的终极指南
  • VRPN:异构设备网络化集成的核心协议与实战指南
  • 【课程设计/毕业设计】基于 SpringBoot+Vue 的企业员工运维日志管理系统的设计与实现 基于 SpringBoot+Vue 的员工工作轨迹记录管理系统【附源码、数据库、万字文档】
  • Python 爬虫实战:北极星日淘日本本土商品数据同步采集(反爬+增量更新)
  • ArkUI 状态管理与页面交互核心:@State、弹窗与路由
  • 3分钟搞定!Soundflower虚拟音频驱动让Mac应用间音频流转如此简单
  • 基于CAMx的空气质量模拟及污染来源解析技术与案例分析
  • 2026年国内用户使用 ChatGPT Plus / Pro:为什么我更建议先考虑稳定,而不是只看价格?
  • 终极宝可梦随机化器:Universal Pokemon Randomizer ZX完全指南,5分钟打造你的专属冒险
  • 【供应链建设】伸缩延长杆源头工厂供应商的工程能力是建立供应链的关键
  • 靠谱AI营销的企业
  • ThinkAdmin路径遍历漏洞CVE-2020-25540深度剖析与防御实战
  • Qwerty Learner:如何通过打字练习重构你的英语肌肉记忆?
  • 如何快速掌握鼠标连点器:面向新手的完整自动化工具指南
  • Python 高性能并发:从 GIL 瓶颈到协程调度的工程突围
  • GitHub今日热榜 | 2026-06-25:Agent开发环境爆发,7个项目首次入榜
  • 鸿翼OpenContent™ AI智能多模态数据管理平台介绍与功能场景
  • TranslucentTB:Windows任务栏透明化终极指南,打造个性化桌面体验