Dayward AI
Week 1 · D4About 6 hours

Model Integration and System Prompts: a Multi-Provider Abstraction With Fallback, Overriding the Default Persona (dg P03/P04/M04)

Learn to abstract multiple model providers behind one layer with automatic failover, and understand why you must override the framework's default system prompt.

Today's goals 0/3

Sign in to tick these off and save your progress.

Today's Goals

  1. Implement a model-calling layer that supports at least 3 providers, switchable by configuration
  2. Write a custom system prompt for an agent that overrides the framework's default persona
  3. Name the factors a fallback strategy must weigh when a model call fails (latency, cost, availability)

For three days your code has had exactly one provider in it: the address hard-coded into a fetch, and the model name coming, at best, from a MODEL environment variable (step 4 of D1's lab did precisely that). Changing the model id is fine; changing vendors means changing code. Today we take that layer apart properly. When you are done, come back and tick off the three goals.

Plain-Language Walkthrough

Wiring up one model means handing your uptime to somebody else's operations team

Picture a restaurant with a single supplier. Dishes, prices, and how fast food arrives all depend on them. Everything is fine, right up until the morning their truck breaks down — and then you are not having a slightly worse day, you cannot open.

Wiring up a model is the same. Hard-coding openai/gpt-4o-mini binds your service's availability entirely to one vendor's operational quality, and LLM API availability is lower than you think: regional outages, an account tripping a rate limit, a model retired or renamed, a call inexplicably hanging for 60 seconds without returning. These are not freak events; they happen a few times a month.

Do the arithmetic and it becomes obvious why this is not a bet worth taking. Suppose a vendor's monthly availability is 99.5% — which sounds high, and means roughly 3.6 hours a month during which your agent is dead and every user sees a spinner and an error. Wire up three vendors whose failures are uncorrelated and all three going down at once is 0.5% cubed, about 1.25 in ten million: unavailable time drops from 3.6 hours (12,960 seconds) to 0.33 seconds, less than one second. The same order of magnitude of code, four-plus orders of magnitude of availability, roughly forty thousand times better.

That number carries one premise that has to be stated out loud: the failures are uncorrelated. So that you can run today's example and today's lab with a single key, all three providers here go through one aggregation gateway, OpenRouter — which means three model ids, not three independent paths. The gateway going down, or the key being suspended, takes all three out at once, and the aggregator has become the new single point of failure. Real redundancy means direct endpoints at different vendors, with independent credentials, independent data centers, and independent billing; only then does that forty-thousand-times figure count. Interviewers love to enter through this crack, so volunteer it: the order of magnitude assumes independence, and a shared gateway does not satisfy that assumption.

The question is how far the last three days' code sits from wiring up three of them. Look at what you have written:

hardcoded.js
// The past three days: the address hard-coded at the call site, and the model name
// coming from an environment variable at best
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: process.env.MODEL ?? 'openai/gpt-4o-mini',
    messages,
  }),
})
const json = await res.json()
// What happens when this vendor goes down? There is no other road, and the whole
// service goes down with it.

Not one line of that is wrong. The problem is that it has only one road. The MODEL variable looks like flexibility, but it swaps a model id, not a vendor: the address, the auth header, and the shape of the response fields are all welded into this one call. When something breaks you can only change a variable, restart, and pray the other vendor's response format happens to match.

There is a more mundane reason too: prices and capabilities change every month. The best-value model today may be beaten two months from now by somebody else's new version at half the price. If switching models means rewriting the calling code and retesting every prompt, you will keep paying for the expensive slow one because switching is too much hassle. A high integration cost is, at bottom, robbing you of the freedom to choose later.

So the first rule of a production-grade agent is: a model is a replaceable part, not a hard-coded foundation.

Which raises the obvious objection — three vendors have three request formats, three response structures, and three ways of reporting errors, so does wiring up three mean writing the calling code three times? The answer is of course to add a layer of abstraction, but the layer is not the hard part. The hard parts are the three things behind it: which classes of error make a retry pure waste (almost everyone puts 404 in the wrong bucket), how fallback triples your bill without you noticing, and a tiered routing scheme that saves over sixty thousand a year. We take them one at a time.

The unified calling layer: gathering three vendors' quirks behind one interface

The answer is a layer, and its job is one sentence: expose one function upward, and absorb every difference downward.

The everyday analogy is a travel power adapter. Your laptop accepts one kind of plug, the world has more than a dozen socket standards, and the adapter's value is not cleverness — it is that it holds the world's mess outside your device. A unified calling layer is identical: whether OpenAI's format, Anthropic's format, or some open-weight model's compatibility endpoint sits underneath, the layer above always receives the same shape.

Start by defining what that same shape is. In: a messages array, an optional temperature, an optional timeout. Out: the reply text, which provider actually served it, and how many tokens it consumed. Note that "which provider actually served it" in the output is not debug information, it is this layer's core product — fallback and cost accounting both depend on it later.

model-router.js
const PROVIDERS = [
  { name: 'fast', model: 'openai/gpt-4o-mini', timeoutMs: 8000 },
  { name: 'strong', model: 'anthropic/claude-3.5-sonnet', timeoutMs: 15000 },
  { name: 'backup', model: 'google/gemini-2.0-flash-001', timeoutMs: 8000 },
]
 
// An error type with a structured status field: let callers read a field instead of
// parsing the error message string
class ProviderError extends Error {
  constructor(message, status) {
    super(message)
    this.name = 'ProviderError'
    this.status = status
  }
}
 
async function callOne(provider, messages) {
  const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ model: provider.model, messages }),
    signal: AbortSignal.timeout(provider.timeoutMs),
  })
  if (!res.ok) throw new ProviderError(`${provider.name} returned HTTP ${res.status}`, res.status)
  const json = await res.json()
  return {
    text: json.choices[0].message.content,
    provider: provider.name,
    tokens: json.usage.total_tokens,
  }
}

One detail here is easy to miss: the timeout lives in configuration, and it differs per vendor. A strong model is slow by nature, and giving it 8 seconds kills a large number of requests that would have succeeded; a fast model that has not produced its first token after 8 seconds is essentially a lost cause. Timeouts must be set per provider — a single global timeout is the most common configuration mistake beginners make.

One more detail no language escapes: vendors do not agree on field naming. The OpenAI lineage uses snake_case total_tokens, while Swift and Java model objects habitually use camelCase. When decoding you either write the mapping explicitly (CodingKeys in Swift, @JsonProperty in Java) or turn on a global snake_case strategy on the decoder. The Swift version above takes the former route — do not expect field names to line up by themselves; this is the most frequent low-grade bug when wiring up a second provider.

Finally, draw a boundary: the unified layer is responsible only for getting a request out and a result back — do not put business logic inside it. People take shortcuts and write something like "in the support scenario, append this extra prompt" into this layer, and then a new business line means editing shared code, and every edit forces a regression pass across three callers. The thinner this layer, the more callers it can serve.

Fallback: when to switch, and when not to

You go to an ATM and it prints a slip. If the slip says the machine is out of cash, another machine solves it. If it says your card has been reported lost, visiting every ATM in town will not help. It is the same action — try another machine — and whether it is worth doing depends entirely on the reason for the failure.

Model calls are identical. The hard part is not the for loop, it is judging whether an error is worth trying another vendor for. The intuitive trap is: they all failed, so retry them all. Wrong — classification has to happen before the retry, and the basis for classifying is not the status code itself but one question: could another vendor plausibly do better?

  • Switching will not help: 400 for a malformed body, 403 blocked by a safety policy. The request itself is the problem, and retrying just commits the same bug twice.
  • Switching will most likely help: 408 timeout, 429 rate limit, 5xx server fault — the textbook "their problem."
  • The two most often filed in the wrong bucket are 404 and 402. A 404 usually means the model id you wrote has been retired or renamed — one of the everyday failures listed at the top of this chapter. It looks like your problem, and switching vendors may well succeed. A 402 is insufficient balance, and switching to an account with money in it obviously rescues it. Filing all 4xx as "do not retry" is the most common error here.
  • A 401 depends on how you manage credentials: with all three sharing one gateway key, switching changes nothing; with separate keys per vendor, A's being revoked while B still works means switching rescues it entirely.

One more thing to get right: judge on structured fields, not by parsing the error message string. Something like err.message.startsWith('HTTP ') fails silently the day you reword a log line. So each version below defines an error type carrying a status first.

fallback.js
// 402 insufficient balance and 404 model retired also belong to "another vendor might
// do better" - do not leave them out
const RETRIABLE_STATUS = new Set([402, 404, 408, 429])
 
function shouldTryNext(err) {
  if (err.name === 'TimeoutError') return true
  if (err instanceof ProviderError) {
    return RETRIABLE_STATUS.has(err.status) || err.status >= 500
  }
  // 401 lands in false here on the premise that all three vendors in this course share
  // one OpenRouter key; with independent credentials, judge 401 as "switch vendors"
  return false // 400 and 403: your own problem, nobody else can help
}
 
async function callModel(messages) {
  const errors = []
  for (const provider of PROVIDERS) {
    try {
      return await callOne(provider, messages)
    } catch (err) {
      errors.push(`${provider.name}: ${err.message}`)
      if (!shouldTryNext(err)) throw err
    }
  }
  throw new Error(`Every provider failed: ${errors.join('; ')}`)
}

That loop is a dozen lines and it costs you three separate bills.

The first is money. Switching to the next vendor means paying twice for the same prompt, and a three-vendor chain costs triple in the worst case. You will not notice normally, because the happy path calls one vendor — but the moment the primary provider gets shaky the bill climbs while you are unaware, which is why "how often fallback fired" has to be a monitored metric.

The second is latency. A serial fallback's total time is the sum of the timeouts: three vendors at 15 seconds each means a worst case of 45 seconds for the user, when failing outright at 20 would have been kinder. So on top of per-vendor timeouts you need a total budget.

The third is the most expensive and it is called a cascade. The instant the primary provider rate-limits, all of your traffic lands on the backup within the same second — and the backup's quota was requested on the basis of taking a slice of traffic, so it cannot absorb a sudden doubling, starts returning 429 itself, and the traffic moves on to the third. All three fall over in sequence, which is worse than having had one. The fix is a circuit breaker: keep a consecutive-failure count per provider, skip it temporarily past a threshold, then let a trickle through after a cooldown and only restore it on success. It shares its lineage with the circuit breakers in a microservice gateway, and it comes up in interviews constantly.

The system prompt: the framework's default persona is a time bomb

When you hire a new support agent, you start with a job description: which company they represent, what they may and may not answer, how long a reply should be, which system to look up an order in. Push them to a desk with none of that explained and they will still serve customers — using whatever they picked up somewhere else. The part you did not write is never blank; it is a default somebody else filled in.

You may not have noticed while getting the Pi SDK running on day three: you wrote no system prompt, and the agent still behaved like an assistant. That persona is the default the framework filled in for you.

At the demo stage that is thoughtful. In production it is a hazard. A default persona usually says something generic along the lines of "you are a helpful AI assistant," which brings three concrete problems.

First, it does not know your business boundary. A user asks it to write a resignation letter and a generic assistant will happily oblige; but if your product is e-commerce support, that reply is completely off-topic and wasted tokens too.

Second, its output format is outside your control. A default persona will not constrain "answer in at most three sentences" or "no Markdown headings," so a level-two heading suddenly appears inside a chat bubble and your frontend styling falls apart.

Third, and worst — it changes when the framework upgrades. Every behavior you tested rests on a piece of text you did not write, cannot see, and which may quietly change after the next pnpm update. That class of bug is exceptionally hard to trace, because the code never moved.

So the rule is: always write the system prompt explicitly, even if it is one sentence. In production it is usually not a hard-coded string but an assembled template:

TextText
You are the after-sales assistant for a certain e-commerce platform.
 
[Scope]
- Only answer questions about orders, returns and exchanges, and shipping
- When asked about anything else, politely explain that you only handle
  after-sales matters, and do not attempt an answer
 
[Output requirements]
- At most 3 sentences per reply; no Markdown headings or lists
- Anything involving amounts or timings must come from a tool call; never answer
  from memory
 
[Current context]
- Current time: 2026-09-04 14:30
- Customer tier: gold
- Available tools: query_order, apply_refund

Look at the structure: persona plus scope plus output requirements plus dynamic context. The first three blocks are a static template; the last is assembled per request. Putting the current time in there is a frequent interview point — the model has no clock, and if you do not tell it today's date it cannot work out which day "the order I placed three days ago" refers to.

Routing: let the cheap model do the rough work

With a unified layer and fallback in place, the last step is choosing a model actively rather than waiting passively for a failure.

The core fact is that model prices differ by more than fifty times, while a large share of your tasks does not need the strongest model at all. Deciding whether a user's sentence is about an order is 99% accurate with the cheapest small model; drafting a dispute clause from three contracts requires the strongest one you have. Using a flagship model for intent classification is driving a sports car to pick up a parcel downstairs.

There are three common routing dimensions:

DimensionHow to judgeTypical approach
Task typeIs this step classification, extraction, or long-form reasoningClassification and extraction go to the cheap model; reasoning and generation to the strong one
Latency requirementIs a user watching, or is this a background batchForeground takes a low-latency model; background jobs can take the slow cheap one
Input lengthHow many tokens is the promptVery long contexts are supported by only some models, and the price climbs steeply

Here is a number you can feel immediately. Suppose a support agent handles 10,000 conversation turns a day at an average of 2,000 tokens per turn. Send everything to a flagship model at 15 per million tokens and that is about 300 a day; but at least six in ten of those turns are rough work like intent classification and information extraction, and moving them to a small model at 0.3 per million tokens brings the daily cost down to roughly 125 — over sixty thousand a year, with users unable to tell the difference. So when an interviewer asks how you control cost, the first answer they want to hear is usually not "compress the context" but "tiered routing."

The implementation is not complicated: add a tier parameter to callModel, let the caller declare whether this call wants fast or strong, have the routing layer pick the starting provider accordingly, and reuse the fallback logic wholesale. Start with static tiers by task type; do not jump straight to dynamic routing where the model decides which model to use — that scheme costs an extra model call of its own, and both the latency and the cost may not repay it.

Source Reading

Hands-On Lab

🧪 D4 lab: a switchable 3-provider model layer plus a custom system prompt

Code location: labs/agent-30days/day-04-multi-provider-router

Acceptance criteria:

  1. MOCK=1 pnpm start prints the complete downgrade path, in the form of fast timing out, then strong being rate-limited, then backup succeeding.
  2. Change fast's mockFailure from 'timeout' to 'bad-request' (the mock layer throws a 400), rerun, and confirm the program prints the do-not-retry line and throws immediately without trying strong or backup.
  3. With the same MOCK=1 pnpm start "write me a poem", the default persona writes you a poem, and once the scope section is written it declines instead — going from off-topic to declining is the evidence the prompt took effect.
  4. Each turn prints a usage line with the provider and token count; ask five different questions in a row and the hit distribution becomes visible.
  5. pnpm typecheck passes with no any.

starter/ has four exercise points cut out of it and runs fully offline under MOCK=1: the mock layer manufactures timeouts, rate limits, and 400s according to mockFailure, so you can verify fallback without actually rate-limiting a real account. The fake model in there also reads the system prompt you assembled — with no declared scope it answers anything, and only a declared scope makes it decline. buildSystemPrompt's default return value is precisely the phoned-in persona this chapter criticizes, so run the poem request against it once as-is and watch it go off-topic with your own eyes.

  1. Extract the three providers' configuration into an array, each with its own timeout value, and write the unified callOne.
  2. Implement shouldTryNext, classifying by whether another vendor could plausibly do better: 400 and 403 throw immediately, while 408, 429, 5xx, and the easily forgotten 402 and 404 continue to the next vendor. Judge on the status field of ProviderError; parsing the error message string is not allowed.
  3. Ask an out-of-scope question with the default persona first and remember how it goes off-topic; then write a custom system prompt (persona plus scope plus output requirements plus the current time) and run the same command to see it decline instead.
  4. Use MOCK=1 to make the first provider time out and the second return 429, verify the third one succeeds, and check that the log shows the full downgrade path.
  5. Print the provider name and token count for every call, ask five questions in a row, and observe the cost distribution.

Interview Questions

Today's four questions are in the bank below, covering the motivation for multiple providers, the fallback trade-offs, what a system prompt is for, and the cost-versus-latency decision. Expand a question and read the analysis before the key points — the follow-up on question 2, about circuit breakers and cascades, is the spot in this chapter you are most likely to be asked about, so do not skip it.

Checklist and Tomorrow

  • Implement a model-calling layer that supports at least 3 providers, switchable by configuration
  • Write a custom system prompt for an agent that overrides the framework's default persona
  • Name the factors a fallback strategy must weigh when a model call fails (latency, cost, availability)
  • Say clearly which errors deserve a fallback and which do not, and explain why retrying a 400 is waste while a 404 should switch vendors
  • The lab's downgrade log shows the full chain from timeout to 429 to success
  • Answer at least 3 of the 4 interview questions without looking at the key points

Tomorrow (D5) attention moves from how you call the model to how the model calls you. You will fit the agent with a real tool system: validate arguments against a schema, feed an error message back to the model when a tool fails so it can correct itself, then use event subscription to expose every internal step. Today's fallback handles "external services are unreliable"; tomorrow's error feedback handles "the model itself makes mistakes" — and only stacked together do those two become the real source of a production agent's stability.

Interview questions

  • Why do production agents usually integrate more than one model provider?为什么生产级 Agent 通常要接入多个模型 provider?
    Common in ChinaCommon overseasBasic#model-routing#reliability

    How to reason about it · think before answering

    1. First decide whether this is an availability question or an architecture question; answering only 'so it doesn't go down' reads as inexperienced.
    2. Follow the causal chain: the model API is an external dependency, dependencies have failure rates, your ceiling is capped by theirs, so you either accept the cap or add redundancy.
    3. Quantify it: 99.5% monthly availability is about 3.6 hours of downtime; three independently failing providers push that to seconds. Orders of magnitude beat adjectives.
    4. The second reason shows engineering maturity: model pricing and capability shift monthly, and high switching cost means you stay on the expensive slow one out of inertia — coupling really costs you future optionality.
    5. Say the premise out loud before they ask: that order of magnitude assumes the three providers fail independently. If all three are model ids behind one aggregator gateway on a single key — which is what most first versions look like — the gateway going down takes all three with it, the redundancy is fake, and the aggregator has become the new single point of failure. Real independence means direct endpoints at different vendors, with separate credentials and billing. Naming this yourself signals operational experience far more than reciting 0.005 cubed.
    6. Expect the follow-up: isn't this more expensive? No — the happy path calls one provider; what costs money is fallback firing often, which is a signal to investigate the primary, not to remove redundancy.

    分析过程 · 先想清楚再作答

    1. 先判断这题问的是「可用性」还是「架构」。只答「防止挂掉」拿不到分,因为面试官想看的是你有没有真的算过账、踩过坑。
    2. 从一条因果链推:模型 API 是外部依赖 → 外部依赖必然有故障率 → 你的可用性上限被它锁死 → 所以要么接受这个上限,要么加冗余。
    3. 把可用性说成数字才有说服力:单家 99.5% 意味着每月约 3.6 小时不可用;三家独立故障时理论不可用时间降到秒级。数量级差异比形容词有力得多。
    4. 第二个理由往往被忽略,但更能体现工程视角:模型的价格和能力每月都在变,接入成本高会让你因为「改起来麻烦」而一直用贵的慢的那个——高耦合真正的代价是剥夺未来的选择权。
    5. 这里有个必须自己先说破的前提:那个数量级是拿「三家故障互不相关」算出来的。如果三家其实都走同一个聚合网关、共用同一把 key(很多人的第一版就是这样),网关一挂三家一起挂,冗余是假的,聚合网关反而成了新的单点。真正的独立要落到不同厂商的直连端点、各自的凭证和计费上。主动点破这一条,比背出 0.005 的三次方更能体现你真的部署过。
    6. 可以预期的追问:多接几家不是更贵吗?答案是不会——正常路径只调一家,多的只是配置和一层抽象;真正贵的是 fallback 被频繁触发,那说明你该查主 provider 而不是砍掉冗余。

    Key points

    • The model API is an external dependency; outages, rate limits and model deprecations are monthly realities
    • 99.5% monthly availability is roughly 3.6 hours down; multi-provider redundancy cuts that by orders of magnitude
    • Pricing and capability shift constantly, so an abstraction layer turns model swaps into config changes
    • The happy path still calls one provider — redundancy costs an abstraction, not a multiplied bill

    答题要点

    • 模型 API 是外部依赖,厂商故障、限流、模型下线都是每月都会遇到的日常,不是小概率事件
    • 单家 99.5% 可用性等于每月约 3.6 小时不可用;多家冗余能把理论不可用时间降低几个数量级
    • 价格与能力每月都在变,统一抽象层让换模型变成改配置,保住了未来做选择的自由
    • 正常路径只调一家,冗余的成本是一层抽象而不是多倍账单
  • What trade-offs shape a model fallback strategy?设计模型 fallback 策略时要权衡哪些因素?
    Common in ChinaCommon overseasIntermediate#model-routing#reliability#cost

    How to reason about it · think before answering

    1. The word 'trade-offs' is the hinge: they are not asking for a for-loop, they want to know you understand fallback has costs.
    2. First key judgment: not every error deserves a fallback, and the test is not the leading digit of the status code but whether another provider could plausibly succeed. A 400 (malformed body) or 403 (blocked by safety policy) fails everywhere, so retrying repeats your own bug at double the cost and latency; a 408, 429 or 5xx is theirs and usually succeeds elsewhere. The two that people get wrong are 402 (out of credit) and 404 (model retired or renamed): both are 4xx, both look like your fault, and both are fixed by switching. A 401 depends on how credentials are managed — one shared gateway key fails everywhere, but per-provider keys mean a revoked key on A is survivable on B. Classification comes before retry.
    3. Cost: switching means paying for the same prompt twice, up to 3x across a three-provider chain. Volunteering this separates people who shipped from people who only read about it.
    4. Latency: serial fallback accumulates timeouts. Three providers at 15s each means a 45s wait — worse than failing fast. Timeouts must be per-provider with an overall budget.
    5. Thundering herd is the most common follow-up: when the primary rate-limits, shifting all traffic at once can take down the backup too. Hence circuit breaking — drop a provider after N consecutive failures, then probe with a trickle.
    6. Expect: how long do you drop it for? Exponential backoff with a half-open probe — the same pattern as database connection pool breakers.

    分析过程 · 先想清楚再作答

    1. 题眼在「权衡」两个字——面试官不要你背一个 for 循环,他要看你知不知道 fallback 是有代价的。
    2. 先拆出第一个关键判断:不是所有错误都该 fallback,而分类的依据不是状态码的首位数字,是「换一家有没有可能变好」。400 请求体不合法、403 被安全策略拦截,换谁都一样,重试只是把同一个 bug 再犯一遍、白花两倍的钱和时间;408 超时、429 限流、5xx 服务端故障是对方的问题,换一家大概率能成。最容易答错的是 402 余额不足和 404 模型被下线或改名——它们同属 4xx、长得像「你的问题」,其实换一家完全可能成功;401 则要看凭证怎么管,三家共用一把网关 key 时换了也没用,各有各的 key 时 A 被吊销切到 B 完全能救。分类是 fallback 的第一步,不是重试。
    3. 再说成本:切换意味着同一段 prompt 你付了两次钱,三家链路最坏是三倍成本。这条一定要主动说出来,它区分了「写过」和「上过线」。
    4. 然后是延迟:串行 fallback 的总耗时是各家超时值的累加。如果每家给 15 秒、三家串下来用户要等 45 秒,那还不如早点失败。所以超时值必须按 provider 分别设,且要设总预算上限。
    5. 最后是雪崩,这是最容易被追问的点:主 provider 限流时你把全部流量瞬间压到备用上,很可能把备用也压垮。所以要加熔断——连续失败 N 次就暂时摘掉该 provider,过一段时间放少量流量试探。
    6. 可以预期的追问:怎么知道该摘多久?答案是指数退避 + 半开状态试探,和数据库连接池的熔断是同一套思路。

    Key points

    • Classify before retrying, judging by whether another provider could plausibly succeed rather than the leading digit: 400/403 must not fail over; 408/429/5xx should; so should 402 (out of credit) and 404 (model retired); 401 depends on whether the providers share one key
    • Cost: every fallback re-pays for the same prompt, so worst-case cost scales with chain length
    • Latency: serial fallback sums the timeouts, so set per-provider timeouts plus an overall budget
    • Thundering herd: shifting full traffic to the backup can topple it too — use circuit breaking with exponential backoff and half-open probes

    答题要点

    • 先分类再重试,判据是「换一家有没有可能变好」而不是状态码首位:400/403 不该切,408/429/5xx 该切,402 余额不足和 404 模型下线同样该切,401 取决于三家是否共用同一把凭证
    • 成本:每次 fallback 都要重付一遍 prompt 的钱,链路越长最坏成本越高
    • 延迟:串行 fallback 的耗时是各超时值累加,必须按 provider 分设超时并设总预算
    • 雪崩防护:主 provider 故障时全量流量压向备用会把备用也压垮,需要熔断 + 指数退避 + 半开试探
  • What does the system prompt do in an agent, and why not rely on the framework default?系统提示词(system prompt)在 Agent 里起什么作用?为什么不能用框架默认的?
    Common in ChinaCommon overseasIntermediate#prompt-engineering#system-prompt

    How to reason about it · think before answering

    1. The first half is a warm-up; the discriminating half is why the default is dangerous.
    2. State the role: it is the one instruction block whose weight stays stable across dozens of turns, setting identity, capability boundaries and output format.
    3. Then give three concrete consequences rather than 'not customized enough': it does not know your business boundary so it happily answers off-topic questions; it does not constrain output format so stray Markdown headings break your UI; and worst, it changes when the framework updates — your tested behavior rests on invisible text, and the bug appears with zero code changes.
    4. Land on practice: a production system prompt is assembled from a template — persona, capability boundary, output requirements, dynamic context — with the last part rebuilt per request.
    5. Expect: what gets forgotten in dynamic context? The current time. Models have no clock; without today's date they cannot resolve 'the order I placed three days ago'.
    6. Second follow-up: how do you test a prompt? Treat it as configuration, not code — store it, version it, roll it out to a percentage, because you cannot unit-test 'the tone got friendlier'.

    分析过程 · 先想清楚再作答

    1. 前半句是送分题,后半句才是区分度所在——很多人答得出 system prompt 是干什么的,答不出「默认值有什么坑」。
    2. 先说作用:它是唯一一段在整段对话里权重稳定、不会被后续几十轮稀释的指令,用来设定身份、能力边界和输出格式。
    3. 再答「为什么不能用默认的」,要给出三条具体后果而不是泛泛说「不够定制」:一是它不知道你的业务边界,用户问业务外的问题它会热情地答;二是它不约束输出格式,前端样式会被冷不丁冒出的 Markdown 标题打乱;三是最要命的——它会随框架升级而变化,你测好的所有行为建立在一段看不见的文本上,出 bug 时你的代码一行没动,极难排查。
    4. 结论落到工程做法:生产环境的 system prompt 是拼出来的模板,结构是「人设 + 能力边界 + 输出要求 + 动态上下文」,最后一块每次请求现拼。
    5. 可以预期的追问:动态上下文里最容易漏什么?答「当前时间」——模型没有时钟,不告诉它今天几号,它算不出「三天前下的单」是哪天。这个细节很能体现有没有真做过。
    6. 第二个追问:prompt 怎么测试?答案是把它当配置而不是代码——存库、加版本号、支持按比例灰度,因为你没法写单元测试断言「模型语气变友好了」。

    Key points

    • The system prompt sets identity, capability boundaries and output format, and keeps stable weight across turns
    • A default persona does not know your business boundary and will cheerfully answer off-topic questions
    • It does not constrain formatting, so stray Markdown can break your UI
    • Most dangerous: defaults change on framework upgrades, producing behavior regressions with no code change
    • Production practice: assemble it explicitly, treat it as versioned configuration, and roll changes out gradually

    答题要点

    • system prompt 设定身份、能力边界与输出格式,是对话里权重最稳定、不被后续轮次稀释的一段指令
    • 框架默认人设不知道你的业务边界,会热情回答业务外的问题,浪费 token 且跑题
    • 默认人设不约束输出格式,模型可能吐出 Markdown 标题打乱前端样式
    • 最危险的是默认值会随框架升级而变化,代码一行没动却出现行为回归,极难排查
    • 生产做法:显式拼模板(人设 + 能力边界 + 输出要求 + 动态上下文),当作配置存储、加版本号、可灰度
  • How do you pick the right model per task, balancing cost against latency?如何在成本和延迟之间给不同任务选择合适的模型?
    Common in ChinaCommon overseasIntermediate#model-routing#cost#latency

    How to reason about it · think before answering

    1. This question tests whether you have ever spent your own money. 'Use the best model' is the worst answer; 'it depends' is too vague — give actionable routing dimensions.
    2. Establish the core fact: model pricing spans 50x or more, and much of your workload does not need the strongest model. Using a flagship for intent detection is driving a sports car to fetch a parcel downstairs.
    3. Give three routing dimensions: task type (classification and extraction go cheap, long-form reasoning goes strong), latency requirement (foreground users need low latency, background batches can be slow and cheap), and input length (only some models handle very long context, and pricing rises steeply).
    4. Quantify it: 10k conversations a day at 2000 tokens each costs roughly 300 CNY/day on a flagship; routing the 60% of grunt work to a small model drops it to about 125 CNY/day, saving 60k+ CNY a year with no perceptible quality change.
    5. Volunteer the implementation trade-off: start with static tiers by task type. Dynamic routing that asks a model which model to use adds another model call, and the latency and cost may not pay for themselves — optimize once you have real data.
    6. Expect: how do you verify the cheaper tier did not hurt quality? A golden set — run both tiers over the same inputs and compare with human or LLM-as-judge scoring, so the decision rests on data rather than vibes.

    分析过程 · 先想清楚再作答

    1. 这题考的是「你有没有真的在花自己的钱」。答「用最好的模型」是最差的答案,答「按需选择」太空,要给出可执行的分档维度。
    2. 先建立核心事实:不同模型的价格能差 50 倍以上,而你的任务里很大一部分根本不需要最强的模型。用旗舰模型做意图识别,等于开跑车去楼下取快递。
    3. 然后给出三个可操作的路由维度:任务类型(分类抽取走便宜模型,长文推理走强模型)、延迟要求(前台用户在等就走低延迟,后台批处理可以慢而便宜)、输入长度(超长上下文只有部分模型支持且价格陡增)。
    4. 结论要落到数字上才有说服力:1 万轮对话每轮 2000 token,全走旗舰约 300 元一天;把六成粗活改走小模型后降到 125 元左右,一年省六万多,用户感知不到差别。
    5. 还要主动说出实现上的取舍:先按任务类型静态分档,不要一上来就做「让模型判断该用哪个模型」的动态路由——那个方案本身又要多一次模型调用,延迟和成本可能得不偿失,等有真实数据再优化。
    6. 可以预期的追问:怎么验证降档没有损失质量?答案是准备 golden set,对同一批输入跑两档模型,用人工或 LLM-as-judge 比对准确率,把降档决策建立在数据上而不是感觉上。

    Key points

    • Model pricing spans 50x or more, so a flagship doing intent detection is obvious waste
    • Three routing dimensions: task type, latency requirement, and input length
    • Add a tier parameter to the call layer, pick the starting provider statically, and reuse the fallback chain
    • Prefer static tiers first — dynamic model-picks-model routing adds a call and may not pay off
    • Validate downgrades against a golden set rather than intuition

    答题要点

    • 不同模型价格能差 50 倍以上,用旗舰模型做意图识别是明显的浪费
    • 三个路由维度:任务类型(分类抽取 vs 推理生成)、延迟要求(前台 vs 后台)、输入长度(是否需要超长上下文)
    • 实现上给调用层加 tier 参数,按任务静态分档挑起始 provider,fallback 逻辑完全复用
    • 先静态分档再考虑动态路由,让模型判断该用哪个模型本身要多一次调用,可能得不偿失
    • 用 golden set 对比两档模型的准确率,把降档决策建立在数据上

Comments