Skip to content

Commit a0ff438

Browse files
committed
Refine README for production readiness; align docs on v1.0-first-print tag
Adds CI badge, teaching-samples disclaimer, configuration/secrets section, cost-expectations callout, CI section, and contributing guide. Fixes layout tree to match disk, drops review-jargon labels, switches main->master in the versioning table, and renames every `v1.0-print-ready` reference in docs to the actual `v1.0-first-print` tag.
1 parent e84017f commit a0ff438

4 files changed

Lines changed: 118 additions & 70 deletions

File tree

README.md

Lines changed: 114 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,97 @@
11
# Generative AI in .NET -- Companion Code
22

3-
Runnable code samples for **Generative AI in .NET** (Najaf Shaikh).
3+
[![CI](https://github.com/CodeShayk/generative-ai-dotnet-samples/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/CodeShayk/generative-ai-dotnet-samples/actions/workflows/ci.yml)
44

5-
Every sample in this repo corresponds to a section of the printed book. The full map between book and code lives in [`docs/citation-index.md`](docs/citation-index.md). When the book has been condensed in favor of pointing here, the manuscript cites a **tag** (e.g. `v1.0-print-ready`) so the code stays aligned with the print run.
5+
Runnable code samples for **Generative AI in .NET** by Najaf Shaikh.
6+
7+
Every sample in this repo corresponds to a section of the printed book. The full book-to-code map lives in [`docs/citation-index.md`](docs/citation-index.md). When the book condenses a section in favor of pointing here, the manuscript cites a **tag** (e.g. `v1.0-first-print`) so the cited code matches the print run -- `master` will drift as the surrounding APIs evolve.
8+
9+
> **About this code.** These are **teaching samples**, not production-grade libraries. They are written for clarity over coverage: each sample demonstrates a single technique, with error handling, retry, observability, and configuration shown to a useful depth rather than exhaustively. Patterns covered -- prompt caching, resilience, evaluation, model routing, output guards, MCP transports -- are real and copy-pasteable, but hardening, threat-modeling, and cost-bounding them for your environment is your responsibility.
610
711
## Layout
812

913
```
10-
samples/
11-
ch01-foundations/
12-
01.3-hello-chat/ -- IChatClient with provider selection
13-
01.3-embeddings/ -- IEmbeddingGenerator + cosine similarity
14-
01.4-secrets-and-config/ -- appsettings + user-secrets + env vars
15-
ch02-extensions-ai/
16-
02.1-console-chat-loop/ -- Sliding-window chat REPL
17-
02.2-streaming-aspnet/ -- SSE streaming in a Minimal API
18-
02.2-structured-output/ -- GetResponseAsync<T>()
19-
02.3-function-calling/ -- Three-tool weather + calendar + reminders
20-
02.4-middleware-pipeline/ -- Logging + caching pipeline
21-
02.4-custom-middleware/ -- Token-budget DelegatingChatClient
22-
ch03-rag/
23-
03.1-semantic-search/ -- Embedding + cosine over a product catalog
24-
03.2-rag-basic/ -- Retrieve / augment / generate
25-
03.2-ingestion-pipeline/ -- Load + chunk + hash + embed + upsert
26-
03.2.7-evaluation/ -- LLM-as-judge for RAG (faithfulness + relevance)
27-
03.3-vision-extract/ -- Receipt image -> typed Receipt record
28-
03.5-local-rag/ -- Local-only RAG (Ollama)
29-
ch04-agent-framework/
30-
04.2.1-hello-agent/ -- ChatClientAgent hello world
31-
04.2.4-anthropic-agents/ -- Same agent API, Claude under the hood
32-
04.3-persistent-session/ -- AgentThread serialize / resume
33-
04.4-tools-and-approval/ -- Function tools + RequiresApproval marker
34-
04.5-agent-middleware/ -- Custom run-middleware
35-
04.6.3-text-processing-walkthrough/ -- Linear workflow walkthrough
36-
04.6.7-content-workflow/ -- Researcher / Writer / Editor multi-agent
37-
04.7-a2a-server/ -- Expose an agent via A2A protocol
38-
04.8-agent-with-mcp/ -- Agent with mixed local + MCP tools
39-
ch05-mcp/
40-
05.2.10-inventory-server/ -- Full MCP server (tools + resources + prompts)
41-
05.3.1-stdio-transport/ -- Minimal stdio server
42-
05.3.2-sse-transport/ -- ASP.NET Core SSE transport
43-
05.3.3-streamable-http/ -- Streamable HTTP (recommended)
44-
05.4-mcp-client/ -- Interactive MCP client REPL
45-
05.6.3-mcp-agent-factory/ -- McpAgentFactory
46-
cached-mcp-tool-provider/ -- CachedMcpToolProvider (Critical-1 fix)
47-
ch06-production/
48-
06.1-observability/ -- OpenTelemetry tracing + OTLP export
49-
06.2.1-resilience/ -- Standard resilience handler
50-
06.2.4-model-routing/ -- Classify-then-route cost optimization
51-
06.5.1-unit-testing/ -- xUnit + StubChatClient
52-
06.5.3-llm-as-judge/ -- Evaluation harness
53-
output-guard-chat-client/ -- OutputGuardChatClient (Critical-3 fix)
54-
docs/
55-
citation-index.md
56-
version-matrix.md
57-
.github/workflows/ci.yml
58-
Directory.Packages.props
59-
global.json
14+
generative-ai-dotnet-samples/
15+
├── AI in .Net.sln -- solution covering every sample + tests
16+
├── Directory.Packages.props -- centralized NuGet versions
17+
├── global.json -- .NET 9 SDK pin
18+
├── LICENSE -- MIT
19+
├── docs/
20+
│ ├── citation-index.md -- book-section -> sample-path map
21+
│ └── version-matrix.md -- pinned package versions per chapter
22+
├── tests/ -- shared test infrastructure
23+
├── .github/workflows/ci.yml -- build matrix (push, PR, weekly)
24+
└── samples/
25+
├── Directory.Build.props -- shared MSBuild properties
26+
├── ch01-foundations/
27+
│ ├── 01.3-hello-chat/ -- IChatClient with provider selection
28+
│ ├── 01.3-embeddings/ -- IEmbeddingGenerator + cosine similarity
29+
│ └── 01.4-secrets-and-config/ -- appsettings + user-secrets + env vars
30+
├── ch02-extensions-ai/
31+
│ ├── 02.1-console-chat-loop/ -- Sliding-window chat REPL
32+
│ ├── 02.2-streaming-aspnet/ -- SSE streaming in a Minimal API
33+
│ ├── 02.2-structured-output/ -- GetResponseAsync<T>()
34+
│ ├── 02.3-function-calling/ -- Weather + calendar + reminders tools
35+
│ ├── 02.4-middleware-pipeline/ -- Logging + caching pipeline
36+
│ └── 02.4-custom-middleware/ -- Token-budget DelegatingChatClient
37+
├── ch03-rag/
38+
│ ├── 03.1-semantic-search/ -- Embedding + cosine over a catalog
39+
│ ├── 03.2-rag-basic/ -- Retrieve / augment / generate
40+
│ ├── 03.2-ingestion-pipeline/ -- Load + chunk + hash + embed + upsert
41+
│ ├── 03.2.7-evaluation/ -- LLM-as-judge for RAG (faithfulness + relevance)
42+
│ ├── 03.3-vision-extract/ -- Receipt image -> typed Receipt record
43+
│ └── 03.5-local-rag/ -- Local-only RAG (Ollama)
44+
├── ch04-agent-framework/
45+
│ ├── 04.2.1-hello-agent/ -- ChatClientAgent hello world
46+
│ ├── 04.2.4-anthropic-agents/ -- Same agent API, Claude under the hood
47+
│ ├── 04.3-persistent-session/ -- Agent session serialize / resume
48+
│ ├── 04.4-tools-and-approval/ -- Function tools + RequiresApproval marker
49+
│ ├── 04.5-agent-middleware/ -- Custom run-middleware
50+
│ ├── 04.6.3-text-processing-walkthrough/ -- Linear workflow walkthrough
51+
│ ├── 04.6.7-content-workflow/ -- Researcher / Writer / Editor multi-agent
52+
│ ├── 04.7-a2a-server/ -- Expose an agent via A2A protocol
53+
│ └── 04.8-agent-with-mcp/ -- Agent with mixed local + MCP tools
54+
├── ch05-mcp/
55+
│ ├── 05.2.10-inventory-server/ -- Full MCP server (tools + resources + prompts)
56+
│ ├── 05.3.1-stdio-transport/ -- Minimal stdio server
57+
│ ├── 05.3.2-sse-transport/ -- ASP.NET Core SSE transport (legacy)
58+
│ ├── 05.3.3-streamable-http/ -- Streamable HTTP (recommended)
59+
│ ├── 05.4-mcp-client/ -- Interactive MCP client REPL
60+
│ ├── 05.6.3-mcp-agent-factory/ -- McpAgentFactory
61+
│ └── cached-mcp-tool-provider/ -- CachedMcpToolProvider (caching wrapper)
62+
├── ch06-production/
63+
│ ├── 06.1-observability/ -- OpenTelemetry tracing + OTLP export
64+
│ ├── 06.2.1-resilience/ -- Standard resilience handler
65+
│ ├── 06.2.4-model-routing/ -- Classify-then-route cost optimization
66+
│ ├── 06.5.1-unit-testing/ -- xUnit + StubChatClient
67+
│ ├── 06.5.3-llm-as-judge/ -- Evaluation harness
68+
│ └── output-guard-chat-client/ -- OutputGuardChatClient (PII + leak guard)
69+
└── appendix-a-packages/ -- Quick-start bundles from Appendix A
6070
```
6171

6272
## Prerequisites
6373

6474
- **.NET 9 SDK** (`dotnet --version` should be 9.0.x).
65-
- **Provider credentials** for samples that hit a live model -- each sample's README names what it needs:
75+
- **Provider credentials** for samples that hit a live model -- each sample's README names exactly which it needs:
6676
- `OPENAI_API_KEY` for OpenAI / GitHub Models samples.
6777
- `AZURE_OPENAI_ENDPOINT` + `AZURE_OPENAI_KEY` for Azure OpenAI samples.
6878
- `ANTHROPIC_API_KEY` for Claude samples.
69-
- **Local services** for offline samples: [Ollama](https://ollama.com/) for local inference; an MCP-compatible runtime (`dnx`) for some MCP samples.
79+
- **[Ollama](https://ollama.com/)** for the offline samples and any sample that lists Ollama as its default profile.
80+
81+
The repo uses **central package management** -- versions live in [`Directory.Packages.props`](Directory.Packages.props), shared MSBuild properties in [`samples/Directory.Build.props`](samples/Directory.Build.props). Project files only carry `<PackageReference Include="..." />` -- no version attributes.
82+
83+
## Configuration and secrets
7084

71-
The repo uses **central package management** -- versions live in [`Directory.Packages.props`](Directory.Packages.props), shared properties in [`samples/Directory.Build.props`](samples/Directory.Build.props). Project files only carry `<PackageReference Include="..." />` -- no version attributes.
85+
All samples read configuration in the standard .NET cascade: `appsettings.json` -> `appsettings.Development.json` -> environment variables -> User Secrets. **No sample contains a hardcoded API key.** Where User Secrets are required, the sample's README spells out the exact `dotnet user-secrets set` commands.
86+
87+
For local development, prefer User Secrets:
88+
89+
```bash
90+
dotnet user-secrets init --project samples/ch01-foundations/01.3-hello-chat
91+
dotnet user-secrets set "OpenAI:ApiKey" "<your-key>" --project samples/ch01-foundations/01.3-hello-chat
92+
```
93+
94+
For anything beyond a developer workstation -- CI, shared environments, deployed services -- use your platform's secret manager (Azure Key Vault, AWS Secrets Manager, Doppler, etc.). Do not commit `appsettings.Development.json` or `.env` files containing keys; the repo's `.gitignore` already excludes the common patterns, but the responsibility is on you.
7295

7396
## Running a sample
7497

@@ -84,6 +107,8 @@ Each sample folder has its own `README.md` with run instructions, expected outpu
84107
dotnet build
85108
```
86109

110+
builds the full `AI in .Net.sln` solution from the repo root.
111+
87112
## What's offline-runnable
88113

89114
These samples need **only** Ollama (no API keys, no cloud calls):
@@ -97,33 +122,56 @@ These samples need **only** Ollama (no API keys, no cloud calls):
97122

98123
The rest assume an OpenAI / Anthropic / Azure OpenAI key.
99124

100-
## 1.x API surface (Agent Framework 1.3 / MCP 1.2)
125+
## Cost expectations
126+
127+
Cloud-provider samples spend real money against your account. Most are single-shot prompts that come in well under a cent at current OpenAI / Azure OpenAI / Anthropic pricing. The two exceptions worth flagging:
128+
129+
- `03.2-ingestion-pipeline` embeds a multi-document corpus -- expect a few cents per full ingestion run, depending on the embedding model.
130+
- `06.5.3-llm-as-judge` and `03.2.7-evaluation` issue judge calls per evaluated response -- a 50-row golden set against a frontier model is typically tens of cents.
131+
132+
If you intend to run any sample unattended (CI, scheduled jobs, scripts), set per-key spend caps in your provider console first.
101133

102-
All samples target `Microsoft.Agents.AI` 1.3 and `ModelContextProtocol` 1.2. The manuscript was originally drafted against the 0.3.x previews, so the printed code occasionally differs from what's in this repo. The original 0.3-preview snippets are preserved alongside the live code as `*.cs.book.txt` for traceability. The shape changes that recur most often:
134+
## Continuous integration
135+
136+
CI runs on every push and pull request, plus a weekly Monday build to catch transitive package drift. The matrix builds every sample in the `samples/` tree against the pinned versions in `Directory.Packages.props`. A green badge above means the print-tagged code still compiles end-to-end. If the badge is red, check the latest [Actions run](https://github.com/CodeShayk/generative-ai-dotnet-samples/actions/workflows/ci.yml) for the breaking package or API change before assuming the samples are wrong.
137+
138+
## Versioning and tags
139+
140+
| Tag | Meaning |
141+
|---|---|
142+
| `v1.0-first-print` | Code as it appears in the first print run of the book. |
143+
| `v1.x-second-print` | Updated for any subsequent print revisions (placeholder; not yet cut). |
144+
| `master` | Always-current; tracks latest stable .NET 9 + Microsoft.Extensions.AI. May drift from any specific print run. |
145+
146+
Use a **tag** in any URL you cite from the book -- never `master`.
147+
148+
## Notes on the 1.x API surface (Agent Framework 1.3 / MCP 1.2)
149+
150+
All samples target `Microsoft.Agents.AI` 1.3 and `ModelContextProtocol` 1.2. The manuscript was originally drafted against the 0.3.x previews, so a few printed snippets differ from what's checked in here. The original 0.3-preview snippets are preserved alongside the live code as `*.cs.book.txt` for traceability. The shape changes that recur most often:
103151

104152
- `ChatClientAgentOptions` no longer carries `Instructions` / `Tools` -- they move to `ChatClientAgentOptions.ChatOptions` (`Instructions`, `Tools`) or to the string-arg `ChatClientAgent` constructor.
105153
- `AgentThread` -> `AgentSession`; `AgentRunResponse` -> `AgentResponse`; `AIAgent.GetNewThread()` -> `AIAgent.CreateSessionAsync(...)`. Session serialization is `agent.SerializeSessionAsync(session)` / `agent.DeserializeSessionAsync(jsonElement)`.
106154
- `WorkflowBuilder()` parameterless ctor is gone -- the start executor is passed to the constructor (`new WorkflowBuilder(start)`). `Executor.From` / `FromAsync` lambdas became `new FunctionExecutor<TIn, TOut>(id, handler)`. `SetOutputExecutor` -> `WithOutputFrom(...)`. `workflow.RunAsync(...)` was replaced by `InProcessExecution.RunAsync(workflow, input)`.
107155
- The `AsBuilder().Use(...)` middleware delegate now receives `(messages, session, options, innerAgent, ct)` -- you call `innerAgent.RunAsync(...)` instead of a `next` delegate.
108-
- `ModelContextProtocol.Client` / `ModelContextProtocol.Server` are no longer separate packages -- everything is in `ModelContextProtocol` / `ModelContextProtocol.Core` / `ModelContextProtocol.AspNetCore`. `McpClientTool` now extends `AIFunction` directly (no `.AsAIFunction()` needed); rename via `tool.WithName(...)`.
156+
- `ModelContextProtocol.Client` / `ModelContextProtocol.Server` are no longer separate packages -- everything is in `ModelContextProtocol` / `ModelContextProtocol.Core` / `ModelContextProtocol.AspNetCore`. `McpClientTool` extends `AIFunction` directly (no `.AsAIFunction()` needed); rename via `tool.WithName(...)`.
109157
- Server bootstrap is `.AddMcpServer().WithHttpTransport().WithToolsFromAssembly()` then `app.MapMcp(...)`. `WithHttpServerTransport(...)` and `HttpTransportType.Sse` have been retired -- streamable HTTP is the only HTTP transport.
110158
- Client transports: `StreamableHttpClientTransport` -> `HttpClientTransport` (with `HttpClientTransportOptions`). `SseClientTransport` is gone.
111159
- A2A server hosting requires implementing `A2A.IAgentHandler`, registering it via `services.AddA2AAgent<THandler>(agentCard)`, then `app.MapA2A("/path")` from `A2A.AspNetCore`. The 0.3-preview's one-liner `app.MapA2A(agent, "/path")` no longer exists.
112160
- `McpClient.OnToolsListChanged(...)` was replaced by `client.RegisterNotificationHandler(NotificationMethods.ToolListChangedNotification, handler)`, which returns an `IAsyncDisposable`.
113161

114-
## Versioning and tags
162+
## Contributing
115163

116-
| Tag | Meaning |
117-
|---|---|
118-
| `v1.0-print-ready` | Code as it appears in the first print run. |
119-
| `v1.x-second-print` | Updated for subsequent print revisions. |
120-
| `main` | Always-current; may drift from any specific print run. |
164+
Issues are welcome -- bugs, builds broken by transitive package drift, samples whose APIs have moved on, README mistakes. Pull requests are welcome too, especially:
165+
166+
- Build fixes for newer NuGet releases.
167+
- README clarifications (per sample or repo-wide).
168+
- New offline-runnable variants of samples that currently require cloud keys.
121169

122-
Use a **tag** in any URL you cite from the book -- never `main`.
170+
Before opening a PR: run `dotnet build` from the repo root and confirm the affected sample(s) still run. If you change observable behavior, update the sample's README.
123171

124172
## Errata
125173

126-
Open an issue at [github.com/CodeShayk/generative-ai-dotnet-samples](https://github.com/CodeShayk/generative-ai-dotnet-samples). Confirmed errata roll into the next print tag and are noted in the parent book's errata page.
174+
Open an issue at [github.com/CodeShayk/generative-ai-dotnet-samples](https://github.com/CodeShayk/generative-ai-dotnet-samples/issues). Confirmed errata roll into the next print tag and are noted in the parent book's errata page.
127175

128176
## License
129177

docs/citation-index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
The map between book sections and their runnable companion code in this repository.
44

5-
When the manuscript condenses a section and points at this repo, the citation should always reference a **tag** (e.g. `v1.0-print-ready`) rather than `main`. `main` will drift.
5+
When the manuscript condenses a section and points at this repo, the citation should always reference a **tag** (e.g. `v1.0-first-print`) rather than `master`. `master` will drift.
66

77
## Chapter 1 -- Foundations
88

@@ -75,7 +75,7 @@ When the manuscript condenses a section and points at this repo, the citation sh
7575

7676
When a manuscript section is condensed in favor of pointing here, use this exact wording (substitute the tag and path):
7777

78-
> *Full implementation: <https://github.com/CodeShayk/generative-ai-dotnet-samples/tree/v1.0-print-ready/samples/ch04-agent-framework/04.6.3-text-processing-walkthrough>*
78+
> *Full implementation: <https://github.com/CodeShayk/generative-ai-dotnet-samples/tree/v1.0-first-print/samples/ch04-agent-framework/04.6.3-text-processing-walkthrough>*
7979
8080
## Pending (scaffolded but not populated)
8181

docs/verification-log.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Pre-publication verification record for *Generative AI in .NET*. One entry per p
1414
- [ ] Most recent week is fully green.
1515
- [ ] No package on the watch list has a known breaking change pending.
1616
- [ ] Companion repo CI green on the latest commit.
17-
- [ ] Companion repo tagged `v1.0-print-ready` matching the manuscript version.
17+
- [ ] Companion repo tagged `v1.0-first-print` matching the manuscript version.
1818

1919
---
2020

docs/version-matrix.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ Mirrors the package matrix in `Appendix-C-Provider-Support-Matrix.md` and `Appen
4545

4646
## Last validated
4747

48-
- **2026-04-29** -- initial pin, set during companion repo scaffolding. Re-verify against live NuGet before tagging `v1.0-print-ready`.
48+
- **2026-04-29** -- initial pin, set during companion repo scaffolding. Re-verify against live NuGet before tagging `v1.0-first-print`.

0 commit comments

Comments
 (0)