codeforge-dev 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/.devcontainer/.env +22 -0
  2. package/.devcontainer/CHANGELOG.md +197 -0
  3. package/.devcontainer/CLAUDE.md +117 -0
  4. package/.devcontainer/README.md +222 -0
  5. package/.devcontainer/config/main-system-prompt.md +502 -0
  6. package/.devcontainer/config/settings.json +47 -0
  7. package/.devcontainer/devcontainer.json +94 -0
  8. package/.devcontainer/features/README.md +113 -0
  9. package/.devcontainer/features/agent-browser/README.md +65 -0
  10. package/.devcontainer/features/agent-browser/devcontainer-feature.json +23 -0
  11. package/.devcontainer/features/agent-browser/install.sh +79 -0
  12. package/.devcontainer/features/ast-grep/README.md +24 -0
  13. package/.devcontainer/features/ast-grep/devcontainer-feature.json +24 -0
  14. package/.devcontainer/features/ast-grep/install.sh +51 -0
  15. package/.devcontainer/features/ccstatusline/README.md +296 -0
  16. package/.devcontainer/features/ccstatusline/devcontainer-feature.json +19 -0
  17. package/.devcontainer/features/ccstatusline/install.sh +290 -0
  18. package/.devcontainer/features/ccusage/README.md +205 -0
  19. package/.devcontainer/features/ccusage/devcontainer-feature.json +38 -0
  20. package/.devcontainer/features/ccusage/install.sh +132 -0
  21. package/.devcontainer/features/claude-code/README.md +498 -0
  22. package/.devcontainer/features/claude-code/config/settings.json +36 -0
  23. package/.devcontainer/features/claude-code/config/system-prompt.md +118 -0
  24. package/.devcontainer/features/claude-code/config/world-building-sp.md +1432 -0
  25. package/.devcontainer/features/claude-code/devcontainer-feature.json +42 -0
  26. package/.devcontainer/features/claude-code/install.sh +466 -0
  27. package/.devcontainer/features/claude-monitor/README.md +74 -0
  28. package/.devcontainer/features/claude-monitor/devcontainer-feature.json +38 -0
  29. package/.devcontainer/features/claude-monitor/install.sh +99 -0
  30. package/.devcontainer/features/lsp-servers/README.md +85 -0
  31. package/.devcontainer/features/lsp-servers/devcontainer-feature.json +40 -0
  32. package/.devcontainer/features/lsp-servers/install.sh +116 -0
  33. package/.devcontainer/features/mcp-qdrant/CHANGES.md +399 -0
  34. package/.devcontainer/features/mcp-qdrant/README.md +474 -0
  35. package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +57 -0
  36. package/.devcontainer/features/mcp-qdrant/install.sh +295 -0
  37. package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +129 -0
  38. package/.devcontainer/features/mcp-reasoner/README.md +177 -0
  39. package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +20 -0
  40. package/.devcontainer/features/mcp-reasoner/install.sh +177 -0
  41. package/.devcontainer/features/mcp-reasoner/poststart-hook.sh +67 -0
  42. package/.devcontainer/features/notify-hook/README.md +86 -0
  43. package/.devcontainer/features/notify-hook/devcontainer-feature.json +23 -0
  44. package/.devcontainer/features/notify-hook/install.sh +38 -0
  45. package/.devcontainer/features/splitrail/README.md +140 -0
  46. package/.devcontainer/features/splitrail/devcontainer-feature.json +34 -0
  47. package/.devcontainer/features/splitrail/install.sh +129 -0
  48. package/.devcontainer/features/tree-sitter/README.md +138 -0
  49. package/.devcontainer/features/tree-sitter/devcontainer-feature.json +52 -0
  50. package/.devcontainer/features/tree-sitter/install.sh +173 -0
  51. package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +106 -0
  52. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +7 -0
  53. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/hooks/hooks.json +17 -0
  54. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-file.py +101 -0
  55. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +7 -0
  56. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +17 -0
  57. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +137 -0
  58. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/.claude-plugin/plugin.json +8 -0
  59. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/SKILL.md +387 -0
  60. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/cli-flags-and-output.md +312 -0
  61. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/sdk-and-mcp.md +569 -0
  62. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/SKILL.md +309 -0
  63. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/compose-services.md +438 -0
  64. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/dockerfile-patterns.md +340 -0
  65. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/SKILL.md +412 -0
  66. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/container-lifecycle.md +388 -0
  67. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/resources-and-security.md +444 -0
  68. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/SKILL.md +344 -0
  69. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/middleware-and-lifespan.md +254 -0
  70. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/pydantic-models.md +245 -0
  71. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/routing-and-dependencies.md +255 -0
  72. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/sse-and-streaming.md +318 -0
  73. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/SKILL.md +345 -0
  74. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/agents-and-tools.md +271 -0
  75. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/models-and-streaming.md +422 -0
  76. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/SKILL.md +220 -0
  77. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/cross-vendor-principles.md +139 -0
  78. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/patterns-and-antipatterns.md +376 -0
  79. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/skill-authoring-patterns.md +356 -0
  80. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/SKILL.md +329 -0
  81. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/advanced-queries.md +314 -0
  82. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/javascript-patterns.md +323 -0
  83. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/python-patterns.md +354 -0
  84. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/schema-and-pragmas.md +326 -0
  85. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/SKILL.md +356 -0
  86. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/ai-sdk-svelte.md +128 -0
  87. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/component-patterns.md +332 -0
  88. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/layercake.md +203 -0
  89. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/migration-guide.md +350 -0
  90. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/runes-and-reactivity.md +328 -0
  91. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/spa-and-routing.md +262 -0
  92. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/svelte-dnd-action.md +181 -0
  93. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/SKILL.md +414 -0
  94. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/fastapi-testing.md +411 -0
  95. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/svelte-testing.md +538 -0
  96. package/.devcontainer/plugins/devs-marketplace/plugins/codeforge-lsp/.claude-plugin/plugin.json +7 -0
  97. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/.claude-plugin/plugin.json +7 -0
  98. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/hooks/hooks.json +17 -0
  99. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +110 -0
  100. package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/.claude-plugin/plugin.json +7 -0
  101. package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +17 -0
  102. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +7 -0
  103. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +17 -0
  104. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/.claude-plugin/plugin.json +7 -0
  105. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/hooks/hooks.json +17 -0
  106. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +108 -0
  107. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272create-pr.md +337 -0
  108. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272new.md +166 -0
  109. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272review-commit.md +290 -0
  110. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272work.md +257 -0
  111. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/plugin.json +8 -0
  112. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/system-prompt.md +184 -0
  113. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +6 -0
  114. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +14 -0
  115. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +989 -0
  116. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +33 -0
  117. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
  118. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +71 -0
  119. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +68 -0
  120. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +120 -0
  121. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +133 -0
  122. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +253 -0
  123. package/.devcontainer/scripts/setup-aliases.sh +80 -0
  124. package/.devcontainer/scripts/setup-config.sh +28 -0
  125. package/.devcontainer/scripts/setup-irie-claude.sh +32 -0
  126. package/.devcontainer/scripts/setup-plugins.sh +80 -0
  127. package/.devcontainer/scripts/setup.sh +58 -0
  128. package/LICENSE.txt +674 -0
  129. package/README.md +267 -0
  130. package/package.json +44 -0
  131. package/setup.js +83 -0
@@ -0,0 +1,271 @@
1
+ # Agents and Tools -- Deep Dive
2
+
3
+ ## 1. Agent Constructor Details
4
+
5
+ The `Agent` class is generic: `Agent[AgentDepsT, OutputDataT]`. The full constructor accepts:
6
+
7
+ ```python
8
+ from pydantic_ai import Agent, Tool
9
+
10
+ agent = Agent(
11
+ model="anthropic:claude-sonnet-4-20250514", # or Model instance, or None
12
+ output_type=str, # BaseModel, dataclass, TypedDict, list, ToolOutput, etc.
13
+ instructions="Be concise.", # static str or Callable[[RunContext], str]
14
+ system_prompt="Always respond in JSON.", # static, preserved in history
15
+ deps_type=type(None), # type of the deps object
16
+ name="my-agent", # optional display name
17
+ model_settings={"temperature": 0}, # default ModelSettings
18
+ retries=1, # default tool retry count
19
+ end_strategy="early", # "early" or "exhaustive"
20
+ tools=[my_func, Tool(other_func, takes_ctx=False)],
21
+ tool_timeout=30.0, # global tool execution timeout in seconds
22
+ )
23
+ ```
24
+
25
+ ### Dynamic Instructions
26
+
27
+ Instructions can be a callable that receives `RunContext` and returns a string. This enables per-run customization based on dependencies:
28
+
29
+ ```python
30
+ agent = Agent(
31
+ "openai:gpt-5",
32
+ deps_type=UserContext,
33
+ instructions=lambda ctx: f"The current user is {ctx.deps.username}. Respond in {ctx.deps.language}.",
34
+ )
35
+ ```
36
+
37
+ The `@agent.instructions` decorator is equivalent:
38
+
39
+ ```python
40
+ @agent.instructions
41
+ def build_instructions(ctx: RunContext[UserContext]) -> str:
42
+ return f"The current user is {ctx.deps.username}."
43
+ ```
44
+
45
+ Instructions differ from `system_prompt` in one critical way: instructions are re-evaluated per run and excluded when `message_history` is provided. System prompts are static and always included.
46
+
47
+ ---
48
+
49
+ ## 2. Tool Registration via Constructor
50
+
51
+ Tools can be registered via decorators or passed directly to the constructor. Constructor registration is useful when tools are defined in separate modules:
52
+
53
+ ```python
54
+ from pydantic_ai import Agent, Tool
55
+
56
+ async def search_docs(ctx: RunContext[Deps], query: str) -> str:
57
+ """Search the documentation."""
58
+ return await ctx.deps.search(query)
59
+
60
+ def get_time() -> str:
61
+ """Get the current UTC time."""
62
+ return datetime.utcnow().isoformat()
63
+
64
+ agent = Agent(
65
+ "openai:gpt-5",
66
+ deps_type=Deps,
67
+ tools=[
68
+ search_docs, # auto-detects RunContext
69
+ Tool(get_time, takes_ctx=False), # explicit: no context
70
+ Tool(search_docs, name="doc_search"), # custom name
71
+ ],
72
+ )
73
+ ```
74
+
75
+ The `Tool` wrapper accepts:
76
+ - **`takes_ctx`**: Explicitly declare whether the function expects `RunContext` as its first parameter.
77
+ - **`name`** / **`description`**: Override the function name or docstring.
78
+ - **`retries`**: Override the agent-level retry count.
79
+ - **`prepare`**: Dynamic preparation function.
80
+
81
+ ---
82
+
83
+ ## 3. Prepare Functions for Dynamic Tool Sets
84
+
85
+ A `prepare` function runs before each model request, allowing tools to be included, excluded, or modified based on the current run state:
86
+
87
+ ```python
88
+ from pydantic_ai import Agent, RunContext
89
+ from pydantic_ai.tools import ToolDefinition
90
+
91
+ async def filter_admin_tools(
92
+ ctx: RunContext[UserDeps], tool_def: ToolDefinition
93
+ ) -> ToolDefinition | None:
94
+ """Only include this tool if the user has admin role."""
95
+ if ctx.deps.user_role != "admin":
96
+ return None # exclude tool from this run
97
+ return tool_def # include unmodified
98
+
99
+ @agent.tool(prepare=filter_admin_tools)
100
+ async def delete_user(ctx: RunContext[UserDeps], user_id: int) -> str:
101
+ """Delete a user account permanently."""
102
+ await ctx.deps.db.execute("DELETE FROM users WHERE id = $1", user_id)
103
+ return f"Deleted user {user_id}"
104
+ ```
105
+
106
+ Returning `None` removes the tool. Returning a modified `ToolDefinition` changes the schema or description sent to the model. The agent-level `prepare_tools` parameter applies a prepare function to all tools.
107
+
108
+ ---
109
+
110
+ ## 4. Result Types and Validators
111
+
112
+ ### Output Type Patterns
113
+
114
+ **Single Pydantic model** -- the most common pattern:
115
+
116
+ ```python
117
+ class WeatherReport(BaseModel):
118
+ city: str
119
+ temperature_c: float
120
+ conditions: str
121
+
122
+ agent = Agent("openai:gpt-5", output_type=WeatherReport)
123
+ ```
124
+
125
+ **Union of types** -- the model chooses which type to return:
126
+
127
+ ```python
128
+ agent = Agent("openai:gpt-5", output_type=[WeatherReport, ErrorReport])
129
+ ```
130
+
131
+ Each type becomes a separate tool the model can call. The first successful tool call ends the run (with `end_strategy="early"`).
132
+
133
+ **Named tool outputs** -- control the tool names:
134
+
135
+ ```python
136
+ from pydantic_ai import ToolOutput
137
+
138
+ agent = Agent(
139
+ "openai:gpt-5",
140
+ output_type=[
141
+ ToolOutput(WeatherReport, name="return_weather", description="Return weather data"),
142
+ ToolOutput(ErrorReport, name="return_error", description="Report an error"),
143
+ ],
144
+ )
145
+ ```
146
+
147
+ **NativeOutput** -- use the model's built-in structured output (JSON mode):
148
+
149
+ ```python
150
+ from pydantic_ai import NativeOutput
151
+
152
+ agent = Agent("openai:gpt-5", output_type=NativeOutput(WeatherReport))
153
+ ```
154
+
155
+ Not all models support native structured output. Falls back to tool-based output automatically.
156
+
157
+ **PromptedOutput** -- inject the schema into the prompt instead of using tools:
158
+
159
+ ```python
160
+ from pydantic_ai import PromptedOutput
161
+
162
+ agent = Agent(
163
+ "openai:gpt-5",
164
+ output_type=PromptedOutput(WeatherReport, template="Respond with JSON matching: {schema}"),
165
+ )
166
+ ```
167
+
168
+ **TextOutput** -- process plain text through a function:
169
+
170
+ ```python
171
+ from pydantic_ai import TextOutput
172
+
173
+ def parse_csv(text: str) -> list[str]:
174
+ return [line.strip() for line in text.split(",")]
175
+
176
+ agent = Agent("openai:gpt-5", output_type=TextOutput(parse_csv))
177
+ ```
178
+
179
+ **Output functions** -- the model calls a function to produce the result:
180
+
181
+ ```python
182
+ def save_report(title: str, content: str, priority: int) -> str:
183
+ """Save a report to the database."""
184
+ db.save(Report(title=title, content=content, priority=priority))
185
+ return f"Saved: {title}"
186
+
187
+ agent = Agent("openai:gpt-5", output_type=[save_report])
188
+ ```
189
+
190
+ ### Result Validators
191
+
192
+ Validators run after the model produces output. They can transform the output or reject it:
193
+
194
+ ```python
195
+ @agent.output_validator
196
+ async def validate_weather(ctx: RunContext[Deps], output: WeatherReport) -> WeatherReport:
197
+ if output.temperature_c < -90 or output.temperature_c > 60:
198
+ raise ModelRetry("Temperature is outside realistic range. Check the data.")
199
+ return output
200
+ ```
201
+
202
+ Raising `ModelRetry` sends the error message back to the model for self-correction. The validator reruns up to `output_retries` times (defaults to `retries`).
203
+
204
+ During streaming, `ctx.partial_output` is `True` for intermediate validations. Skip side effects when partial:
205
+
206
+ ```python
207
+ @agent.output_validator
208
+ async def validate_with_side_effect(ctx: RunContext[Deps], output: Report) -> Report:
209
+ if not ctx.partial_output:
210
+ await ctx.deps.audit_log.record(output)
211
+ return output
212
+ ```
213
+
214
+ ---
215
+
216
+ ## 5. AgentRunResult
217
+
218
+ The return value of `agent.run()` and `agent.run_sync()`:
219
+
220
+ ```python
221
+ result = await agent.run("prompt", deps=deps)
222
+
223
+ result.output # typed OutputDataT
224
+ result.usage() # RunUsage(requests=N, input_tokens=..., output_tokens=...)
225
+ result.all_messages() # full conversation history including system prompts
226
+ result.new_messages() # only messages from this run (for conversation continuation)
227
+ result.run_id # unique identifier for this run
228
+ ```
229
+
230
+ ### Conversation Continuation
231
+
232
+ Pass `message_history` to continue a previous conversation:
233
+
234
+ ```python
235
+ result1 = await agent.run("What's the weather in Paris?", deps=deps)
236
+ result2 = await agent.run(
237
+ "What about London?",
238
+ deps=deps,
239
+ message_history=result1.all_messages(),
240
+ )
241
+ ```
242
+
243
+ When `message_history` is provided, `instructions` are not included (they were already in the history). `system_prompt` is always included.
244
+
245
+ ---
246
+
247
+ ## 6. ModelRetry Mechanics
248
+
249
+ When a tool raises `ModelRetry(message)`:
250
+
251
+ 1. The framework sends the error message back to the model as a tool-call error response.
252
+ 2. The model generates a new tool call (presumably with corrected arguments).
253
+ 3. The tool runs again with the new arguments.
254
+ 4. This repeats up to `retries` times (per-tool or per-agent setting).
255
+ 5. If retries are exhausted, the underlying `ValidationError` or `ModelRetry` propagates as an exception.
256
+
257
+ ```python
258
+ from pydantic_ai import ModelRetry
259
+
260
+ @agent.tool(retries=3)
261
+ async def query_api(ctx: RunContext[Deps], endpoint: str, params: dict) -> str:
262
+ """Query the external API."""
263
+ if not endpoint.startswith("/api/"):
264
+ raise ModelRetry("Endpoint must start with /api/. Available: /api/users, /api/orders")
265
+ response = await ctx.deps.http.get(endpoint, params=params)
266
+ if response.status_code == 404:
267
+ raise ModelRetry(f"Endpoint {endpoint} returned 404. Try a different endpoint.")
268
+ return response.text
269
+ ```
270
+
271
+ `ModelRetry` is distinct from Python exceptions -- it signals a recoverable error that the model can fix by adjusting its tool-call arguments.
@@ -0,0 +1,422 @@
1
+ # Models and Streaming -- Deep Dive
2
+
3
+ ## 1. Model Configuration
4
+
5
+ ### Model Identifiers
6
+
7
+ PydanticAI uses string identifiers in the format `provider:model-name`:
8
+
9
+ ```python
10
+ from pydantic_ai import Agent
11
+
12
+ # Anthropic
13
+ agent = Agent("anthropic:claude-sonnet-4-20250514")
14
+
15
+ # OpenAI
16
+ agent = Agent("openai:gpt-5")
17
+
18
+ # Google Gemini
19
+ agent = Agent("google-gla:gemini-2.0-flash")
20
+
21
+ # Groq
22
+ agent = Agent("groq:llama-3.3-70b-versatile")
23
+
24
+ # OpenAI-compatible endpoints (Ollama, vLLM, etc.)
25
+ from pydantic_ai.models.openai import OpenAIChatModel
26
+ model = OpenAIChatModel("llama3", base_url="http://localhost:11434/v1")
27
+ agent = Agent(model)
28
+ ```
29
+
30
+ Override the model at run time without changing the agent definition:
31
+
32
+ ```python
33
+ result = await agent.run("prompt", model="openai:gpt-5")
34
+ ```
35
+
36
+ ### ModelSettings
37
+
38
+ `ModelSettings` is a `TypedDict` controlling generation parameters. All fields are optional:
39
+
40
+ ```python
41
+ from pydantic_ai import Agent, ModelSettings
42
+
43
+ agent = Agent(
44
+ "openai:gpt-5",
45
+ model_settings=ModelSettings(
46
+ temperature=0.0,
47
+ max_tokens=2000,
48
+ top_p=0.95,
49
+ seed=42,
50
+ timeout=30.0,
51
+ parallel_tool_calls=True,
52
+ ),
53
+ )
54
+ ```
55
+
56
+ Precedence (lowest to highest):
57
+ 1. Model defaults
58
+ 2. `Agent(model_settings=...)` -- agent-level
59
+ 3. `agent.run(model_settings=...)` -- per-run
60
+
61
+ Available fields: `temperature`, `max_tokens`, `top_p`, `timeout`, `seed`, `parallel_tool_calls`, `presence_penalty`, `frequency_penalty`, `logit_bias`, `stop_sequences`, `extra_headers`, `extra_body`.
62
+
63
+ ---
64
+
65
+ ## 2. FallbackModel
66
+
67
+ Sequence multiple models for resilience. The framework tries each model in order and switches on API errors:
68
+
69
+ ```python
70
+ from pydantic_ai.models.fallback import FallbackModel
71
+
72
+ fallback = FallbackModel(
73
+ "anthropic:claude-sonnet-4-20250514",
74
+ "openai:gpt-5",
75
+ "google-gla:gemini-2.0-flash",
76
+ )
77
+ agent = Agent(fallback)
78
+ ```
79
+
80
+ Behavior:
81
+ - Only `ModelHTTPError` and `ModelAPIError` trigger fallback by default.
82
+ - Validation errors (bad tool arguments, output schema mismatch) use the retry mechanism, not fallback.
83
+ - If all models fail, raises `FallbackExceptionGroup` containing all individual exceptions.
84
+ - Custom trigger: pass `fallback_on` to specify which exception types trigger fallback.
85
+
86
+ ```python
87
+ from pydantic_ai.exceptions import ModelHTTPError
88
+
89
+ fallback = FallbackModel(
90
+ "anthropic:claude-sonnet-4-20250514",
91
+ "openai:gpt-5",
92
+ fallback_on=(ModelHTTPError, TimeoutError),
93
+ )
94
+ ```
95
+
96
+ ---
97
+
98
+ ## 3. Streaming
99
+
100
+ ### Text Streaming
101
+
102
+ ```python
103
+ async with agent.run_stream("Tell me a story") as result:
104
+ # Cumulative text (each iteration includes all previous text)
105
+ async for text in result.stream_text():
106
+ print(text)
107
+
108
+ # Delta mode (each iteration is only the new chunk)
109
+ async for delta in result.stream_text(delta=True):
110
+ print(delta, end="", flush=True)
111
+ ```
112
+
113
+ ### Structured Output Streaming
114
+
115
+ When the agent has a Pydantic output type, stream partially-validated objects:
116
+
117
+ ```python
118
+ from pydantic import BaseModel
119
+
120
+ class Story(BaseModel):
121
+ title: str
122
+ chapters: list[str]
123
+
124
+ agent = Agent("openai:gpt-5", output_type=Story)
125
+
126
+ async with agent.run_stream("Write a story") as result:
127
+ async for partial_story in result.stream_output(debounce_by=0.1):
128
+ # partial_story is a Story instance with whatever fields have been parsed
129
+ print(partial_story.title if hasattr(partial_story, 'title') else "...")
130
+ ```
131
+
132
+ The `debounce_by` parameter (default `0.1` seconds) batches rapid chunks to reduce Pydantic validation overhead. Set to `None` for maximum responsiveness.
133
+
134
+ ### Sync Streaming
135
+
136
+ ```python
137
+ with agent.run_stream_sync("Tell me a story") as result:
138
+ for delta in result.stream_text(delta=True):
139
+ print(delta, end="", flush=True)
140
+ ```
141
+
142
+ ### StreamedRunResult API
143
+
144
+ ```python
145
+ class StreamedRunResult:
146
+ # Streaming iterators
147
+ async def stream_text(delta: bool = False, debounce_by: float | None = 0.1) -> AsyncIterator[str]
148
+ async def stream_output(debounce_by: float | None = 0.1) -> AsyncIterator[OutputDataT]
149
+ async def get_output() -> OutputDataT # consume stream, return final result
150
+
151
+ # State
152
+ is_complete: bool
153
+
154
+ # Messages and usage
155
+ def all_messages() -> list[ModelMessage]
156
+ def new_messages() -> list[ModelMessage]
157
+ def usage() -> RunUsage
158
+ ```
159
+
160
+ ---
161
+
162
+ ## 4. VercelAIAdapter
163
+
164
+ `VercelAIAdapter` bridges PydanticAI agents to Vercel AI SDK frontends. It translates PydanticAI's streaming events into the Vercel AI Data Stream Protocol.
165
+
166
+ ### FastAPI Integration
167
+
168
+ ```python
169
+ from fastapi import FastAPI, Request, Response
170
+ from pydantic_ai import Agent
171
+ from pydantic_ai.ui.vercel_ai import VercelAIAdapter
172
+
173
+ app = FastAPI()
174
+ agent = Agent("anthropic:claude-sonnet-4-20250514", instructions="Be helpful.")
175
+
176
+ @app.post("/chat")
177
+ async def chat(request: Request) -> Response:
178
+ return await VercelAIAdapter.dispatch_request(request, agent=agent)
179
+ ```
180
+
181
+ `dispatch_request` handles the full lifecycle:
182
+ 1. Parses the Vercel AI SDK request body (messages, model settings).
183
+ 2. Converts frontend messages to PydanticAI's `ModelMessage` format.
184
+ 3. Runs the agent in streaming mode.
185
+ 4. Encodes PydanticAI events as Vercel AI Data Stream Protocol SSE.
186
+ 5. Returns a `StreamingResponse`.
187
+
188
+ ### With Dependencies
189
+
190
+ ```python
191
+ @app.post("/chat")
192
+ async def chat(request: Request, db: DB) -> Response:
193
+ return await VercelAIAdapter.dispatch_request(
194
+ request,
195
+ agent=agent,
196
+ deps=AppDeps(db=db),
197
+ )
198
+ ```
199
+
200
+ ### Manual Usage (Non-Starlette)
201
+
202
+ ```python
203
+ adapter = VercelAIAdapter(agent=agent, run_input=request_data, accept="text/event-stream")
204
+ event_stream = adapter.run_stream() # AsyncIterator[VercelAIEvent]
205
+ sse_stream = adapter.encode_stream(event_stream) # AsyncIterator[str] (SSE-formatted)
206
+ ```
207
+
208
+ ### Message Conversion
209
+
210
+ Convert between PydanticAI and Vercel AI message formats:
211
+
212
+ ```python
213
+ # Frontend messages -> PydanticAI messages
214
+ pydantic_messages = VercelAIAdapter.load_messages(ui_messages)
215
+
216
+ # PydanticAI messages -> frontend messages
217
+ ui_messages = VercelAIAdapter.dump_messages(pydantic_messages)
218
+ ```
219
+
220
+ ### Svelte Frontend
221
+
222
+ ```svelte
223
+ <script>
224
+ import { useChat } from '@ai-sdk/svelte';
225
+ const { messages, input, handleSubmit, isLoading } = useChat({ api: '/chat' });
226
+ </script>
227
+
228
+ <div>
229
+ {#each $messages as message}
230
+ <p><strong>{message.role}:</strong> {message.content}</p>
231
+ {/each}
232
+ </div>
233
+
234
+ <form onsubmit={handleSubmit}>
235
+ <input bind:value={$input} placeholder="Type a message..." />
236
+ <button type="submit" disabled={$isLoading}>Send</button>
237
+ </form>
238
+ ```
239
+
240
+ The `useChat` hook manages conversation state, streaming, and message history automatically. The protocol translation is fully handled by `VercelAIAdapter` on the backend.
241
+
242
+ ---
243
+
244
+ ## 5. TestModel
245
+
246
+ `TestModel` is a procedural model that calls all registered tools and generates data matching output schemas without making real API calls:
247
+
248
+ ```python
249
+ from pydantic_ai.models.test import TestModel
250
+
251
+ with agent.override(model=TestModel()):
252
+ result = agent.run_sync("test prompt")
253
+ assert isinstance(result.output, CityInfo)
254
+ ```
255
+
256
+ `TestModel` behavior:
257
+ - Calls each registered tool once with generated arguments matching the tool's parameter schema.
258
+ - If the agent has a structured `output_type`, generates a response matching the output schema.
259
+ - If the output type is `str`, returns `"Final response"` (or a custom string).
260
+
261
+ Custom text output:
262
+
263
+ ```python
264
+ with agent.override(model=TestModel(custom_output_text="Paris is beautiful")):
265
+ result = agent.run_sync("Tell me about Paris")
266
+ assert result.output == "Paris is beautiful"
267
+ ```
268
+
269
+ ### Override for Dependency Swapping
270
+
271
+ `agent.override()` replaces both model and dependencies:
272
+
273
+ ```python
274
+ mock_deps = AppDeps(db=FakeDB(), cache=FakeCache(), api_key="test")
275
+
276
+ with agent.override(model=TestModel(), deps=mock_deps):
277
+ result = agent.run_sync("test")
278
+ ```
279
+
280
+ ---
281
+
282
+ ## 6. FunctionModel
283
+
284
+ Full control over model responses via a callback function:
285
+
286
+ ```python
287
+ from pydantic_ai.models.function import FunctionModel, AgentInfo
288
+ from pydantic_ai.messages import ModelResponse, TextPart, ToolCallPart
289
+
290
+ def my_model(messages: list[ModelMessage], info: AgentInfo) -> ModelResponse:
291
+ # info.function_tools lists available tools with their schemas
292
+ if info.function_tools:
293
+ tool = info.function_tools[0]
294
+ return ModelResponse(parts=[
295
+ ToolCallPart(tool_name=tool.name, args={"query": "test"})
296
+ ])
297
+ return ModelResponse(parts=[TextPart("mock response")])
298
+
299
+ with agent.override(model=FunctionModel(my_model)):
300
+ result = await agent.run("test")
301
+ ```
302
+
303
+ The callback receives:
304
+ - **`messages`**: The full conversation history as `list[ModelMessage]`.
305
+ - **`info`**: An `AgentInfo` object with `function_tools` (list of tool definitions), `output_tools` (output schema tools), `model_settings`, and `allow_text_output`.
306
+
307
+ ### Async FunctionModel
308
+
309
+ ```python
310
+ async def async_model(messages, info: AgentInfo) -> ModelResponse:
311
+ await asyncio.sleep(0.1) # simulate latency
312
+ return ModelResponse(parts=[TextPart("async mock")])
313
+
314
+ with agent.override(model=FunctionModel(async_model)):
315
+ result = await agent.run("test")
316
+ ```
317
+
318
+ ### Streaming FunctionModel
319
+
320
+ Return a `StreamedResponse` for streaming tests:
321
+
322
+ ```python
323
+ from pydantic_ai.models.function import FunctionModel, StreamedResponse
324
+
325
+ def streaming_model(messages, info):
326
+ return StreamedResponse(
327
+ model_name="test-model",
328
+ timestamp=datetime.now(),
329
+ )
330
+ ```
331
+
332
+ ---
333
+
334
+ ## 7. Usage Tracking
335
+
336
+ ### RunUsage
337
+
338
+ Every run returns usage statistics:
339
+
340
+ ```python
341
+ result = await agent.run("prompt")
342
+ usage = result.usage()
343
+
344
+ print(usage.requests) # number of API round-trips
345
+ print(usage.input_tokens) # total input tokens across all requests
346
+ print(usage.output_tokens) # total output tokens
347
+ print(usage.total_tokens) # input + output
348
+ print(usage.tool_calls) # number of tool executions
349
+ ```
350
+
351
+ ### UsageLimits
352
+
353
+ Enforce hard limits on resource consumption:
354
+
355
+ ```python
356
+ from pydantic_ai import UsageLimits
357
+
358
+ result = await agent.run(
359
+ "prompt",
360
+ usage_limits=UsageLimits(
361
+ request_limit=10, # max API requests (default 50)
362
+ input_tokens_limit=10000,
363
+ output_tokens_limit=5000,
364
+ total_tokens_limit=15000,
365
+ tool_calls_limit=20,
366
+ ),
367
+ )
368
+ ```
369
+
370
+ Exceeding any limit raises `UsageLimitExceeded`. The default `request_limit` is 50 -- this prevents infinite tool-call loops.
371
+
372
+ ### Aggregating Usage Across Runs
373
+
374
+ ```python
375
+ total_usage = RunUsage()
376
+
377
+ for prompt in prompts:
378
+ result = await agent.run(prompt)
379
+ total_usage += result.usage()
380
+
381
+ print(f"Total tokens: {total_usage.total_tokens}")
382
+ print(f"Total requests: {total_usage.requests}")
383
+ ```
384
+
385
+ ---
386
+
387
+ ## 8. Capturing Messages for Assertions
388
+
389
+ Use `capture_run_messages` to inspect the full message exchange in tests:
390
+
391
+ ```python
392
+ from pydantic_ai import capture_run_messages
393
+
394
+ with capture_run_messages() as messages:
395
+ result = await agent.run("What's the weather?", deps=deps)
396
+
397
+ # messages is a list of ModelMessage objects
398
+ assert any("weather" in str(m) for m in messages)
399
+ ```
400
+
401
+ This captures all messages exchanged during the run, including system prompts, user messages, tool calls, tool responses, and the final output. Useful for verifying that the agent called the expected tools with the expected arguments.
402
+
403
+ ---
404
+
405
+ ## 9. Preventing Accidental Real Requests
406
+
407
+ In test suites, set the global guard at the module or conftest level:
408
+
409
+ ```python
410
+ # conftest.py
411
+ import pydantic_ai.models
412
+ pydantic_ai.models.ALLOW_MODEL_REQUESTS = False
413
+ ```
414
+
415
+ Any attempt to use a real model (not `TestModel` or `FunctionModel`) raises an error immediately. Use `agent.override()` to swap in test models:
416
+
417
+ ```python
418
+ def test_agent_behavior():
419
+ with agent.override(model=TestModel()):
420
+ result = agent.run_sync("test")
421
+ assert result.output is not None
422
+ ```