原语(问题)
写程序时,我们用整数、布尔值、枚举这些基本类型拼出复杂逻辑。象信把"判断"也做成了这样的基本单元,称为原语。每个原语由一对东西组成:
象信一号一共支持三种问题类型:
| 类型 | 回答的问题 | 返回字段 |
|---|---|---|
| Choice 选择题 | 这是哪一个选项? | choice、probabilities、confidence |
| Score 评分 | 落在哪一档? | score、legend、probabilities、confidence |
| Noul 是非题 | 这件事成立吗? | noul(0 到 1) |
一次请求可以只问一个问题,也可以一口气问几十个。同一请求里的所有问题看到的是同一份 state,彼此独立求值,答案按你起的 ID 返回。
每个问题只问一个"直觉判断"
系统一模型擅长的是快速、聚焦的判断:一个懂行的人看一眼材料、一两秒内就能给出的结论。
- 好问题:"这条消息是否表达了着急?"
- 坏问题:"分析这条消息,给出最佳处理方案。"
第二个问题需要多步推理和权衡,这正是信号:应该把它拆成若干个小问题,再在代码里把答案组合起来。
比如评估一份商业计划书,不要问"这份计划书好不好",而是分别问市场空间、技术可行性、差异化程度,然后在代码里按你的权重加权。业务重点变了,改一个系数就行,不用重写提示词。详见 组合评分。
定义一个问题
每个问题由四部分组成:
- ID:你自己起的键名,比如
wants_refund。答案会以同样的键返回。ID 不会发送给模型,只给你的代码用。 type:choice、score或noul之一。instructions:你要问的问题本身,判断逻辑都写在这里。可以写成疑问句,也可以写成一个陈述让模型判断真假。大多数情况一个字符串就够;也可以是对象或数组,把问题和它引用的数据分开放,见 进阶:结构化。criteria:答案空间。Choice 是"选项 → 描述"的映射;Score 是从低到高的档位列表;Noul 可选地给出"是"和"否"各指什么。
下面这个问题判断顾客是否要求退款:
from xiangxin import Noul
questions = {
"wants_refund": Noul(
instructions="顾客是否要求退款?",
),
}提示
因为 ID 不会给模型看,所以哪怕 ID 已经"不言自明",也要把完整的问题写进 instructions。wants_refund 这个名字对模型毫无意义。
选择问题类型
按你需要的答案"形状"来选:
- Choice:答案是一组已知选项之一,选项之间没有高低顺序。例如工单分给哪个部门、商品属于哪个类目、一段代码是什么编程语言。把选项列全,如果可能覆盖不到所有输入,就加一个"其他"或"都不是"。
- Score:答案落在一条可以分档描述的刻度上。例如 bug 严重程度、顾客的不满程度、候选人的某项技能熟练度。档位由你定义,模型返回它在刻度上的位置。
- Noul:一个干净的是/否问题,而且"是的概率"本身就有用。例如消息是否包含手机号、顾客是否要求退款、简历是否提到分布式系统经验。
注意 Noul 不是刻度
"这位候选人的 Python 强吗?"如果问成 Noul,0.5 的意思是"强"和"不强"的可能性各半,不是"中等水平"。而且"强"没有定义,这个概率很难解读。
想衡量熟练度,就用 Score,并写清每一档:没接触过 / 了解过 / 日常使用 / 深度专家。想要一个是非判断,就把条件写死,例如"简历是否写明候选人在工作中使用过 Python?"
如果两种类型都说得通,选你的代码能直接用上的那种:在"退款 / 改签 / 咨询"三者之间的 Choice 正好对应三条代码分支;不满程度的 Score 对应一个阈值;Noul 对应一个 if。
返回什么
答案也是原语:每种问题返回一个带类型的值,你可以比较、设阈值、排序,传给后续逻辑,或者放进下一次请求的 state。
| 类型 | 答案字段 | 怎么读 |
|---|---|---|
| Choice | choice、probabilities、confidence | choice 是概率最高的选项;probabilities 是所有选项上的分布;confidence 概括分布有多"尖"。 |
| Score | score、legend、probabilities、confidence | score 是在档位刻度上的位置,可以落在两档之间;legend 把档位编号映射回描述;probabilities 是各档的分布。 |
| Noul | noul | "是"的概率。接近 1 是强烈的"是",接近 0 是强烈的"否",0.5 附近表示拿不准。Noul 没有单独的 confidence。 |
这些答案之所以好组合,是因为两条性质:
- 答案永远落在你给的选项里。 模型输出的是你定义的选项或档位上的概率分布,不会冒出列表外的值,你的代码不必从一段文字里"抠"答案。
- 答案彼此独立。 一个问题的答案不会成为另一个问题的隐藏上下文。增加或删除问题,不会改变其他问题的结果。
confidence 如何从 probabilities 算出,以及怎样用它决定"自动执行"还是"转人工",见 置信度。
引用 state 中的具体字段
state 往往是一个包含多个部分的 JSON 对象:一段对话、一条订单记录、一份规则。当问题只针对其中某一部分时,在 instructions 里用反引号写出它的路径(点号加下标),模型就知道该看哪一块。
以一个外卖平台的售后场景为例:
{
"ticket": {
"channel": "App 在线客服",
"messages": [
{"from": "customer", "text": "我点的酸菜鱼少送了一份米饭,汤也洒了一半,要求退款。"},
{"from": "agent", "text": "非常抱歉,我们正在联系商家核实。"}
]
},
"order": {
"id": "WM20260918-7731",
"items": [
{"name": "酸菜鱼", "price_yuan": 58},
{"name": "米饭", "qty": 2, "price_yuan": 4}
]
},
"policy": "餐品漏送可退对应金额;餐品洒漏超过一半可全额退款。"
}下面两个问题通过路径分别指向顾客消息、规则和订单:
questions = {
"wants_refund": {
"type": "noul",
"instructions": "`ticket.messages[0].text` 是否要求退款?",
},
"policy_allows_full_refund": {
"type": "noul",
"instructions": (
"根据 `policy`,`ticket.messages[0].text` 描述的情况"
"是否满足全额退款条件?"
),
},
}显式路径让每个判断该依据 state 的哪一部分一目了然。如何组织 state,见 状态。
一次请求问多个问题
凡是基于同一份 state 的问题,都放进同一个请求里,三种类型可以随意混搭。模型会并行评估所有问题:state 只处理一次,每多一个问题只多出很少的 token 和几乎可以忽略的延迟。多问一个可能用不上的问题,几乎是免费的。
下面这个请求同时对一张工单做了分类、判断是否紧急、给不满程度打分:
from xiangxin import Choice, Noul, Score, XiangxinClient
state = {
"message": "高铁票退了三天钱还没到账,12306 显示已退款,银行说没收到,到底谁负责?",
"refund_rule": "退票款原路返回,一般 1–7 个工作日到账。",
}
with XiangxinClient() as client:
resp = client.system_one(
state=state,
questions={
"department": Choice(
instructions="`message` 应该由哪个团队处理?",
criteria={
"payments": "退款、扣款、到账等资金问题",
"ticketing": "购票、改签、座位等票务操作",
"account": "登录、实名认证、账号安全",
},
),
"is_urgent": Noul(
instructions="`message` 是否表达了着急或有时间压力?",
),
"frustration": Score(
instructions="`message` 中顾客有多不满?",
criteria=[
"平静,只是陈述事实",
"有些不满,但语气克制",
"非常愤怒,措辞激烈",
],
),
},
)
print(resp.answers["department"].choice) # payments
print(resp.answers["is_urgent"].noul) # 0.57
print(resp.answers["frustration"].score) # 0.76投机式地多问
把代码可能用到的问题都问上,包括只对部分输入有意义的问题,由代码决定用哪些答案。例如工单最后不是 bug 反馈,那就忽略"bug 严重程度"那一题的答案。这就是 推测式扇出 模式。
提示
编程智能体比人更容易养成"一次调用只问一个问题"的习惯。象信智能体 Skill 会明确告诉它:把问题尽量合并到同一次调用里。
把复杂判断拆成几个问题
一个依赖多个因素的判断,最好一个因素一个问题,再在代码里按权重组合。权重在你手里:当组合结果和团队的判断不一致时,改权重再跑一遍即可。拆开之后问题是并行的,响应时间几乎不变,只多一点问题本身的 token。
例如工单优先级可以由三个 Score 组成:bug 有多严重、顾客有多不满、报告里给工程师的信息够不够。Score 页面 有完整的请求和归一化加权代码,这种做法就是 组合评分。
问题之间有依赖时
同一请求里的问题互相独立:一个答案不会成为另一个问题的上下文。如果后面的判断确实依赖前面的答案,就在代码里发第二次请求。
判断依赖是否"真实"的标准是:不拿到第一个答案,代码就无法构造第二个请求。比如需要根据答案去查更多数据放进 state,或者根据答案决定下一题有哪些选项(逐层走一棵类目树)。除此之外,都应该在第一次请求里一起问,让代码忽略用不上的答案。
两次请求是例外,不是常态。如果第二次的问题完全可以针对原始 state 提出,就把它们并到第一次里。
更多关于如何把一个业务流程拆成聚焦判断的讨论,见 如何用象信构建。
下一步
- Choice 选择题:从固定列表中选一个。
- Score 评分:在有序档位上定位。
- Noul 是非题:得到一个陈述为真的概率。
- 模式:看这些原语如何组合成系统架构。

