|
29 | 29 | const int MaxContextWindowTokens = 1_050_000; |
30 | 30 | const int MaxOutputTokens = 128_000; |
31 | 31 |
|
32 | | -// Create a compaction strategy based on the model's context window. |
33 | | -// gpt-5.4: 1,050,000 token context window, 128,000 max output tokens. |
34 | | -// Defaults: tool result eviction at 50% of input budget, truncation at 80%. |
35 | | -var compactionStrategy = new ContextWindowCompactionStrategy( |
36 | | - maxContextWindowTokens: MaxContextWindowTokens, |
37 | | - maxOutputTokens: MaxOutputTokens); |
38 | | - |
39 | | -// Create an OpenAIClient that communicates with the Foundry responses service and get an IChatClient with stored output disabled |
40 | | -// so that chat history is managed locally by the agent framework. |
41 | | -// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. |
42 | | -// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid |
43 | | -// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. |
44 | | -OpenAIClientOptions clientOptions = new() { Endpoint = new Uri(endpoint), RetryPolicy = new ClientRetryPolicy(3) }; |
45 | | -IChatClient chatClient = new OpenAIClient(new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"), clientOptions) |
46 | | - .GetResponsesClient() |
47 | | - .AsIChatClientWithStoredOutputDisabled(deploymentName) |
48 | | - .AsBuilder() |
49 | | - .UseFunctionInvocation() |
50 | | - .UsePerServiceCallChatHistoryPersistence() |
51 | | - .UseAIContextProviders(new CompactionProvider(compactionStrategy)) |
52 | | - .Build(); |
53 | | - |
54 | | -// Create web browsing tools for downloading and converting HTML pages to markdown. |
55 | | -var webBrowsingTools = new WebBrowsingTools(); |
56 | | - |
57 | 32 | // Create a ChatClientAgent with the Harness providers (TodoProvider and AgentModeProvider) |
58 | 33 | // and research-focused instructions including the mandatory planning workflow. |
59 | 34 | var instructions = |
@@ -123,36 +98,70 @@ Also save intermediate notes and findings as you go — this helps with long mul |
123 | 98 | When a temporary file is no longer needed, delete it to keep file memory tidy. |
124 | 99 | """; |
125 | 100 |
|
126 | | -AIAgent agent = new ChatClientAgent( |
127 | | - chatClient, |
128 | | - new ChatClientAgentOptions |
129 | | - { |
130 | | - Name = "ResearchAgent", |
131 | | - Description = "A research assistant that plans and executes research tasks.", |
132 | | - AIContextProviders = |
133 | | - [ |
134 | | - new TodoProvider(), |
135 | | - new AgentModeProvider(), |
136 | | - new FileMemoryProvider( |
137 | | - new FileSystemAgentFileStore(Path.Combine(AppContext.BaseDirectory, "agent-files")), |
138 | | - (_) => new FileMemoryState() { WorkingFolder = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss") + "_" + Guid.NewGuid().ToString() }) |
139 | | - ], |
140 | | - RequirePerServiceCallChatHistoryPersistence = true, |
141 | | - UseProvidedChatClientAsIs = true, |
142 | | - ChatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions |
| 101 | +// Create a compaction strategy based on the model's context window. |
| 102 | +// gpt-5.4: 1,050,000 token context window, 128,000 max output tokens. |
| 103 | +// Defaults: tool result eviction at 50% of input budget, truncation at 80%. |
| 104 | +var compactionStrategy = new ContextWindowCompactionStrategy( |
| 105 | + maxContextWindowTokens: MaxContextWindowTokens, |
| 106 | + maxOutputTokens: MaxOutputTokens); |
| 107 | + |
| 108 | +AIAgent agent = |
| 109 | + // Create an OpenAIClient that communicates with the Foundry responses service. |
| 110 | + new OpenAIClient( |
| 111 | + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. |
| 112 | + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid |
| 113 | + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. |
| 114 | + new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"), |
| 115 | + new OpenAIClientOptions() |
143 | 116 | { |
144 | | - ChatReducer = compactionStrategy.AsChatReducer(), |
145 | | - }), |
146 | | - ChatOptions = new ChatOptions |
| 117 | + Endpoint = new Uri(endpoint), |
| 118 | + RetryPolicy = new ClientRetryPolicy(3) // Enable retries to improve resiliency. |
| 119 | + }) |
| 120 | + .GetResponsesClient() |
| 121 | + .AsIChatClientWithStoredOutputDisabled(deploymentName) // We want to manage chat history locally (not stored in the responses service), so that we can manage compaction ourselves. |
| 122 | + |
| 123 | + // Build a ChatClient Pipeline |
| 124 | + .AsBuilder() |
| 125 | + .UseFunctionInvocation() // We are building our own stack from scratch so we need to include Function Invocation ourselves. |
| 126 | + .UsePerServiceCallChatHistoryPersistence() // Save chat history updates to the session after each service call, rather than only at the end of the run. |
| 127 | + .UseAIContextProviders(new CompactionProvider(compactionStrategy)) // Add Compaction before each service call to responses so that long function invocation loops don't overflow the context. |
| 128 | + |
| 129 | + // Build our agent on top of the ChatClient Pipeline |
| 130 | + .BuildAIAgent( |
| 131 | + new ChatClientAgentOptions |
147 | 132 | { |
148 | | - // Set a high token limit for long research tasks with many tool calls and long outputs. |
149 | | - // This matches gpt-5.4's max output tokens, and should be adjusted depending on the model used and expected response length. |
150 | | - MaxOutputTokens = 128_000, |
151 | | - Instructions = instructions, |
152 | | - Reasoning = new() { Effort = ReasoningEffort.Medium }, |
153 | | - Tools = [ResponseTool.CreateWebSearchTool().AsAITool(), .. webBrowsingTools.Tools], |
154 | | - }, |
155 | | - }); |
| 133 | + Name = "ResearchAgent", |
| 134 | + Description = "A research assistant that plans and executes research tasks.", |
| 135 | + UseProvidedChatClientAsIs = true, // Since we built our own stack from scratch we need to tell the agent not to also add defaults like Function Invocation. |
| 136 | + RequirePerServiceCallChatHistoryPersistence = true, // Since we are added the per service call persistence ChatClient, we need to tell the agent to not also store chat history at the end of the run. |
| 137 | + ChatHistoryProvider = new InMemoryChatHistoryProvider( // Store chat history in memory in the session object. Will persist if the session is persisted. |
| 138 | + new InMemoryChatHistoryProviderOptions |
| 139 | + { |
| 140 | + ChatReducer = compactionStrategy.AsChatReducer(), // Run compaction on the InMemory chat history when it gets too large. |
| 141 | + }), |
| 142 | + AIContextProviders = |
| 143 | + [ |
| 144 | + new TodoProvider(), // Add an AIContextProvider to allow the agent to create a TODO list, which is stored in the session. |
| 145 | + new AgentModeProvider(), // Add an AIContextProvider that tracks the agent mode and allows switching mode. Current mode is stored in the session. |
| 146 | + new FileMemoryProvider( // Add an AIContextProvider that can store memories in files under a session specific working folder. |
| 147 | + new FileSystemAgentFileStore(Path.Combine(AppContext.BaseDirectory, "agent-files")), |
| 148 | + (_) => new FileMemoryState() { WorkingFolder = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss") + "_" + Guid.NewGuid().ToString() }) |
| 149 | + ], |
| 150 | + ChatOptions = new ChatOptions |
| 151 | + { |
| 152 | + Instructions = instructions, |
| 153 | + Tools = |
| 154 | + [ |
| 155 | + ResponseTool.CreateWebSearchTool().AsAITool(), // Add the foundry hosted web search tool that runs in the service. |
| 156 | + new WebBrowsingTool(), // Add a local web browsing tool that converts html to markdown. |
| 157 | + ], |
| 158 | + MaxOutputTokens = MaxOutputTokens, // Set a high token limit for long research tasks with many tool calls and long outputs. |
| 159 | + Reasoning = new() { Effort = ReasoningEffort.Medium }, |
| 160 | + }, |
| 161 | + }) |
| 162 | + .AsBuilder() |
| 163 | + .UseToolApproval() // Add the ability to auto approve tools once a user has said they don't want to be asked again. Approval rules are tied to the session. |
| 164 | + .Build(); |
156 | 165 |
|
157 | 166 | // Run the interactive console session using the shared HarnessConsole helper. |
158 | 167 | await HarnessConsole.RunAgentAsync(agent, title: "Research Assistant", userPrompt: "Enter a research topic to get started.", maxContextWindowTokens: MaxContextWindowTokens, maxOutputTokens: MaxOutputTokens); |
0 commit comments