# 论文终于中了。实验花在各个平台的钱,还要一笔笔翻出来报销? | EasyNet Source: https://easynet.run/cases/problems/paper-accepted-experiment-api-expenses [easynet.run · 全部问题场景](https://easynet.run/cases#problem-scenarios) # 论文终于中了。实验花在各个平台的钱,还要一笔笔翻出来报销? 录用邮件终于来了。你把消息发到群里,导师回了一句:“实验的费用整理一下,准备报销。”这才想起来:主实验用的是实验室的 OpenAI 项目,消融实验刷了自己的 Claude API,rebuttal 前又临时换了一个平台。钱确实花了,但哪一笔属于这篇论文、哪张票据还没下载,现在都得重新找。 你希望对 Agent 说的是:“把 paper-17 从主实验到 rebuttal 的 API 支出整理出来。按平台、付款人和币种列清楚,附上原始账单;不能确定的单独问我。”有价值的结果不是一个看起来准确的总数,而是一份你敢交给导师核对的材料:每项费用从哪里来,为什么算给这篇论文,还缺哪份凭证。这里用的是虚构项目与金额,不是已发生的报销记录。 难点不是加法。实验室的一个 Key 可能同时服务两篇论文;同一天的支出也可能混着调试和正式实验。充值 100 美元,不等于这篇论文消耗了 100 美元;token 估算、实际成本、付款收据和月度账单也不能重复相加。论文录用后才开始整理时,没有记录过的项目归属不能靠 Agent 猜回来。 先走最短的一条路:让各账号所有者导出覆盖实验时间的成本记录和已有票据,再提供运行日志或项目标签。只有两个导出文件、做一次报销,电子表格或本地 Python 就足够;已有网关的成本看板也值得先用。OpenAI 官方建议财务对账使用 Costs 数据;Claude 也提供 Usage and Cost API,但调用资格和可分组维度要按平台确认。本文不假设它们都能直接返回“这篇论文花了多少钱”。 EasyNet 适合补上的,是材料长期散在不同人、账号和机器上的那一段。你可以让实验室管理员维护一个只读的项目费用查询函数,另一位合作者维护他的导出整理函数,通过函数接入让获准的 Agent 复用。账号管理员的凭据留在提供方;Agent 拿到的是获准项目的费用行和材料引用,不是整个组织的账单访问权。项目参数不能代替权限检查。每个平台的读取和字段映射仍需实现,这不是已经内置的“一键报销”连接器。 附件先把核心对账跑通:平台 A 有 40 美元已归属成本,平台 B 有 27.50 美元成本和 -2.50 美元冲减,归属金额分别是 40 和 25 美元。重复导入的一行不会再算一次;另有 12 美元共享费用不知道属于哪篇论文,9 美元只是 token 估算,分别留待核对和排除。平台 B 的成本缺付款凭证,也会列出来。因此 65 美元是这个样例已归属的成本,不是“已获准报销 65 美元”。代码不联网,不获取真实平台账单。 真正落地时,让 Agent 先把附件测试跑过,再按 AGENTS.md 接你的获准导出。输出一份费用明细、一份待核对清单和一个凭证索引;保留原始文件、导出时间、币种和行号来源。跨币种不直接相加,税费、退款、个人订阅、充值与未消耗余额单独核对。是否能报销、用哪天汇率、需要什么发票或收据,由学校或单位的财务要求决定,Agent 只准备材料,不代替审批。 下一篇论文开始实验时,就给它独立的项目标识,并保留每次运行与平台项目或费用记录的关联。这样到了录用那天,工作就从“回忆这几个月做了什么”,变成“取出已归属的记录、核对缺口、补齐附件”。这才是让 AI 用上各处上下文和工具在科研里的一个具体结果。 01 各账号所有者 获准成本导出、运行记录、原始票据 02 按论文对账 去重、保留币种与冲减,归属不明不猜 03 人工核对材料 费用明细+缺口清单+凭证索引 从分散的费用记录到可核对的报销材料;示意流程,不是平台账单截图。 先对六行样例账目 下载 reconcile.py 后运行 python3 -B reconcile.py。样例格式由本文定义,不是平台 API 的原始响应。 本地示例 · python ``` """Offline normalized-cost demo. No credentials, vendor API calls or invoices.""" from collections import defaultdict from decimal import Decimal, InvalidOperation import json def reconcile(rows, paper): if not paper: raise ValueError("paper is required") seen, totals, review, excluded = {}, defaultdict(Decimal), [], [] for row in rows: # IDs must come from a stable source row, not an import's row number. key = tuple(row.get(k) for k in ("provider", "account", "line_id")) if not all(isinstance(v, str) and v for v in key): raise ValueError("stable provider/account/line_id required") if key in seen: if seen[key] != row: raise ValueError("conflicting source row; reconcile revisions first") continue seen[key] = dict(row) if row.get("kind") != "actual_cost": excluded.append({"source": key, "reason": "not an actual cost row"}) continue try: amount = Decimal(str(row["amount"])) except (KeyError, InvalidOperation): raise ValueError("invalid amount") from None currency = row.get("currency", "") if not amount.is_finite() or len(currency) != 3 or not currency.isascii() or not currency.isupper() or not currency.isalpha(): raise ValueError("finite amount and uppercase currency code required") if not row.get("paper") or not row.get("attribution_ref"): review.append({"source": key, "reason": "paper attribution missing"}) continue if row["paper"] != paper: continue totals[(row["provider"], currency)] += amount if not row.get("document_ref"): review.append({"source": key, "reason": "billing document missing"}) return { "paper": paper, "attributed_costs": [ {"provider": provider, "currency": currency, "amount": str(amount)} for (provider, currency), amount in sorted(totals.items()) ], "needs_review": review, "excluded": excluded, "status": "draft_for_review_not_reimbursement_approval", } DEMO = [ dict(provider="platform-a", account="lab", line_id="cost-01", kind="actual_cost", amount="40.00", currency="USD", paper="paper-17", attribution_ref="run-baseline", document_ref="receipt-a.pdf"), dict(provider="platform-b", account="author", line_id="cost-02", kind="actual_cost", amount="27.50", currency="USD", paper="paper-17", attribution_ref="run-ablation", document_ref=""), dict(provider="platform-b", account="author", line_id="credit-03", kind="actual_cost", amount="-2.50", currency="USD", paper="paper-17", attribution_ref="credit-for-cost-02", document_ref="credit-note.pdf"), dict(provider="platform-a", account="lab", line_id="shared-04", kind="actual_cost", amount="12.00", currency="USD", paper="", attribution_ref="", document_ref="receipt-a.pdf"), dict(provider="platform-c", account="author", line_id="estimate-05", kind="token_estimate", amount="9.00", currency="USD"), ] if __name__ == "__main__": print(json.dumps(reconcile(DEMO + [DEMO[0]], "paper-17"), indent=2)) ``` 预期结果:A:USD 40.00;B:USD 25.00;两项待核对、一项排除。不是获批报销金额。 让 Agent 整理这篇论文的材料 先跑本地测试,再读取获准导出;不索要管理员密钥,不自动提交报销。 [下载 AGENTS.md](https://easynet.run/examples/paper-expenses/AGENTS.md)[reconcile.py](https://easynet.run/examples/paper-expenses/reconcile.py)[test_reconcile.py](https://easynet.run/examples/paper-expenses/test_reconcile.py) 阅读完整交接任务 ``` # Prepare a paper's experiment-expense evidence pack Ask for the paper/project identifier, the experiment interval and timezone, accounts involved, and the institution's requested documents. Do not submit a claim, log into accounts, or collect admin keys without explicit authorization. 1. Run `python3 -B -m unittest test_reconcile.py` and `python3 -B reconcile.py`. These use fictional normalized rows, not provider API response formats. 2. Have each account owner export authorized cost records and billing documents. For an API connector, consult the current provider docs, handle pagination, exclusive interval ends and the reporting timezone, and record export time. Keep provider admin credentials with the account owner, out of prompts/logs. 3. Preserve originals. Normalize monetary amounts as decimal strings, retaining provider, account, currency, stable source row ID, signed credit adjustments, and a source-file reference. Do not combine usage estimates, prepaid top-ups, invoices and cost rows as if they were separate expenses for the same work. 4. Map rows to the paper using dedicated provider projects or recorded run IDs. Keep an attribution reference. Shared keys and aggregate daily costs may not establish paper ownership. Put ambiguous rows in a review list; do not infer exact attribution from dates, titles or token proportions without approval. 5. Use reconcile(rows, paper) for the small demo. Real exports need versioned adapters and tests. Keep different currencies separate. The demo's totals include attributed costs missing documents; they are not approved claim totals. 6. Only if repeated multi-owner access is useful, expose an owner-scoped read-only report through the existing EasyNet function-connection guide. Enforce caller access to a fixed project and interval server-side. A caller-supplied paper ID is a filter, not authorization. Test a different caller is denied. No need to run experiments across two devices just for this task. 7. Produce draft-summary.csv, needs-review.csv, a source manifest and a document index in a new user-approved output directory. Never overwrite originals. Reconcile totals against platform billing, including shared usage, tax, credits, currency conversion and missing periods. Obtain finance review of allocation, reimbursement eligibility and exchange-rate evidence; do not fabricate invoices. References: - https://platform.openai.com/docs/api-reference/usage - https://platform.claude.com/docs/en/manage-claude/usage-cost-api - https://easynet.run/docs/connect/function ``` 接入时的检查项 样例输出平台 A USD 40.00、平台 B USD 25.00,两项待核对、一项排除。 重复导出不重复计费,同一来源行金额冲突时停止,不静默覆盖。 真实查询要拒绝其他项目和未获准调用者;正式票据不得由模型编造。 源码与接入资料 [函数接入指南](https://easynet.run/docs/connect/function) [上下文接入指南](https://easynet.run/docs/connect/context) [OpenAI 官方:Usage 与 Costs API](https://platform.openai.com/docs/api-reference/usage) [Claude 官方:使用量与费用 API](https://platform.claude.com/docs/en/manage-claude/usage-cost-api)