PydanticAI vs LangChain Agents (порівняння для продакшену) + код

  • Обери правильно без “демка вирішила” жалю.
  • Побач, що ламається в проді (ops, cost, drift).
  • Отримай міграційний шлях + decision checklist.
  • Забери defaults: budgets, validation, stop reasons.
PydanticAI тисне в бік типізованих I/O та інваріантів. LangChain Agents — у бік гнучкої оркестрації. У проді вирішують validation, replay і контроль tools.
На цій сторінці
  1. Проблема (з реального продакшену)
  2. Швидке рішення (кому що обрати)
  3. Чому в проді обирають не те
  4. 1) They think a framework replaces governance
  5. 2) They treat structured outputs as “nice to have”
  6. 3) They over-index on integration count
  7. Таблиця порівняння
  8. Де це ламається в продакшені
  9. Typed-first breaks
  10. Flexible breaks
  11. Приклад реалізації (реальний код)
  12. Реальний інцидент (з цифрами)
  13. Шлях міграції (A → B)
  14. Flexible → typed-first
  15. Typed-first → flexible (when you need it)
  16. Гайд по вибору
  17. Компроміси
  18. Коли НЕ варто
  19. Чекліст (можна копіювати)
  20. Безпечний дефолтний конфіг (JSON/YAML)
  21. FAQ (3–5)
  22. Пов’язані сторінки (3–6 лінків)

Проблема (з реального продакшену)

At some point you’ll hit the same production problem: the model output shape matters more than the model prose.

If you’re calling tools, parsing JSON, and triggering side effects, you need:

  • schema validation
  • invariants
  • fail-closed behavior

That’s where “typed agent frameworks” are attractive. And where “flexible agent frameworks” can either help or hurt, depending on how much discipline your team has.

Швидке рішення (кому що обрати)

  • Pick PydanticAI-style typed outputs if your system is tool-heavy and you want validation to be the default, not an afterthought.
  • Pick LangChain agents if you need flexibility across integrations and you’re willing to enforce schemas and governance yourself.
  • If you don’t validate outputs, it doesn’t matter which you pick — you’ll ship silent failures.

Чому в проді обирають не те

1) They think a framework replaces governance

No framework replaces:

  • budgets
  • tool permissions
  • monitoring
  • approvals for writes

2) They treat structured outputs as “nice to have”

In prod, structured outputs are how you prevent:

  • tool response corruption turning into actions
  • prompt injection steering tool calls
  • “close enough JSON” becoming “close enough incident”

3) They over-index on integration count

“It integrates with everything” isn’t a production plan. If your tool gateway is unsafe, more integrations just means more blast radius.

Таблиця порівняння

| Criterion | PydanticAI (typed-first) | LangChain agents (flexible) | What matters in prod | |---|---|---|---| | Default output validation | Strong | Depends on you | Fail closed | | Integration surface | Smaller | Larger | Blast radius | | Debuggability | Better if typed | Better if instrumented | Traces | | Failure handling | Explicit if enforced | Emergent if loose | Stop reasons | | Best for | Tool-heavy systems | Rapid integration | Team discipline |

Де це ламається в продакшені

Typed-first breaks

  • you still have to maintain schemas
  • you can over-constrain and reject useful outputs
  • teams misuse typing as “security” (it isn’t)

Flexible breaks

  • silent parse errors
  • “best effort” JSON coercion
  • tool outputs treated as instructions
  • drift changes output shapes without tests

Приклад реалізації (реальний код)

No matter what framework you use, put a strict validator between the model and side effects.

This shows a minimal typed decision object with fail-closed parsing.

PYTHON
from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True)
class Decision:
  kind: str  # "final" | "tool"
  tool: str | None
  args: dict[str, Any] | None
  answer: str | None


class InvalidDecision(RuntimeError):
  pass


def validate_decision(obj: Any) -> Decision:
  if not isinstance(obj, dict):
      raise InvalidDecision("expected object")
  kind = obj.get("kind")
  if kind not in {"final", "tool"}:
      raise InvalidDecision("invalid kind")
  if kind == "final":
      ans = obj.get("answer")
      if not isinstance(ans, str) or not ans.strip():
          raise InvalidDecision("missing answer")
      return Decision(kind="final", tool=None, args=None, answer=ans)
  tool = obj.get("tool")
  args = obj.get("args")
  if not isinstance(tool, str):
      raise InvalidDecision("missing tool")
  if not isinstance(args, dict):
      raise InvalidDecision("missing args")
  return Decision(kind="tool", tool=tool, args=args, answer=None)
JAVASCRIPT
export class InvalidDecision extends Error {}

export function validateDecision(obj) {
if (!obj || typeof obj !== "object") throw new InvalidDecision("expected object");
const kind = obj.kind;
if (kind !== "final" && kind !== "tool") throw new InvalidDecision("invalid kind");

if (kind === "final") {
  if (typeof obj.answer !== "string" || !obj.answer.trim()) throw new InvalidDecision("missing answer");
  return { kind: "final", answer: obj.answer };
}

if (typeof obj.tool !== "string") throw new InvalidDecision("missing tool");
if (!obj.args || typeof obj.args !== "object") throw new InvalidDecision("missing args");
return { kind: "tool", tool: obj.tool, args: obj.args };
}

Реальний інцидент (з цифрами)

We saw a team ship a flexible agent that parsed “tool calls” with best-effort JSON extraction.

During a partial outage, tool output included an HTML error page. The model copied part of it into the “args”. The parser coerced it into a dict.

Impact:

  • 17 runs wrote garbage data into a queue
  • downstream workers crashed for ~25 minutes
  • on-call spent ~2 hours tracing the root cause because logs only had the final answer

Fix:

  1. strict parsing + schema validation for decisions and tool outputs
  2. fail closed before any write
  3. monitoring for invalid_decision_rate

Typed outputs didn’t solve this alone — strict validation did.

Шлях міграції (A → B)

Flexible → typed-first

  1. add schema validation at the boundary (model output + tool output)
  2. define a small decision schema (tool vs final)
  3. gradually type the high-risk parts (writes) first

Typed-first → flexible (when you need it)

  1. keep typed boundaries for actions and tools
  2. allow free-form text only inside “analysis” fields that never trigger side effects

Гайд по вибору

  • If your system does writes → prioritize typed/validated boundaries.
  • If you’re doing experiments → flexibility is fine, but keep budgets and logging.
  • If you’re multi-tenant → strict validation is non-negotiable.

Компроміси

  • Validation rejects some outputs. That’s good. It forces you to handle the failure path.
  • Typing adds maintenance overhead.
  • Flexibility can ship faster, but it ships more production surprises too.

Коли НЕ варто

  • Don’t rely on typing as security. You still need permissions and approvals.
  • Don’t use best-effort parsing for tool calls that trigger writes.
  • Don’t skip monitoring. Validation failures are a metric, not a shame.

Чекліст (можна копіювати)

  • [ ] Validate model decisions (schema) before acting
  • [ ] Validate tool outputs (schema + invariants)
  • [ ] Fail closed for writes
  • [ ] Budgets + stop reasons
  • [ ] Audit logs for tool calls
  • [ ] Canary changes; drift is real

Безпечний дефолтний конфіг (JSON/YAML)

YAML
validation:
  model_decision:
    fail_closed: true
    schema: "Decision(kind, tool?, args?, answer?)"
  tool_output:
    fail_closed: true
    max_chars: 200000
budgets:
  max_steps: 25
  max_tool_calls: 12
monitoring:
  track: ["invalid_decision_rate", "tool_output_invalid_rate", "stop_reason"]

FAQ (3–5)

Does typing guarantee correctness?
No. It guarantees shape. You still need invariants, permissions, and safe-mode behavior.
Is LangChain ‘unsafe’?
No. It’s flexible. Safety comes from how you enforce boundaries: budgets, validation, and a tool gateway.
What should we type first?
Anything that triggers writes or money: tool calls, approvals, budget policy outputs.
Can strict validation hurt completion rate?
Yes. That’s usually the point: stop guessing and handle failure paths explicitly.

Q: Does typing guarantee correctness?
A: No. It guarantees shape. You still need invariants, permissions, and safe-mode behavior.

Q: Is LangChain ‘unsafe’?
A: No. It’s flexible. Safety comes from how you enforce boundaries: budgets, validation, and a tool gateway.

Q: What should we type first?
A: Anything that triggers writes or money: tool calls, approvals, budget policy outputs.

Q: Can strict validation hurt completion rate?
A: Yes. That’s usually the point: stop guessing and handle failure paths explicitly.

Пов’язані сторінки (3–6 лінків)

Не впевнені, що це ваш кейс?

Спроєктувати агента →
⏱️ 7 хв читанняОновлено Бер, 2026Складність: ★★☆
Інтегровано: продакшен-контрольOnceOnly
Додай guardrails до агентів з tool-calling
Зашип цей патерн з governance:
  • Бюджетами (кроки / ліміти витрат)
  • Дозволами на інструменти (allowlist / blocklist)
  • Kill switch та аварійна зупинка
  • Ідемпотентність і dedupe
  • Audit logs та трасування
Інтегрована згадка: OnceOnly — контрольний шар для продакшен агент-систем.
Автор

Цю документацію курують і підтримують інженери, які запускають AI-агентів у продакшені.

Контент створено з допомогою AI, із людською редакторською відповідальністю за точність, ясність і продакшн-релевантність.

Патерни та рекомендації базуються на постмортемах, режимах відмов і операційних інцидентах у розгорнутих системах, зокрема під час розробки та експлуатації governance-інфраструктури для агентів у OnceOnly.