forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuse-coagent.ts
More file actions
377 lines (351 loc) · 10.4 KB
/
Copy pathuse-coagent.ts
File metadata and controls
377 lines (351 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/**
* <Callout type="info">
* Usage of this hook assumes some additional setup in your application, for more information
* on that see the CoAgents <span className="text-blue-500">[getting started guide](/coagents/quickstart/langgraph)</span>.
* </Callout>
* <Frame className="my-12">
* <img
* src="https://cdn.copilotkit.ai/docs/copilotkit/images/coagents/SharedStateCoAgents.gif"
* alt="CoAgents demonstration"
* className="w-auto"
* />
* </Frame>
*
* This hook is used to integrate an agent into your application. With its use, you can
* render and update the state of an agent, allowing for a dynamic and interactive experience.
* We call these shared state experiences agentic copilots, or CoAgents for short.
*
* ## Usage
*
* ### Simple Usage
*
* ```tsx
* import { useCoAgent } from "@copilotkit/react-core";
*
* type AgentState = {
* count: number;
* }
*
* const agent = useCoAgent<AgentState>({
* name: "my-agent",
* initialState: {
* count: 0,
* },
* });
*
* ```
*
* `useCoAgent` returns an object with the following properties:
*
* ```tsx
* const {
* name, // The name of the agent currently being used.
* nodeName, // The name of the current LangGraph node.
* state, // The current state of the agent.
* setState, // A function to update the state of the agent.
* running, // A boolean indicating if the agent is currently running.
* start, // A function to start the agent.
* stop, // A function to stop the agent.
* run, // A function to re-run the agent. Takes a HintFunction to inform the agent why it is being re-run.
* } = agent;
* ```
*
* Finally we can leverage these properties to create reactive experiences with the agent!
*
* ```tsx
* const { state, setState } = useCoAgent<AgentState>({
* name: "my-agent",
* initialState: {
* count: 0,
* },
* });
*
* return (
* <div>
* <p>Count: {state.count}</p>
* <button onClick={() => setState({ count: state.count + 1 })}>Increment</button>
* </div>
* );
* ```
*
* This reactivity is bidirectional, meaning that changes to the state from the agent will be reflected in the UI and vice versa.
*
* ## Parameters
* <PropertyReference name="options" type="UseCoagentOptions<T>" required>
* The options to use when creating the coagent.
* <PropertyReference name="name" type="string" required>
* The name of the agent to use.
* </PropertyReference>
* <PropertyReference name="initialState" type="T | any">
* The initial state of the agent.
* </PropertyReference>
* <PropertyReference name="state" type="T | any">
* State to manage externally if you are using this hook with external state management.
* </PropertyReference>
* <PropertyReference name="setState" type="(newState: T | ((prevState: T | undefined) => T)) => void">
* A function to update the state of the agent if you are using this hook with external state management.
* </PropertyReference>
* </PropertyReference>
*/
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { Message } from "@copilotkit/shared";
import { useAgent, useCopilotKit } from "../v2";
import type { AgentSubscriber } from "@ag-ui/client";
import { useAgentNodeName } from "./use-agent-nodename";
interface UseCoagentOptionsBase {
/**
* The name of the agent being used.
*/
name: string;
/**
* @deprecated - use "config.configurable"
* Config to pass to a LangGraph Agent
*/
configurable?: Record<string, any>;
/**
* Config to pass to a LangGraph Agent
*/
config?: {
configurable?: Record<string, any>;
[key: string]: any;
};
}
interface WithInternalStateManagementAndInitial<
T,
> extends UseCoagentOptionsBase {
/**
* The initial state of the agent.
*/
initialState: T;
}
interface WithInternalStateManagement extends UseCoagentOptionsBase {
/**
* Optional initialState with default type any
*/
initialState?: any;
}
interface WithExternalStateManagement<T> extends UseCoagentOptionsBase {
/**
* The current state of the agent.
*/
state: T;
/**
* A function to update the state of the agent.
*/
setState: (newState: T | ((prevState: T | undefined) => T)) => void;
}
type UseCoagentOptions<T> =
| WithInternalStateManagementAndInitial<T>
| WithInternalStateManagement
| WithExternalStateManagement<T>;
export interface UseCoagentReturnType<T> {
/**
* The name of the agent being used.
*/
name: string;
/**
* The name of the current LangGraph node.
*/
nodeName?: string;
/**
* The ID of the thread the agent is running in.
*/
threadId?: string;
/**
* A boolean indicating if the agent is currently running.
*/
running: boolean;
/**
* The current state of the agent.
*/
state: T;
/**
* A function to update the state of the agent.
*/
setState: (newState: T | ((prevState: T | undefined) => T)) => void;
/**
* A function to start the agent.
*/
start: () => void;
/**
* A function to stop the agent.
*/
stop: () => void;
/**
* A function to re-run the agent. The hint function can be used to provide a hint to the agent
* about why it is being re-run again.
*/
run: (...args: any[]) => Promise<any>;
}
export interface HintFunctionParams {
/**
* The previous state of the agent.
*/
previousState: any;
/**
* The current state of the agent.
*/
currentState: any;
}
export type HintFunction = (params: HintFunctionParams) => Message | undefined;
/**
* This hook is used to integrate an agent into your application. With its use, you can
* render and update the state of the agent, allowing for a dynamic and interactive experience.
* We call these shared state experiences "agentic copilots". To get started using agentic copilots, which
* we refer to as CoAgents, checkout the documentation at https://docs.copilotkit.ai/langgraph-python/quickstart.
*/
export function useCoAgent<T = any>(
options: UseCoagentOptions<T>,
): UseCoagentReturnType<T> {
const { agent } = useAgent({ agentId: options.name });
const { copilotkit } = useCopilotKit();
const nodeName = useAgentNodeName(options.name);
const handleStateUpdate = useCallback(
(newState: T | ((prevState: T | undefined) => T)) => {
if (!agent) return;
if (typeof newState === "function") {
const updater = newState as (prevState: T | undefined) => T;
agent.setState(updater(agent.state));
} else {
agent.setState({ ...agent.state, ...newState });
}
},
[agent?.state, agent?.setState],
);
useEffect(() => {
if (!options.config && !options.configurable) return;
let config = options.config ?? {};
if (options.configurable) {
config = {
...config,
configurable: {
...options.configurable,
...config.configurable,
},
};
}
copilotkit.setProperties(config);
}, [options.config, options.configurable]);
const externalStateStr = useMemo(
() =>
isExternalStateManagement(options)
? JSON.stringify(options.state)
: undefined,
[
isExternalStateManagement(options)
? JSON.stringify(options.state)
: undefined,
],
);
// Sync internal state with external state if state management is external
useEffect(() => {
if (
agent?.state &&
isExternalStateManagement(options) &&
JSON.stringify(options.state) !== JSON.stringify(agent.state)
) {
handleStateUpdate(options.state);
}
}, [agent, externalStateStr, handleStateUpdate]);
const hasStateValues = useCallback((value?: Record<string, any>) => {
return Boolean(value && Object.keys(value).length);
}, []);
const initialStateRef = useRef<any>(
isExternalStateManagement(options)
? options.state
: "initialState" in options
? options.initialState
: undefined,
);
useEffect(() => {
if (isExternalStateManagement(options)) {
initialStateRef.current = options.state;
} else if ("initialState" in options) {
initialStateRef.current = options.initialState;
}
}, [
isExternalStateManagement(options)
? JSON.stringify(options.state)
: "initialState" in options
? JSON.stringify(options.initialState)
: undefined,
]);
useEffect(() => {
if (!agent) return;
const subscriber: AgentSubscriber = {
onStateChanged: (args: any) => {
if (isExternalStateManagement(options)) {
options.setState(args.state);
}
},
onRunInitialized: (args: any) => {
const runHasState = hasStateValues(args.state);
if (runHasState) {
handleStateUpdate(args.state);
return;
}
if (hasStateValues(agent.state)) {
return;
}
if (initialStateRef.current !== undefined) {
handleStateUpdate(initialStateRef.current);
}
},
};
const subscription = agent.subscribe(subscriber);
return () => {
subscription.unsubscribe();
};
}, [agent, handleStateUpdate, hasStateValues]);
// Return a consistent shape whether or not the agent is available
return useMemo<UseCoagentReturnType<T>>(() => {
if (!agent) {
const noop = () => {};
const noopAsync = async () => {};
const initialState =
// prefer externally provided state if available
("state" in options && (options as any).state) ??
// then initialState if provided
("initialState" in options && (options as any).initialState) ??
({} as T);
return {
name: options.name,
nodeName,
threadId: undefined,
running: false,
state: initialState as T,
setState: noop,
start: noop,
stop: noop,
run: noopAsync,
};
}
return {
name: agent?.agentId ?? options.name,
nodeName,
threadId: agent.threadId,
running: agent.isRunning,
state: agent.state,
setState: handleStateUpdate,
// TODO: start and run both have same thing. need to figure out
start: agent.runAgent,
stop: agent.abortRun,
run: agent.runAgent,
};
}, [
agent?.state,
agent?.runAgent,
agent?.abortRun,
agent?.runAgent,
agent?.threadId,
agent?.isRunning,
agent?.agentId,
handleStateUpdate,
options.name,
]);
}
const isExternalStateManagement = <T>(
options: UseCoagentOptions<T>,
): options is WithExternalStateManagement<T> => {
return "state" in options && "setState" in options;
};