easynet.run · 全部问题场景

每做一个新 Agent,团队又重写一套接入和授权代码。

售前助手要查订单,客服助手也要查,分析助手还要按月份汇总。三个项目各自接了一遍订单系统。后来接口改了一个字段,三组人分别修复;更麻烦的是,同样叫“订单金额”,有的返回含税价,有的已经扣除了退款。

这部分工作并不需要每个 Agent 重新实现。先对照现有代码,找出真正相同的一项操作,例如按订单编号返回订单状态和允许查看的金额。汇总分析需要另一种输入和权限,不能仅因为都涉及订单,就塞进一个什么都能查的工具。

如果已有内部 API 或 SDK 已经统一了这些规则,继续用它。需要补上的可能只是让不同助手找到并使用这个入口,而不是再写一个订单服务。EasyNet 的用途在于把这种已维护的操作接给不同调用方,不是替换团队已有的业务系统。

对于仍散落在脚本里的实现,可以由订单系统的维护者选定一个函数,通过 EasyRemote 接入;已有服务则保留原有接口和维护方式。函数描述要说清输入、金额口径、失败原因和负责人,再为需要使用它的助手配置支持的调用入口,例如 MCP。

共享实现不能变成共享超级账号。客服能查哪些客户,分析助手能取哪些字段,仍要由提供方依据可信的调用身份检查。助手在参数里声称“我是管理员”不构成授权,工具说明写了“只读”也不能代替底层账号和业务代码的限制。

先让两个现有项目使用同一个查询,分别测试正常订单、无权查看的订单和业务接口不可用的情况。也要检查助手是否理解结果:接口返回的是含税总额,就不该在回答里说成已到账收入。运行结果一致与业务解释正确,需要分别验证。

如果下次接口变化只需维护者修一次,两个项目仍能得到同样含义的结果,复用才真正成立。团队少维护的是重复的业务连接和规则;每个助手仍需完成自己的连接配置、身份授权和任务测试。

  1. 01

    固定业务含义

    金额是否含税、状态来自何处。

  2. 02

    连接维护好的函数

    实现与上游凭证由提供方维护。

  3. 03

    两个项目复用

    同一语义,不同授权范围。

订单逻辑维护一处,消费者保留各自权限。

同一个订单查询,销售与分析拿到不同字段

运行 python3 -B orders.py。销售能看 DEMO-42 的状态和配送备注,分析只能看订单号、金额与口径;销售无权读取 DEMO-43。两个调用方复用同一查询实现。这是提供方编写的业务权限示例,不是 EasyNet 自动生成的字段权限。

本地示例 · python
"""One query implementation, two synthetic provider-controlled access scopes."""
from copy import deepcopy
from dataclasses import dataclass


@dataclass(frozen=True)
class Access:
    order_ids: frozenset[str]
    fields: frozenset[str]


class OrderService:
    def __init__(self, orders, grants):
        self._orders = deepcopy(orders)
        self._grants = dict(grants)
        self.reads = []  # Test observation, not a production audit ledger.

    def get_order(self, caller: str, order_id: str) -> dict:
        access = self._grants.get(caller)
        if access is None or order_id not in access.order_ids:
            raise PermissionError("order not authorized")
        # Check business scope before looking up the record.
        self.reads.append(order_id)
        order = self._orders[order_id]
        return deepcopy({key: value for key, value in order.items() if key in access.fields})


def demo_service() -> OrderService:
    return OrderService(
        {
            "DEMO-42": {
                "order_id": "DEMO-42", "status": "shipped",
                "delivery_note": "Synthetic: reception desk",
                "total": {"amount_cents": 10800, "currency": "USD", "tax_included": True},
                "metric": "order_total_not_booked_revenue",
            },
            "DEMO-43": {
                "order_id": "DEMO-43", "status": "processing",
                "total": {"amount_cents": 5400, "currency": "USD", "tax_included": True},
                "metric": "order_total_not_booked_revenue",
            },
        },
        {
            "demo-sales": Access(frozenset({"DEMO-42"}), frozenset({"order_id", "status", "delivery_note", "total", "metric"})),
            "demo-analysis": Access(frozenset({"DEMO-42", "DEMO-43"}), frozenset({"order_id", "total", "metric"})),
        },
    )


if __name__ == "__main__":
    import json
    service = demo_service()
    for caller in ("demo-sales", "demo-analysis"):
        print(json.dumps({"caller": caller, "result": service.get_order(caller, "DEMO-42")}, ensure_ascii=False))

预期结果:两份结果都注明含税订单总额 10800 美分,不是确认收入;分析结果没有配送备注。口径写清楚了,助手是否解释正确仍要核对。

原查询函数只注册一次

提供方配置好 OrderService 后创建 node;@node.register 发布入口,Context 注入调用者。模型只传 order_id,不能自行选择销售身份。此处是注册适配器,未启动 Runtime。

本地示例 · python
"""Use real owner-controlled grants when creating this node, not demo callers."""
from easyremote import ComputeNode, Context
from orders import OrderService


def create_node(service: OrderService) -> ComputeNode:
    node = ComputeNode(namespace="orders")

    @node.register
    def get_order(ctx: Context, order_id: str) -> dict:
        """Read an authorized order; returned fields depend on caller scope."""
        return service.get_order(ctx.caller, order_id)

    return node

预期结果:注册接入与订单、字段权限分开负责;本地测试中的身份字符串不是认证凭证。

运行两个调用方的对照测试

英文 AGENTS.md 附五项测试:共同口径、字段差异、未知调用者、订单越权与返回值隔离。只用合成订单,不连接业务数据库。

阅读完整交接任务
# Reuse one order query without sharing every caller's access

The example has two fictional orders and two synthetic callers. Run locally;
do not connect to an order database, publish a provider, or use customer data.

Download orders.py, test_orders.py and provider.py from this attachment's
directory using the user's supplied page origin. Do not overwrite existing
work. Run `python3 -B -m unittest -v test_orders.py` and `python3 -B orders.py`.

Compare DEMO-42: both outputs name the same USD 108.00 tax-included order total,
explicitly not booked revenue. Sales receives status and delivery_note; analysis
does not. Both call the same implementation. The five tests also check unknown
callers, order scope before reading, an unknown order, and response isolation.
These values are fixture data, not real orders or claimed business outcomes.

Explain the two layers:

1. EasyRemote registers the function using @node.register. Context supplies the
   admitted caller to the function; callers do not submit their identity as an
   order query argument. Runtime admission is not tested by the local fixture.
2. The provider's OrderService decides which orders and fields that caller can
   read. This mapping is custom business policy in this example, not an EasyNet
   built-in field-permission feature or a Realm/User/Agent management screen.

Field scope here is top-level: permitting total permits its full nested object.
If the real schema adds confidential nested fields, define and test a narrower
projection before connecting it; do not treat this fixture as recursive masking.

The demo strings are not credentials or real URAs. For an approved integration,
obtain actual caller identities and approved business scopes from the owner,
replace the fixture repository with an authorized adapter, and configure the
service server-side before create_node(service).serve(). Never make the grants
editable by the model or borrow the provider's identity to pass a denied call.
Do not assume an agent's caller proves which human employee requested the work.

Keep existing database and API authorization. If a shared HTTP or MCP service
already exposes this query with the right access checks, reuse it; the reason
to add EasyRemote is function-level registration in the existing environment,
not a claim that other frameworks cannot share business code. A second device
is unnecessary for these local tests.

Before claiming remote success, separately verify installed-version publication,
the actual invocation route, distinct admitted identities, permitted responses,
denied orders and an inaccessible field. Merely registering the function or
constructing two caller strings does not prove those steps. Do not claim the
amount label guarantees an assistant will interpret the metric correctly.

Report local code checks, Runtime admission, business authorization and actual
consumer execution separately. No production deployment is requested by this
attachment.
接入时的检查项
  • 两个消费者调用同一业务实现并获得一致语义。
  • 权限不同的消费者返回范围确实不同。
  • 业务接口变更能通过共同契约测试被发现。
源码与接入资料