你写了一个清洗销售表的函数,自己每周都在用。同事也想用:输入一份表格,拿回处理好的结果,本来应该是件小事。可代码发过去以后,他先碰到 Python 版本不对,又缺一个依赖。最后还是把表格发回给你,说:“要不你帮我跑一下吧。”
他并不需要维护你的代码,更不想接管你的开发环境。他只需要使用那个已经跑通的处理方法。每多一个人使用,就让他重新配置一次环境,往往会把一项已经完成的工作,变成作者长期负责的安装答疑和人工代跑。
当然,不是所有函数都值得变成远程服务。没有复杂依赖的小工具,直接发一个包更方便;团队已经有容器或内部 API,也可以继续使用。只有当这套方法需要由你持续维护,而别人主要想提交输入、获取结果时,把执行留在提供方才有实际意义。
EasyRemote 提供的是这种函数级接入:将选定函数注册为可调用的能力,处理逻辑仍在你的环境运行。EasyNet Runtime 是负责接收和处理这些调用的运行服务。另一位使用者提交规定的参数,由提供方执行,再取得允许返回的结果;他不需要安装你的处理依赖,但仍需要完成连接和访问授权。
如果同事也写 Python,可以给他带类型提示的接口,编辑器会提示该传什么、返回什么。先处理小表格时,可以由调用端读出允许的列,把有数量上限的记录作为参数发送,再将返回记录保存为文件。不能直接传一个本地路径,期待另一台电脑自动读到它。大文件需要另外接好受控上传或双方有权访问的文件入口;这些都应先约定,不能把“函数可调用”当作“文件已经送达”。
第一步不必把整个项目发布出去。先切出清洗这一项工作,规定必要列和结果格式,并在提供方检查数据量与非法输入。再让同事用自己的身份,从另一台电脑完成一次处理。真正要观察的是:他能否独立取得正确结果,出错时是否知道该改输入还是联系维护者。网络 Python 库示例展示的是接口分发方式,实际表格处理和文件交付仍由你接好。
把函数交出去,也意味着你开始维护别人依赖的服务。电脑睡眠、网络断开或依赖失效时,对方应看到明确状态,不能继续相信一句“已经完成”。如果这种维护成本可以接受,变化才是实在的:同事继续做自己的分析,你继续维护那一套方法,不再因为每个人的环境不同,反复处理同一份安装问题。
- 01
读小表格
调用方选择列并限制记录数
- 02
提供方清洗
已有函数检查并处理记录
- 03
保存结果
同事取得返回记录,不复制执行环境
先把一个清洗规则写清楚
本地示例只验证去除姓名两端空白这一条规则;网络注册、身份和文件交付需要另接。
def clean_names(rows: list[dict[str, str]]) -> list[dict[str, str]]:
if len(rows) > 100:
raise ValueError("Too many rows")
result = []
for row in rows:
if not isinstance(row.get("customer"), str):
raise ValueError("customer must be text")
result.append({"customer": row["customer"].strip()})
return result
print(clean_names([{"customer": " North shop "}]))预期结果:[{'customer': 'North shop'}];超过100行或字段不是文本会拒绝。
同一个函数,上方加上 @node.register
将下面的完整代码保存为 provider.py。函数体不变;类型标注描述输入输出,装饰器把它登记为 sales.clean_names。安装 EasyRemote 并配置好提供方 Runtime 后,运行 python3 provider.py,由 node.serve() 启动接入。这里演示 Python 函数接入,不代表 EasyNet 的其他接入方式也只支持 Python。
"""Run only after configuring the provider's EasyNet Runtime."""
from easyremote import ComputeNode
node = ComputeNode(namespace="sales")
@node.register
def clean_names(rows: list[dict[str, str]]) -> list[dict[str, str]]:
if len(rows) > 100:
raise ValueError("Too many rows")
result = []
for row in rows:
if not isinstance(row.get("customer"), str):
raise ValueError("customer must be text")
result.append({"customer": row["customer"].strip()})
return result
if __name__ == "__main__":
node.serve()
预期结果:提供方运行清洗逻辑;调用输入为 {"rows":[{"customer":" North shop "}]}。实际调用地址须从发布信息取得,sales.clean_names 只是名称。装饰器不会自动授予同事访问权,本段不是跨用户调用成功实录。
发布后,查看这项函数的调用记录
在提供方终端,将 CLEAN_NAMES_ABILITY_URA 设为发布结果中的实际 ability URA,再执行下面的查询。它按能力筛选记录;查询结果仍受当前身份的读取权限约束,不是拿到地址就能读取别人的记录。
easynet invocation list --ability-ura "${CLEAN_NAMES_ABILITY_URA:?Set the actual published ability URA first}" --format json预期结果:用 invocation URA、调用方与状态找到本次处理。空列表不表示处理成功;调用记录也不是输入输出字节统计。
把这次接入交给编程 Agent
复制 AGENTS.md,并附上本页地址。它从清洗规则开始验证,再检查发布信息、调用授权与返回结果。附件为英文,代码下载与上方展示使用同一份源码。
阅读完整交接任务
# Share one maintained cleaning function
Help the user expose their existing cleaning function through EasyRemote and
let an authorized consumer obtain its result without installing the provider's
business dependencies. The sample trims the `customer` field in at most 100
rows. It is deliberately small: it demonstrates registration, not a reason
to use remote execution for every string operation.
## Start with the actual need and inspected files
Ask what dependency or maintained business rule makes provider-side execution
useful. If this simple trimming function is the whole task, a shared Python
package or ordinary document with the code may be enough. Do not invent a
second-device requirement. An existing authenticated HTTP service or MCP tool
can also remain in use; do not remove it merely to demonstrate EasyNet.
Obtain this case's page URL from the user if the source files were not supplied.
Resolve `/examples/shared-cleaning/provider.py` against that URL. Do not invent
a download origin. A localhost URL on one device does not identify the same
server on another device. Ask for an approved reachable source or file transfer.
Read the file and local repository instructions before executing anything.
Use a new user-approved working directory; do not overwrite an existing
AGENTS.md, application, or output file.
The page's provider.py and downloadable provider.py are the same source. The
function keeps only `customer`, trims its ends, and preserves row order. It
does not deduplicate customers, validate email addresses, read files, preserve
other columns, or perform general sales-data cleaning. Empty strings remain
empty. Use synthetic inputs only until the owner approves actual data.
## Verify the local rule first
Test the function body without starting a Runtime or registering a provider.
For this local check, copy the function into an isolated test module without
the `@node.register` decorator and without the module's imports, node creation,
or `node.serve()` call. Keep the body unchanged and record that this is only a
local calculation check, not an SDK or network test.
Check these exact cases:
```json
[
{"input": [{"customer": " North shop "}], "output": [{"customer": "North shop"}]},
{"input": [], "output": []},
{"input": [{"customer": " "}], "output": [{"customer": ""}]}
]
```
101 rows must raise `ValueError("Too many rows")`; a missing customer or a
numeric customer must raise `ValueError("customer must be text")`. Do not
turn these rejected inputs into successful empty results.
## Register on the provider
Identify the provider and consumer, their operators, and the approved test
scope. Read the installed CLI help and version-matched function connection
guide. Do not install, pair, restart shared services, change firewall rules,
or expose business data without the user's authorization.
In a compatible EasyRemote environment with the provider Runtime configured,
run `python3 provider.py`. Keep that process running. `@node.register` registers
the function as `sales.clean_names`; `node.serve()` starts its provider
lifecycle. Neither action grants a colleague access.
In a separate terminal, inspect:
```bash
easynet ability list --node local --format json
```
Find this provider's actual published ability and obtain its actual owner URA,
ability URA, descriptor reference and input schema. Verify the function name
and the `rows` argument. Do not treat `sales.clean_names`, a device nickname,
or a guessed hash as the callable address. Publication in the local catalogue
does not prove discovery or permission from the consumer.
## Call with the consumer's own permission
Use the actual published descriptor and the installed CLI or SDK's documented
invocation interface. Read its help rather than inventing flags. The invocation
argument object is:
```json
{"rows": [{"customer": " North shop "}]}
```
Obtain the owner's approval for this specific function and consumer through
the supported authorization workflow. Never borrow the provider's account,
copy private keys, mint an authority field, or weaken admission to make the
demo pass. If authorization cannot be established, stop this stage and report
the missing step. Do not substitute same-user execution for a claimed
different-colleague test.
Invoke once and verify the returned value is exactly
`[{"customer": "North shop"}]`, not merely a successful process exit. Send
records, not a local file path. The provider does not read the caller's disk.
The consumer needs its own compatible connection setup but not the provider's
business implementation. Do not retry an unknown outcome blindly.
For a remote consumer, the inspected development CLI supports the following
command after authorization. Confirm these flags in the installed
`easynet ability invoke --help` first. Set CLEAN_NAMES_DESCRIPTOR_REF to the
actual descriptor reference (`<ability-ura>@<version>`) from the catalogue and
PROVIDER_CALLEE_URA to its exact owner/callee URA. Neither variable is a label
or a permission grant. Generate a fresh nonce for this one request:
```bash
cleaning_nonce_hex=$(python3 -c 'import secrets; print(secrets.token_hex(16))')
easynet ability invoke "${CLEAN_NAMES_DESCRIPTOR_REF:?Set the actual descriptor reference first}" \
--node "${PROVIDER_CALLEE_URA:?Set the actual provider callee URA first}" \
--args '{"rows":[{"customer":" North shop "}]}' \
--nonce-hex "$cleaning_nonce_hex" --causal-root --timeout 30
```
This declares a root test invocation. Do not use `--causal-root` to conceal
an actual parent invocation inside an existing mission. If the installed
version rejects the interface or admission rejects the request, retain the
sanitized error and stop rather than dropping authorization-related options.
## Inspect the matching call and failures
On the provider, set CLEAN_NAMES_ABILITY_URA to the actual published value:
```bash
easynet invocation list --ability-ura "${CLEAN_NAMES_ABILITY_URA:?Set the actual published ability URA first}" --format json
```
This is a scoped history query, subject to the current identity's read
permissions. Match the invocation identifier, caller and terminal state to
the actual test. An empty history is not proof of success, and a history
record is not a network-byte measurement.
Separately test invalid input and an unapproved test identity. Test provider
unavailability only by stopping the demo process you started, with approval;
do not stop a shared Runtime. A rejected request must not be presented as a
successful cleaned table. A timeout does not prove cancellation.
Report local calculation, provider publication, authorized consumer result,
rejected consumer, invalid input and unavailable provider separately. Keep
sanitized identifiers and exact returned values, never credentials. Mark
unrun checks as unrun. Do not claim complete cross-user use from the website's
syntax, rendering or local-function tests.
接入时的检查项
- 调用方无需安装提供方的处理依赖即可得到预期结果。
- 非法输入在提供方被拒绝,而不是进入业务代码。
- 提供方停止后调用方得到明确失败,不误报完成。