Repository navigation
feat(model): 支持 systemone model 调用 #372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Rook1ex
wants to merge
1
commit into
main
Choose a base branch
from
feature/jasindev_sysone
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| TYPESAFE_API_KEY= | ||
| TYPESAFE_BASE_URL= | ||
| TYPESAFE_DEFAULT_MODEL=jev-1.14.0 | ||
|
|
||
| LAYA_BASE_URL=http://127.0.0.1:8000 | ||
| LAYA_API_KEY= | ||
| LAYA_MODEL= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| # System One TypeSafe 与 Laya 示例 | ||
|
|
||
| 本示例用同一组客服工单问题,分别调用官方 TypeSafe API 和本地 Laya HTTP 服务。问题类型覆盖 `Choice`、`Score` 和 `Noul`。Laya 路径还会打印 checkpoint 路由,并对两条工单调用 `system_one_batch`。 | ||
|
|
||
| ## 关键特性 | ||
|
|
||
| - **同一组问题,两个客户端**:`SystemOneClient.typesafe` 调用官方 API,`SystemOneClient.laya` 调用 `laya-serve` | ||
| - **三种答案**:部门用 `Choice`,紧急程度用 `Score`,是否退款用 `Noul`,再用 `choice_answer`、`score_answer`、`noul_answer` 按问题名取出 | ||
| - **Laya 可省略鉴权和模型**:不设置 `LAYA_API_KEY` 时不发送 `Authorization`;不设置 `LAYA_MODEL` 时由服务端选择 checkpoint | ||
| - **Laya 批量**:`system_one_batch` 一次提交两条工单。官方后端不提供该接口,本示例只在 Laya 路径调用它 | ||
| - **缺配置即跳过**:`TYPESAFE_API_KEY` 为空时跳过 TypeSafe,`LAYA_BASE_URL` 为空时跳过 Laya,另一侧仍会运行 | ||
|
|
||
| ## 调用结构 | ||
|
|
||
| ```text | ||
| run.py | ||
| ├── questions: department (Choice), urgency (Score), refund (Noul) | ||
| ├── typesafe: SystemOneClient.typesafe -> system_one | ||
| └── laya: SystemOneClient.laya -> system_one, system_one_batch | ||
| └── response.routing | ||
| ``` | ||
|
|
||
| 关键文件: | ||
|
|
||
| - [examples/systemone_model/config.py](./config.py):读取两个客户端的环境变量 | ||
| - [examples/systemone_model/run.py](./run.py):构造问题、调用客户端并打印答案 | ||
| - [examples/systemone_model/.env](./.env):环境变量占位 | ||
|
|
||
| ## 关键代码解释 | ||
|
|
||
| ### 1) 官方 TypeSafe 客户端(`run.py`) | ||
|
|
||
| - `SystemOneClient.typesafe(api_key=..., base_url=..., default_model=...)` 创建客户端 | ||
| - `api_key` 来自 `TYPESAFE_API_KEY`。`base_url` 和 `default_model` 留空时,由官方 SDK 使用它自己的默认地址和模型 `jev-latest` | ||
| - 用 `async with` 保证客户端关闭 | ||
| - 对一条工单调用 `system_one` | ||
|
|
||
| ### 2) Laya 客户端(`run.py`) | ||
|
|
||
| - `SystemOneClient.laya(base_url, api_key=..., default_model=...)` 创建客户端 | ||
| - `base_url` 是服务根地址,例如 `http://127.0.0.1:8000` | ||
| - 单条结果若带 `routing`,打印 checkpoint 名称和仓库 | ||
| - 随后用同一组问题调用 `system_one_batch` | ||
|
|
||
| ### 3) 读取答案(`run.py`) | ||
|
|
||
| - `choice_answer(response, "department").choice` 是选中的部门标签 | ||
| - `score_answer(response, "urgency").score` 是紧急程度分数 | ||
| - `noul_answer(response, "refund").noul` 是是否退款的 0/1 答案 | ||
|
|
||
| ## 环境要求 | ||
|
|
||
| - Python3.10+,推荐 Python3.12 | ||
| - TypeSafe 路径需要可访问官方 API 的 key | ||
| - Laya 路径需要已启动的 `laya-serve`。安装项目时已包含 `typesafe-sdk` | ||
|
|
||
| ## 构建步骤 | ||
|
|
||
| ```bash | ||
| git clone https://github.com/trpc-group/trpc-agent-python.git | ||
| cd trpc-agent-python | ||
| ./build.sh | ||
| source .venv/bin/activate | ||
| ``` | ||
|
|
||
| ## 运行步骤 | ||
|
|
||
| ### 配置环境变量 | ||
|
|
||
| 在 [examples/systemone_model/.env](./.env) 中配置(或通过 `export`): | ||
|
|
||
| | 变量 | 必填 | 说明 | | ||
| |------|------|------| | ||
| | `TYPESAFE_API_KEY` | TypeSafe 必填 | 官方 API key。为空时跳过 TypeSafe | | ||
| | `TYPESAFE_BASE_URL` | 否 | 官方 API 地址。为空时使用 SDK 默认地址 | | ||
| | `TYPESAFE_DEFAULT_MODEL` | 否 | 默认模型。为空时使用 `jev-latest` | | ||
| | `LAYA_BASE_URL` | Laya 必填 | Laya 服务根地址,例如 `http://127.0.0.1:8000` | | ||
| | `LAYA_API_KEY` | 否 | 为空时不发送 `Authorization` | | ||
| | `LAYA_MODEL` | 否 | 为空时由 Laya 选择 checkpoint | | ||
|
|
||
| ### 运行命令 | ||
|
|
||
| ```bash | ||
| cd examples/systemone_model | ||
| python3 run.py | ||
| ``` | ||
|
|
||
| ## 运行结果(示例) | ||
|
|
||
| 下面是一次成功调用时的输出形态。部门标签、分数和 `noul` 的具体取值由服务端模型决定。 | ||
|
|
||
| ```text | ||
| ============================================================ | ||
| Backend: typesafe | ||
| ============================================================ | ||
| state: We were billed twice. Please refund the duplicate. | ||
| department: billing | ||
| urgency: 1.4 | ||
| refund: 1 | ||
| model: jev-latest | ||
| usage: input_tokens=120 output_tokens=0 | ||
| ---------------------------------------- | ||
| ============================================================ | ||
| Backend: laya | ||
| ============================================================ | ||
| state: We were billed twice. Please refund the duplicate. | ||
| department: billing | ||
| urgency: 1.7 | ||
| refund: 1 | ||
| model: laya-rl-agent | ||
| usage: input_tokens=300 output_tokens=0 | ||
| routing: model=multilingual repo=convaiinnovations/laya-multilingual | ||
| --- batch --- | ||
| [1/2] | ||
| state: We were billed twice. Please refund the duplicate. | ||
| department: billing | ||
| urgency: 1.7 | ||
| refund: 1 | ||
| model: laya-rl-agent | ||
| usage: input_tokens=300 output_tokens=0 | ||
| routing: model=multilingual repo=convaiinnovations/laya-multilingual | ||
| [2/2] | ||
| state: The dashboard has been down since this morning. | ||
| department: technical | ||
| urgency: 2.0 | ||
| refund: 0 | ||
| model: laya-rl-agent | ||
| usage: input_tokens=280 output_tokens=0 | ||
| routing: model=english repo=convaiinnovations/laya-english | ||
| ---------------------------------------- | ||
| ``` | ||
|
|
||
| ## 结果分析(是否符合要求) | ||
|
|
||
| 跑通后可以核对这几件事: | ||
|
|
||
| - **两个客户端都能回答同一组问题**:TypeSafe 与 Laya 都打印 `department`、`urgency`、`refund` | ||
| - **答案类型正确**:部门是 `criteria` 里的标签,紧急程度是分数,退款是 `noul` | ||
| - **Laya 带回路由**:`routing` 给出 checkpoint 名称;服务端报告仓库时一并打印 | ||
| - **批量条数一致**:`system_one_batch` 的两条结果与提交的两条工单一一对应 | ||
| - **缺配置不会拖垮另一条路径**:只配置其中一个后端时,另一条打印 `[SKIP]` | ||
|
|
||
| ## 适用场景建议 | ||
|
|
||
| - 验证 System One 问题构造,以及 TypeSafe / Laya 两个客户端的单次调用:适合使用本示例 | ||
| - 验证 Laya 的 checkpoint 路由和 `system_one_batch`:适合使用本示例 | ||
| - 需要把分类结果接到 `LlmAgent` 工具循环里:本示例只覆盖客户端调用,可把 `run.py` 里的问题构造和答案读取搬进自己的工具函数 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # Tencent is pleased to support the open source community by making tRPC-Agent-Python available. | ||
| # | ||
| # Copyright (C) 2026 Tencent. All rights reserved. | ||
| # | ||
| # tRPC-Agent-Python is licensed under Apache-2.0. | ||
| """Read System One client settings from the environment.""" | ||
|
|
||
| import os | ||
| from dataclasses import dataclass | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class TypeSafeSettings: | ||
| """Settings for ``SystemOneClient.typesafe``.""" | ||
|
|
||
| api_key: str | ||
| base_url: str | None | ||
| default_model: str | None | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class LayaSettings: | ||
| """Settings for ``SystemOneClient.laya``.""" | ||
|
|
||
| base_url: str | ||
| api_key: str | None | ||
| default_model: str | None | ||
|
|
||
|
|
||
| def typesafe_settings() -> TypeSafeSettings | None: | ||
| """Return TypeSafe settings, or ``None`` when ``TYPESAFE_API_KEY`` is empty.""" | ||
| api_key = os.getenv("TYPESAFE_API_KEY", "").strip() | ||
| if not api_key: | ||
| return None | ||
| return TypeSafeSettings( | ||
| api_key=api_key, | ||
| base_url=_optional("TYPESAFE_BASE_URL"), | ||
| default_model=_optional("TYPESAFE_DEFAULT_MODEL"), | ||
| ) | ||
|
|
||
|
|
||
| def laya_settings() -> LayaSettings | None: | ||
| """Return Laya settings, or ``None`` when ``LAYA_BASE_URL`` is empty.""" | ||
| base_url = os.getenv("LAYA_BASE_URL", "").strip() | ||
| if not base_url: | ||
| return None | ||
| return LayaSettings( | ||
| base_url=base_url, | ||
| api_key=_optional("LAYA_API_KEY"), | ||
| default_model=_optional("LAYA_MODEL"), | ||
| ) | ||
|
|
||
|
|
||
| def _optional(name: str) -> str | None: | ||
| value = os.getenv(name, "").strip() | ||
| return value or None |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| # Tencent is pleased to support the open source community by making tRPC-Agent-Python available. | ||
| # | ||
| # Copyright (C) 2026 Tencent. All rights reserved. | ||
| # | ||
| # tRPC-Agent-Python is licensed under Apache-2.0. | ||
| """Classify the same support ticket with the TypeSafe API and with Laya.""" | ||
|
|
||
| import asyncio | ||
|
|
||
| from dotenv import load_dotenv | ||
| from trpc_agent_sdk.models.systemone import Choice | ||
| from trpc_agent_sdk.models.systemone import LayaSystemOneResponse | ||
| from trpc_agent_sdk.models.systemone import Noul | ||
| from trpc_agent_sdk.models.systemone import Score | ||
| from trpc_agent_sdk.models.systemone import SystemOneClient | ||
| from trpc_agent_sdk.models.systemone import SystemOneResponse | ||
| from trpc_agent_sdk.models.systemone import choice_answer | ||
| from trpc_agent_sdk.models.systemone import noul_answer | ||
| from trpc_agent_sdk.models.systemone import score_answer | ||
|
|
||
| from config import LayaSettings | ||
| from config import TypeSafeSettings | ||
| from config import laya_settings | ||
| from config import typesafe_settings | ||
|
|
||
| load_dotenv() | ||
|
|
||
| QUESTIONS = { | ||
| "department": Choice( | ||
| instructions="Which team should handle this?", | ||
| criteria={ | ||
| "billing": "invoices, payments, refunds", | ||
| "technical": "bugs, outages", | ||
| "other": "everything else", | ||
| }, | ||
| ), | ||
| "urgency": Score( | ||
| instructions="How urgent is this?", | ||
| criteria=["not urgent", "soon", "blocking"], | ||
| ), | ||
| "refund": Noul(instructions="Does the user request a refund?"), | ||
| } | ||
|
|
||
| TICKET = {"body": "We were billed twice. Please refund the duplicate."} | ||
| BATCH_TICKETS = [ | ||
| TICKET, | ||
| {"body": "The dashboard has been down since this morning."}, | ||
| ] | ||
|
|
||
|
|
||
| def print_answers(response: SystemOneResponse) -> None: | ||
| """Print the three ticket answers plus model, usage, and Laya routing.""" | ||
| print(f"department: {choice_answer(response, 'department').choice}") | ||
| print(f"urgency: {score_answer(response, 'urgency').score}") | ||
| print(f"refund: {noul_answer(response, 'refund').noul}") | ||
| print(f"model: {response.model}") | ||
| usage = response.usage | ||
| print(f"usage: input_tokens={usage.input_tokens} output_tokens={usage.output_tokens}") | ||
| if isinstance(response, LayaSystemOneResponse) and response.routing is not None: | ||
| routing = response.routing | ||
| repo = f" repo={routing.repo}" if routing.repo else "" | ||
| print(f"routing: model={routing.model}{repo}") | ||
|
|
||
|
|
||
| def print_state(state: dict[str, str]) -> None: | ||
| """Print the ticket text that was sent as ``state``.""" | ||
| print(f"state: {state['body']}") | ||
|
|
||
|
|
||
| async def run_typesafe(settings: TypeSafeSettings) -> None: | ||
| """Evaluate one ticket on the official TypeSafe API.""" | ||
| async with SystemOneClient.typesafe( | ||
| api_key=settings.api_key, | ||
| base_url=settings.base_url, | ||
| default_model=settings.default_model, | ||
| ) as client: | ||
| response = await client.system_one(TICKET, QUESTIONS) | ||
| print_state(TICKET) | ||
| print_answers(response) | ||
|
|
||
|
|
||
| async def run_laya(settings: LayaSettings) -> None: | ||
| """Evaluate one ticket, then a two-ticket batch, on a Laya server.""" | ||
| async with SystemOneClient.laya( | ||
| settings.base_url, | ||
| api_key=settings.api_key, | ||
| default_model=settings.default_model, | ||
| ) as client: | ||
| response = await client.system_one(TICKET, QUESTIONS) | ||
| batch = await client.system_one_batch(BATCH_TICKETS, QUESTIONS) | ||
| print_state(TICKET) | ||
| print_answers(response) | ||
| print("--- batch ---") | ||
| for index, (state, item) in enumerate(zip(BATCH_TICKETS, batch), start=1): | ||
| print(f"[{index}/{len(BATCH_TICKETS)}]") | ||
| print_state(state) | ||
| print_answers(item) | ||
|
|
||
|
|
||
| async def _run_typesafe() -> bool: | ||
| """Run TypeSafe when ``TYPESAFE_API_KEY`` is set.""" | ||
| settings = typesafe_settings() | ||
| if settings is None: | ||
| print("[SKIP] TYPESAFE_API_KEY is not set") | ||
| return False | ||
| await run_typesafe(settings) | ||
| return True | ||
|
|
||
|
|
||
| async def _run_laya() -> bool: | ||
| """Run Laya when ``LAYA_BASE_URL`` is set.""" | ||
| settings = laya_settings() | ||
| if settings is None: | ||
| print("[SKIP] LAYA_BASE_URL is not set") | ||
| return False | ||
| await run_laya(settings) | ||
| return True | ||
|
|
||
|
|
||
| async def main() -> None: | ||
| """Run TypeSafe and Laya, skipping a client whose required setting is missing.""" | ||
| attempted = 0 | ||
| succeeded = 0 | ||
| for label, runner in (("typesafe", _run_typesafe), ("laya", _run_laya)): | ||
| print("=" * 60) | ||
| print(f"Backend: {label}") | ||
| print("=" * 60) | ||
| try: | ||
| sent = await runner() | ||
| except Exception as exc: | ||
| print(f"[FAIL] {exc}") | ||
| attempted += 1 | ||
| continue | ||
| if not sent: | ||
| continue | ||
| attempted += 1 | ||
| succeeded += 1 | ||
| print("-" * 40) | ||
| if attempted == 0: | ||
| raise SystemExit("No backend ran. Set TYPESAFE_API_KEY and/or LAYA_BASE_URL.") | ||
| if succeeded == 0: | ||
| raise SystemExit(1) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,3 +56,6 @@ aiofiles | |
| mem0ai>=1.0.3 | ||
| fastapi | ||
| mempalace==3.3.4 | ||
|
|
||
| # Test Typesafe(Jev) | ||
| typesafe-sdk>=0.7.5 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Tencent is pleased to support the open source community by making tRPC-Agent-Python available. | ||
| # | ||
| # Copyright (C) 2026 Tencent. All rights reserved. | ||
| # | ||
| # tRPC-Agent-Python is licensed under Apache-2.0. | ||
| """Unit tests for the System One clients.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Tencent is pleased to support the open source community by making tRPC-Agent-Python available. | ||
| # | ||
| # Copyright (C) 2026 Tencent. All rights reserved. | ||
| # | ||
| # tRPC-Agent-Python is licensed under Apache-2.0. | ||
| """Unit tests for the TypeSafe and Laya System One clients.""" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
问题: README 第 55 行称「安装项目时已包含
typesafe-sdk」,但typesafe-sdk只在[typesafe]和[all]extra 中(pyproject.toml),普通pip install trpc-agent-py并装入examples/systemone_model/运行python3 run.py时不会安装它;该表述只在走./build.sh(会装 requirements-test.txt)时成立。触发条件: 按 README 步骤克隆、
pip install trpc-agent-py(不带 extra)、配置好环境变量后直接python3 run.py。实际影响: 示例在 import 阶段因
ModuleNotFoundError: typesafe_sdk崩溃且无指引(与__init__.py裸导入无提示叠加),用户无法从文档判断需要安装[typesafe]extra,与 pyproject 把 typesafe-sdk 设计为可选依赖的意图矛盾。修正方向: 将第 55 行改为明确「需要执行
pip install 'trpc-agent-py[typesafe]'或通过./build.sh安装测试依赖」,与 pyproject 的 extra 语义保持一致。