Skip to content

Commit 742efbb

Browse files
authored
feat: enable setting langgraph config from ui (CopilotKit#1704)
1 parent 0631837 commit 742efbb

12 files changed

Lines changed: 98 additions & 53 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@copilotkit/react-core": patch
3+
"@copilotkit/runtime": patch
4+
---
5+
6+
- feat: enable setting langgraph config from ui
7+
- chore: document usage of new config

CopilotKit/packages/react-core/src/hooks/use-chat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
353353
agentStates: Object.values(coagentStatesRef.current!).map((state) => ({
354354
agentName: state.name,
355355
state: JSON.stringify(state.state),
356-
configurable: JSON.stringify(state.configurable ?? {}),
356+
config: JSON.stringify(state.config ?? {}),
357357
})),
358358
forwardedParameters: options.forwardedParameters || {},
359359
},

CopilotKit/packages/react-core/src/hooks/use-coagent.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -103,41 +103,40 @@ import { useToast } from "../components/toast/toast-provider";
103103
import { useCopilotRuntimeClient } from "./use-copilot-runtime-client";
104104
import { parseJson } from "@copilotkit/shared";
105105

106-
interface WithInternalStateManagementAndInitial<T> {
106+
interface UseCoagentOptionsBase {
107107
/**
108108
* The name of the agent being used.
109109
*/
110110
name: string;
111111
/**
112-
* The initial state of the agent.
112+
* @deprecated - use "config.configurable"
113+
* Config to pass to a LangGraph Agent
113114
*/
114-
initialState: T;
115+
configurable?: Record<string, any>;
115116
/**
116117
* Config to pass to a LangGraph Agent
117118
*/
118-
configurable?: Record<string, any>;
119+
config?: {
120+
configurable?: Record<string, any>;
121+
[key: string]: any;
122+
};
119123
}
120124

121-
interface WithInternalStateManagement {
125+
interface WithInternalStateManagementAndInitial<T> extends UseCoagentOptionsBase {
122126
/**
123-
* The name of the agent being used.
127+
* The initial state of the agent.
124128
*/
125-
name: string;
129+
initialState: T;
130+
}
131+
132+
interface WithInternalStateManagement extends UseCoagentOptionsBase {
126133
/**
127134
* Optional initialState with default type any
128135
*/
129136
initialState?: any;
130-
/**
131-
* Config to pass to a LangGraph Agent
132-
*/
133-
configurable?: Record<string, any>;
134137
}
135138

136-
interface WithExternalStateManagement<T> {
137-
/**
138-
* The name of the agent being used.
139-
*/
140-
name: string;
139+
interface WithExternalStateManagement<T> extends UseCoagentOptionsBase {
141140
/**
142141
* The current state of the agent.
143142
*/
@@ -146,10 +145,6 @@ interface WithExternalStateManagement<T> {
146145
* A function to update the state of the agent.
147146
*/
148147
setState: (newState: T | ((prevState: T | undefined) => T)) => void;
149-
/**
150-
* Config to pass to a LangGraph Agent
151-
*/
152-
configurable?: Record<string, any>;
153148
}
154149

155150
type UseCoagentOptions<T> =
@@ -416,7 +411,11 @@ const getCoagentState = <T>({
416411
return {
417412
name,
418413
state: isInternalStateManagementWithInitial<T>(options) ? options.initialState : {},
419-
configurable: options.configurable ?? {},
414+
config: options.config
415+
? options.config
416+
: options.configurable
417+
? { configurable: options.configurable }
418+
: {},
420419
running: false,
421420
active: false,
422421
threadId: undefined,

CopilotKit/packages/react-core/src/types/coagent-state.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ export interface CoagentState {
44
running: boolean;
55
active: boolean;
66
threadId?: string;
7-
configurable?: Record<string, any>;
7+
config?: {
8+
configurable?: Record<string, any>;
9+
[key: string]: any;
10+
};
811
nodeName?: string;
912
runId?: string;
1013
}

CopilotKit/packages/runtime/src/graphql/inputs/agent-state.input.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ export class AgentStateInput {
99
state: string;
1010

1111
@Field(() => String, { nullable: true })
12-
configurable?: string;
12+
config?: string;
1313
}

CopilotKit/packages/runtime/src/lib/runtime/remote-action-constructors.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ export function constructLGCRemoteAction({
5757
});
5858

5959
let state = {};
60-
let configurable = {};
60+
let config = {};
6161
if (agentStates) {
6262
const jsonState = agentStates.find((state) => state.agentName === name);
6363
if (jsonState) {
6464
state = parseJson(jsonState.state, {});
65-
configurable = parseJson(jsonState.configurable, {});
65+
config = parseJson(jsonState.config, {});
6666
}
6767
}
6868

@@ -76,7 +76,7 @@ export function constructLGCRemoteAction({
7676
nodeName,
7777
messages: [...messages, ...additionalMessages],
7878
state,
79-
configurable,
79+
config,
8080
properties: graphqlContext.properties,
8181
actions: tryMap(actionInputsWithoutAgents, (action: ActionInput) => ({
8282
name: action.name,
@@ -206,12 +206,12 @@ export function constructRemoteActions({
206206
});
207207

208208
let state = {};
209-
let configurable = {};
209+
let config = {};
210210
if (agentStates) {
211211
const jsonState = agentStates.find((state) => state.agentName === name);
212212
if (jsonState) {
213213
state = parseJson(jsonState.state, {});
214-
configurable = parseJson(jsonState.configurable, {});
214+
config = parseJson(jsonState.config, {});
215215
}
216216
}
217217

@@ -226,7 +226,7 @@ export function constructRemoteActions({
226226
nodeName,
227227
messages: [...messages, ...additionalMessages],
228228
state,
229-
configurable,
229+
config,
230230
properties: graphqlContext.properties,
231231
actions: tryMap(actionInputsWithoutAgents, (action: ActionInput) => ({
232232
name: action.name,

CopilotKit/packages/runtime/src/lib/runtime/remote-lg-action.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ interface ExecutionArgs extends Omit<LangGraphPlatformEndpoint, "agents"> {
2626
nodeName: string;
2727
messages: Message[];
2828
state: State;
29-
configurable?: Record<string, any>;
29+
config?: {
30+
configurable?: Record<string, any>;
31+
[key: string]: any;
32+
};
3033
properties: CopilotRequestContextProperties;
3134
actions: ExecutionAction[];
3235
logger: Logger;
@@ -119,7 +122,7 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
119122
agent,
120123
nodeName: initialNodeName,
121124
state: initialState,
122-
configurable,
125+
config: explicitConfig,
123126
messages,
124127
actions,
125128
logger,
@@ -234,11 +237,40 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
234237
const graphInfo = await client.assistants.getGraph(assistantId);
235238
const graphSchema = await client.assistants.getSchemas(assistantId);
236239
const schemaKeys = getSchemaKeys(graphSchema);
237-
if (configurable) {
238-
const filteredConfigurable = schemaKeys?.config
239-
? filterObjectBySchemaKeys(configurable, schemaKeys?.config)
240-
: configurable;
241-
await client.assistants.update(assistantId, { config: { configurable: filteredConfigurable } });
240+
241+
if (explicitConfig) {
242+
let filteredConfigurable = retrievedAssistant.config.configurable;
243+
if (explicitConfig.configurable) {
244+
filteredConfigurable = schemaKeys?.config
245+
? filterObjectBySchemaKeys(explicitConfig?.configurable, schemaKeys?.config)
246+
: explicitConfig?.configurable;
247+
}
248+
249+
const newConfig = {
250+
...retrievedAssistant.config,
251+
...explicitConfig,
252+
configurable: filteredConfigurable,
253+
};
254+
255+
// LG does not return recursion limit if it's the default, therefore we check: if no recursion limit is currently set, and the user asked for 25, there is no change.
256+
const isRecursionLimitSetToDefault =
257+
retrievedAssistant.config.recursion_limit == null && explicitConfig.recursion_limit === 25;
258+
// Deep compare configs to avoid unnecessary update calls
259+
const configsAreDifferent =
260+
JSON.stringify(newConfig) !== JSON.stringify(retrievedAssistant.config);
261+
262+
// Check if the only difference is the recursion_limit being set to default
263+
const isOnlyRecursionLimitDifferent =
264+
isRecursionLimitSetToDefault &&
265+
JSON.stringify({ ...newConfig, recursion_limit: null }) ===
266+
JSON.stringify({ ...retrievedAssistant.config, recursion_limit: null });
267+
268+
// If configs are different, we further check: Is the only diff a request to set the recursion limit to its already default?
269+
if (configsAreDifferent && !isOnlyRecursionLimitDifferent) {
270+
await client.assistants.update(assistantId, {
271+
config: newConfig,
272+
});
273+
}
242274
}
243275

244276
// Do not input keys that are not part of the input schema

docs/content/docs/coagents/advanced/adding-runtime-configuration.mdx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ function YourMainContent() {
3131

3232
useCoAgent<AgentState>({
3333
name: "sample_agent",
34-
// [!code highlight:4]
35-
configurable: {
36-
authToken: 'example-token'
34+
// [!code highlight:7]
35+
config: {
36+
configurable: {
37+
authToken: 'example-token'
38+
},
39+
recursion_limit: 50,
3740
}
3841
})
3942

sdk-python/copilotkit/agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def execute( # pylint: disable=too-many-arguments
3434
self,
3535
*,
3636
state: dict,
37-
configurable: Optional[dict] = None,
37+
config: Optional[dict] = None,
3838
messages: List[Message],
3939
thread_id: str,
4040
actions: Optional[List[ActionDict]] = None,

sdk-python/copilotkit/integrations/fastapi.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ async def handler_v1(
193193
if method == 'POST' and path == 'agents/execute':
194194
thread_id = body.get("threadId")
195195
node_name = body.get("nodeName")
196-
configurable = body.get("configurable")
196+
config = body.get("config")
197197

198198
name = body_get_or_raise(body, "name")
199199
state = body_get_or_raise(body, "state")
@@ -208,7 +208,7 @@ async def handler_v1(
208208
node_name=node_name,
209209
name=name,
210210
state=state,
211-
configurable=configurable,
211+
config=config,
212212
messages=messages,
213213
actions=actions,
214214
meta_events=meta_events,
@@ -273,7 +273,7 @@ def handle_execute_agent( # pylint: disable=too-many-arguments
273273
thread_id: str,
274274
name: str,
275275
state: dict,
276-
configurable: Optional[dict] = None,
276+
config: Optional[dict] = None,
277277
messages: List[Message],
278278
actions: List[ActionDict],
279279
node_name: str,
@@ -287,7 +287,7 @@ def handle_execute_agent( # pylint: disable=too-many-arguments
287287
name=name,
288288
node_name=node_name,
289289
state=state,
290-
configurable=configurable,
290+
config=config,
291291
messages=messages,
292292
actions=actions,
293293
meta_events=meta_events,

0 commit comments

Comments
 (0)