fix(consult): complete natal chart tool on homepage topic cards (BUG-630)

Thinking models were spending the first natal step on skill_read or a Level 2 draft, so home chips such as 家庭 never satisfied the calculation contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-09 22:46:46 +08:00
co-authored by Cursor
parent 4067aff86c
commit c53decdb85
12 changed files with 204 additions and 20 deletions
+27 -15
View File
@@ -25,6 +25,7 @@ import {
} from "@/lib/agent-observability";
import {
consultationEntrypointSchema,
pinsConsultationDomains,
resolveConsultationQuestion,
shouldLoadGeneralDailyPanchanga,
} from "@/lib/consultation-entrypoint";
@@ -46,6 +47,7 @@ import {
AGENT_SLICE_MAX_STEPS,
consultationContinueGenerationSettings,
consultationGenerationSettings,
consultationNatalPrepareStep,
consultationSliceGenerationSettings,
createConsultationAgentContext,
createWindowConsultationAgentContext,
@@ -127,7 +129,7 @@ const generalChatRequestSchema = z.object({
consultationMode: z.literal("general_no_birth_time"),
question: z.string().trim().min(1).max(500),
theme: consultationDomainSchema,
entrypoint: z.literal("daily_starlanguage").optional(),
entrypoint: consultationEntrypointSchema.exclude(["birth_time_rectification"]).optional(),
}).strict();
const windowChatRequestSchema = z.object({
@@ -135,7 +137,7 @@ const windowChatRequestSchema = z.object({
consultationMode: z.literal("declared_birth_window"),
question: z.string().trim().min(1).max(500),
theme: consultationDomainSchema,
entrypoint: z.literal("daily_starlanguage").optional(),
entrypoint: consultationEntrypointSchema.exclude(["birth_time_rectification"]).optional(),
}).strict();
const chatRequestSchema = z.union([
@@ -838,9 +840,12 @@ export async function POST(request: Request) {
}
};
const cacheBoundary = cachedSystemMessage("【上下文缓存边界】后续内容为本轮请求输入。", selectedModel.model);
const natalToolInstruction = pinsConsultationDomains(consultEntrypoint)
? "如需新的个人星盘结论,必须调用服务器绑定的排盘工具。调用时不要填写 domains,沿用服务器已选定的主题。"
: "如需新的个人星盘结论,必须调用服务器绑定的排盘工具。";
const natalInstruction = consultEntrypoint === "daily_starlanguage"
? "先加载 Jyotish Skill;如需新的个人星盘结论,必须调用服务器绑定的排盘工具。按三节写:今日趋势、适合推进 / 需要避开、一个行动(把技法审计表和「探索性日提示,不是确定预测」放进最后一节)。不要复述内部 JSON 字段。"
: "先加载 Jyotish Skill;如需新的个人星盘结论,必须调用服务器绑定的排盘工具。事业/财富/婚恋/家庭按 skill Level 2 模板写:原始结构、六步宫位、Yoga 表、时机、综合、文末技法审计表,然后才是现代生活措辞。不要复述内部 JSON 字段。";
? `${natalToolInstruction}按三节写:今日趋势、适合推进 / 需要避开、一个行动(把技法审计表和「探索性日提示,不是确定预测」放进最后一节)。不要复述内部 JSON 字段。`
: `${natalToolInstruction}事业/财富/婚恋/家庭按 skill Level 2 模板写:原始结构、六步宫位、Yoga 表、时机、综合、文末技法审计表,然后才是现代生活措辞。不要复述内部 JSON 字段。`;
const adoptedRangeNote = consultationMode === "verified_chart"
&& prepared.serverChart?.truth.birthTimeStatus === "accepted"
&& prepared.serverChart.toolInput.candidate_range
@@ -884,14 +889,21 @@ export async function POST(request: Request) {
hooks,
...consultationGenerationSettings(selectedModel.model),
};
async function streamWithOverflowRetry(agent: {
stream: (
messages: typeof baseMessages,
options: typeof streamOptions,
) => Promise<{ fullStream: AsyncIterable<unknown> | ReadableStream<unknown>; totalUsage: Promise<Usage> }>;
}) {
const natalStreamOptions = {
...streamOptions,
prepareStep: consultationNatalPrepareStep,
};
async function streamWithOverflowRetry(
agent: {
stream: (
messages: typeof baseMessages,
options: typeof streamOptions | typeof natalStreamOptions,
) => Promise<{ fullStream: AsyncIterable<unknown> | ReadableStream<unknown>; totalUsage: Promise<Usage> }>;
},
options: typeof streamOptions | typeof natalStreamOptions = streamOptions,
) {
try {
const result = await agent.stream(baseMessages, streamOptions);
const result = await agent.stream(baseMessages, options);
usages.push(result.totalUsage);
return result;
} catch (error) {
@@ -900,7 +912,7 @@ export async function POST(request: Request) {
// clients cannot parse rectification `attempt.reset`, so the retry stays
// server-side and never opens a second user-visible wait.
baseMessages = consultationBaseMessages(true);
const overflow = await agent.stream(baseMessages, streamOptions);
const overflow = await agent.stream(baseMessages, options);
usages.push(overflow.totalUsage);
return overflow;
}
@@ -1114,7 +1126,7 @@ export async function POST(request: Request) {
state,
});
const agent = getJyotishAgent(selectedModel, agentContext);
const result = await streamWithOverflowRetry(agent);
const result = await streamWithOverflowRetry(agent, natalStreamOptions);
const retry = async () => {
const retried = await agent.stream([
...baseMessages,
@@ -1122,7 +1134,7 @@ export async function POST(request: Request) {
role: "user" as const,
content: "运行合同不完整:本次尚未取得服务器计算结果。请调用 run-jyotish-consultation 完成计算,再据此回答;不要在工具参数中添加出生资料。",
},
], streamOptions);
], natalStreamOptions);
usages.push(retried.totalUsage);
return retried.fullStream;
};
@@ -1136,7 +1148,7 @@ export async function POST(request: Request) {
role: "user" as const,
content: "服务器计算已经完成,但上一轮没有输出任何回答文本。请重新取回本次计算结果,然后直接给出回答;不要只描述过程或工具调用。",
},
], streamOptions);
], natalStreamOptions);
usages.push(retried.totalUsage);
return retried.fullStream;
};