Span
Span 在 state 的文本里找出所有符合描述的片段,返回每一段的位置。例如找出日志里的凭据和密钥、工单里的姓名和地址、合同里的违约条款,再在代码里打码、提取或高亮。它是象信 3.0 的第五种问题类型,可以与 Choice、Score、Noul、Box 放在同一个请求中。
正则表达式只能找有固定格式的文本。Span 结合上下文判断,例如“Token:”后面的串是凭据,而同一段里的任务 ID 虽然看起来也是一串随机字符,却不算凭据。
定义问题
Span 只需要 type: "span" 和 instructions,不接受 criteria。instructions 写清要找什么;需要排除的情况也写进去。
json
{
"secrets": {"type": "span", "instructions": "所有凭据(token、密码、私钥),不含示例值和占位符"},
"people": {"type": "span", "instructions": "所有人名"}
}使用 model: "xiangxin-3.0"。Span 只在 state 的字符串值里找,不会返回 JSON 的键名,也不会返回 instructions 或图片里的内容。
返回位置
假设 state 是:
json
{
"log": "生产环境地址:http://10.1.2.3/api/v1,Token:vw_0000aaaa1111bbbb。cron job id 4cc321f271f3。",
"owner": "负责人张伟,住北京市海淀区中关村大街 27 号。"
}返回:
json
{
"model": "xiangxin-3.8",
"answers": {
"secrets": {
"type": "span",
"spans": [
{"path": "/log", "start": 36, "end": 55, "text": "vw_0000aaaa1111bbbb", "confidence": 0.97}
],
"confidence": 0.98
},
"people": {
"type": "span",
"spans": [
{"path": "/owner", "start": 3, "end": 5, "text": "张伟", "confidence": 0.99}
],
"confidence": 0.99
}
},
"usage": {"input_tokens": 402, "output_tokens": 0}
}spans 按位置先后排列,每一项都包含以下字段:
| 字段 | 含义 |
|---|---|
path | 片段所在的字符串在 state 中的位置,写法是 JSON Pointer:/log、/messages/3/content。state 本身是字符串时为 "" |
start、end | 片段在该字符串里的起止位置,按 Unicode 码点计数,左闭右开,即 Python 的 s[start:end] |
text | 片段原文,等于该字符串的 [start, end) 切片 |
confidence | 0–1,模型对这一段的把握 |
外层 confidence 是 state 里存在符合描述的文本的概率。没有找到时 spans 是空列表,与 Box 不同,不需要另配一题 Noul。
打码与提取
python
import json
state = {...} # 发送的 state
answer = response.json()["answers"]["secrets"]
def get(obj, path):
for part in path.split("/")[1:]:
part = part.replace("~1", "/").replace("~0", "~")
obj = obj[int(part)] if isinstance(obj, list) else obj[part]
return obj
# 同一字符串里从后往前替换,前面片段的位置不会被改动
by_path = {}
for s in answer["spans"]:
if s["confidence"] >= 0.5:
by_path.setdefault(s["path"], []).append(s)
for path, spans in by_path.items():
text = get(state, path)
for s in sorted(spans, key=lambda s: s["start"], reverse=True):
text = text[: s["start"]] + "*" * (s["end"] - s["start"]) + text[s["end"] :]
print(path, text)JavaScript 字符串按 UTF-16 计数,emoji 等字符占两个单位。请先转成码点数组再切片:
js
const chars = Array.from(text); // 按码点切开
const piece = chars.slice(span.start, span.end).join("");置信度与阈值
- 每一段的
confidence用来决定是否采用这一段。打码这类宁可多遮的场景,阈值可以低一些;提取后要入库的场景,阈值可以高一些。阈值请按自己的样本定,不要直接套用 Choice 或 Score 的概率公式。 - 外层
confidence适合做请求级的判断,例如是否需要人工复核。
使用建议
- 一题找一类目标。 凭据、人名、地址各写一题 Span。同一请求的多个问题共享 state,计费时 state 只算一次。
- 把边界写清楚。 如“地址(到门牌号为止,不含邮编)”、“完整的 API key,不含前面的
Token:”。 - 边界可能差一两个字符。 片段的起止按模型的分词单位对齐,少数情况会多带或少带一两个字符。打码时可以向两侧各扩一个字符。
- 长文本可以直接放进 state。 单个请求最长 64k token,agent 的执行记录、长日志都可以整段传入,无需自己切块。
- 要判断,不只是要位置时,搭配其他问题。 例如用 Noul 问“是否包含生产环境凭据”,用 Span 找出位置;两题在一次请求中独立求值。

