A proxy for hiagent platform.
  • Rust 93.8%
  • Python 6.2%
Find a file
liquidhelium a82e49edf4 feat(proxy): glm-native 官方格式 + infogather 锚名可配 + 工具结果 recap
- protocol/glm.rs: 新增 glm-native 协议(官方 <tool_call>/<arg_key>/<arg_value>
  格式),与 glm-xml/semixml 并列;masker 适配新标签
- protocol/infogather.rs: 锚工作流名可配(with_anchor_tool),教学/清单/
  序列化全部用实例锚名;新增 custom_anchor_tool_renames_everywhere 测试
- config.rs/model.rs: ModelConfig 新增 anchor_tool 字段(空串回退 tool_call)
- prompt.rs + openai/{chat,responses}.rs: PromptMessage 新增 tool_name,
  tool 结果消息按 name 回填(OpenAI 规范 name 可选,客户端常不填),
  各 native flow 走 format_tool_result_named 输出调用回显
- dsml.rs/masker.rs/mod.rs: 配套调整与回归测试

实测:localhost8080/glm53-infogather(infogather + glm-native +
anchor_tool=tool_call_glm)端到端跑通子代理只读调研任务,挂起/恢复
链路正常。
2026-09-27 23:00:37 +08:00
scripts feat: add HIAGENT_PROXY_TRACE to dump built prompt + raw upstream answer 2026-08-30 13:24:54 +08:00
src feat(proxy): glm-native 官方格式 + infogather 锚名可配 + 工具结果 recap 2026-09-27 23:00:37 +08:00
.gitignore chore: 忽略 docs/(上游平台调研文档,不入库) 2026-09-25 18:40:55 +08:00
Cargo.lock hiagent-proxy: standalone OpenAI Responses/Chat-Completions proxy for Tongji HiAgent 2026-08-29 23:07:18 +08:00
Cargo.toml hiagent-proxy: standalone OpenAI Responses/Chat-Completions proxy for Tongji HiAgent 2026-08-29 23:07:18 +08:00
config.example.toml feat(proxy): glm-native 官方格式 + infogather 锚名可配 + 工具结果 recap 2026-09-27 23:00:37 +08:00
DEPLOY.md docs: add DEPLOY.md for service VM deployment 2026-08-29 23:20:29 +08:00
mock_hiagent.py feat(openai): 冒号收尾假意图自动重试(同会话 nudge,默认开) 2026-09-25 20:06:44 +08:00
probe_kimi.py test(proxy): Kimi 端到端管线测试 + 探测脚本增强(check/drift/swallow) 2026-09-13 19:18:53 +08:00
README.md feat(proxy): glm-native 官方格式 + infogather 锚名可配 + 工具结果 recap 2026-09-27 23:00:37 +08:00

hiagent-proxy

把同济 HiAgent(会话式智能体 API)适配为 OpenAI 兼容接口的独立 Rust 代理:客户端只见到 /v1/chat/completions、/v1/responses 与 /v1/models, 上游始终是 HiAgent 的 create_conversation / chat_query_v2(SSE 流式)会话 协议。

连接方式逐条照搬 dsh-llm-hiagent (原生 DSH LLM 适配器)的设计,但本代理是无状态、可单独部署的 HTTP 服务, 架构参考 NIyueeE/ds-free-api。

特性

  • OpenAI 双协议:/v1/chat/completions(含 reasoning_content、工具调用) 与 /v1/responses(物品模型:message / reasoning / function_call,含流式事件)
  • 一个模型 = 一个 API Key:HiAgent 每个 agent 有独立 key,配置里按模型 id 绑定(model_keys 字典 + models[].api_key 行内 + 顶层回退)
  • 六种工具协议:deepseek-dsml(DeepSeek 原生 DSML 块)、json-toolcall (<tool_calls>+JSON 数组,与模型无关,实测最稳)、glm-xml、glm-semixml (GLM XML 变体)、kimi-xtml、infogather——解析、历史序列化、流式 masker、 截断恢复全部照搬 dsh-llm-hiagent(含服务端随机吞掉块开头的 relaxed 恢复)。 2026-09 实测结论:HiAgent 推理网关会原生解析并消费模型自家工具标记 (规范 DSML 在 agent 未注册平台工具时直接变空回复+兜底文案),因此 DeepSeek 模型推荐改用 json-toolcall(端到端 8/8;DSML 依赖模型逐字模仿 U+2502 变体,偶发退化)。
  • infogather 协议(information_gathering 工作流):把工具调用包装成对平台 工作流 tool_call(参数名恒为 input,值为 {"tool":…,"args":…} JSON 字符串) 的一次调用,模型走平台原生格式发出,平台据此挂起(SSE interrupted)等待补充 信息,同会话下一条 chat_query 即恢复。它委托一个内层协议做语法: models[].format(缺省 deepseek-dsml)决定用哪种原生格式教学与解析, 于是 protocol = "infogather" + format = "deepseek-dsml" 构成一个整体协议。
  • 真流式:Accept: text/event-stream 解锁逐 token 增量;think_message 事件按类型路由为 reasoning(chat 的 reasoning_content / responses 的 reasoning 物品);DSML/XML 工具区实时遮蔽,结束做权威解析
  • 会话双模式:
    • no_reuse = true(默认):每次请求新建 HiAgent 会话,完整历史作为只读 上下文发入首条消息(anti-self-Q&A 标题),彻底绕开服务端 ~3 轮滑动窗口
    • no_reuse = false:复用会话,映射持久化到 hiagent-conversations.json
  • 容错:会话过期(400)自动重建重试一次;「失败兜底文案」(空回复)自动 换新会话重试一次(可关);空闲超时(默认 180s);token 计数来自 message_cost 事件
  • 下游鉴权:[server] api_keys(Bearer)与上游 key 完全分离;可选

复用模式如何适配 OpenAI 格式接口

复用机制原本为 DeepSeek Harness(DSH)的会话模型设计:DSH 提供 sessionId 并逐轮发送增量消息。OpenAI 接口是无状态的,代理做了两处适配:

  1. chat completions(无状态、每次重发全量历史):会话 key = 消息前缀 哈希(到首个 assistant 轮为止)。客户端把 API 返回的 assistant 消息 原样回传时,哈希稳定命中 → 复用同一 HiAgent 会话,只发增量(最后一条 assistant 之后的消息),模型靠 HiAgent 服务端会话记忆(约百轮)续上下文。 若客户端改写/截断了 assistant 历史,哈希不命中 → 自动退化为新会话全量 重发(语义仍正确,只是多花 token)。首轮(消息里还没有 assistant 轮)也 会落映射——用「请求消息 + 合成回复」计算 key,回复恰好补上第一个 assistant 轮。
  2. responses previous_response_id:这是 OpenAI 原生的会话链续机制, 按存储的会话 key 直接复用,与客户端回传什么无关(更稳)。实测跨轮记忆 正常。

会话身份隔离:DSH 用 sessionId 隔离会话(fork 铸造新 id,分支互不 串话);OpenAI 的等价钩子是请求里的可选字段 user。传了 user 的 客户端按 模型 + user 隔离会话 key——前缀相同的并行会话不会串到同一个 HiAgent 会话:

# 客户端 A / B 各自传自己的 user
curl ... -d '{"model":"hiagent-2","user":"session-A","messages":[...]}'

需要独立上下文的并行任务(agent 循环、多会话 UI)请务必传 user。

快速开始

# 复制示例配置并填入模型与 key
cp config.example.toml config.toml
# 运行
cargo run --release -- -c config.toml
# 或直接使用编译产物
cargo build --release && ./target/release/hiagent-proxy -c config.toml

首次运行(无 config.toml 且未指定 -c)会自动生成一份默认配置。 配置文件路径也可用环境变量 HIAGENT_PROXY_CONFIG 指定。

客户端接入(与 OpenAI SDK 相同):

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <下游 api key,未配置则省略>" \
  -d '{"model":"hiagent-2","messages":[{"role":"user","content":"你好"}]}'

/v1/responses(含 previous_response_id 链续)、/v1/models、 GET /v1/responses/{id} 同样可用。

配置

见 config.example.toml(每个字段都有注释)。要点:

[hiagent]
base_url = "https://agent.tongji.edu.cn/api/proxy/api/v1/"   # 必须以 / 结尾!

# 每个模型的独立 key(一个模型 = 一个 API key)
[hiagent.model_keys]
"hiagent-2" = "da0123456789abcdef012"   # 注意:含点的模型 id 必须加引号!
"glm-5.2"  = "da0123456789abcdef012"    # 否则 TOML 会把 5.2 解析成嵌套表

[[models]]
id = "hiagent-2"
name = "DeepSeek V4 Flash"
protocol = "deepseek-dsml"   # deepseek-dsml | glm-xml | glm-semixml
context_window = 128000
max_tokens = 4096

[[models]]
id = "glm-5.2"
name = "GLM-5.2"
protocol = "glm-xml"

[conversation]
no_reuse = true            # 默认:每次新建会话 + 完整历史
max_reuse_turns = 100      # no_reuse=false 时:轮换阈值
retry_empty_reply = true   # 空回复自动重试一次

配置项

字段 默认 说明
server.host / server.port 127.0.0.1 / 8080 监听地址
server.api_keys [] 下游 Bearer key 列表;空=开放
server.cors_origins ["*"] CORS 允许的 Origin
hiagent.base_url 同济网关 必须以 / 结尾(端点是拼接的)
hiagent.user_id default_user HiAgent 用户 ID
hiagent.api_key / api_key_env — 全局回退上游 key
hiagent.model_keys {} 按模型 id 的 key 字典(最高优先)
hiagent.model_id / display_name hiagent / HiAgent 未配置 models 时的默认模型
models[].api_key / api_key_env — 行内 key(低于 model_keys)
models[].protocol deepseek-dsml 工具协议(六种:deepseek-dsml / json-toolcall / glm-xml / glm-semixml / kimi-xtml / infogather);未知 id 回退 deepseek-dsml
models[].format "" 仅 protocol = "infogather" 用:内层原生格式 id(缺省 deepseek-dsml),与 protocol 共同构成整体协议
models[].context_window / max_tokens 128000 / 4096 元数据 + /v1/models 展示
models[].no_reuse / max_reuse_turns 继承顶层 逐模型覆盖会话行为
conversation.no_reuse true 每次新建会话(消除 ~3 轮窗口锁死)
conversation.max_reuse_turns 100 复用轮数上限(0=关闭轮换)
conversation.store_path hiagent-conversations.json 会话映射持久化(相对配置目录)
conversation.max_initial_* 120 / 100000 / 800000 首条提示防御上限(0=不限)
conversation.retry_empty_reply true 空回复自动重试一次
conversation.idle_timeout_secs 180 流式空闲超时

上游连接方式(与 dsh-llm-hiagent 一致)

  • POST {base}create_conversation,请求头 Apikey: <key>,body {UserID, Inputs: {var: "variable"}} → Conversation.AppConversationID
  • POST {base}chat_query_v2,请求头 Apikey + Accept: text/event-stream, body {UserID, AppConversationID, Query, ResponseMode: "streaming"} → Dify 风格 SSE
  • POST {base}delete_conversation:尽力而为的会话清理(轮换 / 过期 / noReuse 换新前)
  • SSE 事件按事件类型路由:think_message → 思考增量;message / agent_message / text 的 answer → 可见文本增量;message_cost → token 计数;message_replace → 重置累积;error → 流内错误

工具调用走模型原生格式(DSML 或 GLM XML),模型输出后由协议层解析为 OpenAI tool_calls;历史里的 assistant 工具调用会序列化为原生格式(DSML/ XML)回注提示词,让历史成为可模仿的教师样例。

已知坑

  1. base_url 必须以 / 结尾,否则端点拼接 404。
  2. model_keys 里含点的模型 id 必须加引号(如 "glm-5.2" = "..."), 否则 TOML 把 glm-5.2 当成嵌套表(dotted key)。
  3. 上游可能返回「失败兜底文案」(如 模型无法理解你的问题或回复内容为空): 代理检测到后自动换新会话重试一次,仍失败则以 empty_response 错误返回。 这是模型侧行为,代理只保证提示词完整(同 dsh-llm-hiagent 已知坑 #10/#13)。
  4. GLM 系 agent 需要合适的 agent 配置;若某 agent 对任何输入都回兜底文案, 是上游问题,与代理无关。
  5. n>1 不受支持(HiAgent 每次请求单回答);图片输入不支持(纯文本模型)。

测试

cargo test          # 单元测试:DSML/GLM 解析、masker、提示词构建、配置、SSE 帧
cargo clippy        # 无警告
cargo fmt --check

架构

src/
├── main.rs            # 入口:日志、配置加载、运行时
├── lib.rs             # 模块树 + 重导出
├── config.rs          # TOML 配置(models + model_keys + 会话行为)
├── error.rs           # ProxyError -> OpenAI 错误 JSON
├── model.rs           # 模型注册表:id -> key / 协议 / 限制
├── hiagent.rs         # HiAgent 客户端:会话创建/删除、chat_query_v2 SSE 解析
├── prompt.rs          # OpenAI 消息 -> HiAgent 文本(历史框架、anti-self-Q&A)
├── conv.rs            # 会话复用映射持久化
├── protocol/          # 工具协议:dsml.rs / glm.rs / masker.rs / common.rs
├── openai/            # types.rs(线上类型)、mod.rs(执行管线)、chat.rs、
│                      # responses.rs(+ ResponseStore)、sse.rs
└── server.rs          # axum 路由、下游鉴权、CORS、优雅退出

执行管线(openai/mod.rs,从 adapter.stream() 移植)对 chat 与 responses 共用:会话定位/创建 → 提示词构建 → SSE 流式消费(masker + think 路由)→ 权威解析 → 会话过期/空回复重试 → 复用映射持久化。