Skip to content

Commit

Permalink
fix: add warning when using agents that are not available on agent re…
Browse files Browse the repository at this point in the history
…lated hooks (CopilotKit#1200)
  • Loading branch information
ranst91 authored Jan 9, 2025
1 parent 506e63c commit 649ebcc
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 17 deletions.
7 changes: 7 additions & 0 deletions CopilotKit/.changeset/twenty-dogs-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@copilotkit/react-core": patch
"@copilotkit/runtime-client-gql": patch
"@copilotkit/runtime": patch
---

- fix: add warning when using agents that are not available on agent related hooks
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* ```
*/

import { useCallback, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
CopilotContext,
CopilotApiConfig,
Expand Down Expand Up @@ -42,6 +42,7 @@ import { ToastProvider } from "../toast/toast-provider";
import { useCopilotRuntimeClient } from "../../hooks/use-copilot-runtime-client";
import { shouldShowDevConsole } from "../../utils";
import { CopilotErrorBoundary } from "../error-boundary/error-boundary";
import { Agent } from "@copilotkit/runtime-client-gql";

export function CopilotKit({ children, ...props }: CopilotKitProps) {
const showDevConsole = props.showDevConsole === undefined ? "auto" : props.showDevConsole;
Expand Down Expand Up @@ -277,6 +278,7 @@ export function CopilotKitInternal({ children, ...props }: CopilotKitProps) {
});
};

const [availableAgents, setAvailableAgents] = useState<Agent[]>([]);
const [coagentStates, setCoagentStates] = useState<Record<string, CoagentState>>({});
const coagentStatesRef = useRef<Record<string, CoagentState>>({});
const setCoagentStatesWithRef = useCallback(
Expand All @@ -294,6 +296,16 @@ export function CopilotKitInternal({ children, ...props }: CopilotKitProps) {
[],
);

useEffect(() => {
const fetchData = async () => {
const result = await runtimeClient.availableAgents();
if (result.data?.availableAgents) {
setAvailableAgents(result.data.availableAgents.agents);
}
};
void fetchData();
}, []);

let initialAgentSession: AgentSession | null = null;
if (props.agent) {
initialAgentSession = {
Expand Down Expand Up @@ -349,6 +361,7 @@ export function CopilotKitInternal({ children, ...props }: CopilotKitProps) {
runId,
setRunId,
chatAbortControllerRef,
availableAgents,
authConfig: props.authConfig,
authStates,
setAuthStates,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ function Toast({
style={{
backgroundColor: bgColors[type],
color: "white",
padding: "0.5rem 1rem",
padding: "0.5rem 1.5rem",
borderRadius: "0.25rem",
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
position: "relative",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CopilotChatSuggestionConfiguration } from "../types/chat-suggestion-con
import { CoAgentStateRender, CoAgentStateRenderProps } from "../types/coagent-action";
import { CoagentState } from "../types/coagent-state";
import { CopilotRuntimeClient, ForwardedParametersInput } from "@copilotkit/runtime-client-gql";
import { Agent } from "@copilotkit/runtime-client-gql";

/**
* Interface for the configuration of the Copilot API.
Expand Down Expand Up @@ -176,6 +177,7 @@ export interface CopilotContextParams {
* The forwarded parameters to use for the task.
*/
forwardedParameters?: Pick<ForwardedParametersInput, "temperature">;
availableAgents: Agent[];

/**
* The auth states for the CopilotKit.
Expand Down Expand Up @@ -251,6 +253,7 @@ const emptyCopilotContext: CopilotContextParams = {
runId: null,
setRunId: () => {},
chatAbortControllerRef: { current: null },
availableAgents: [],
};

export const CopilotContext = React.createContext<CopilotContextParams>(emptyCopilotContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { useRef, useContext, useEffect } from "react";
import { CopilotContext } from "../context/copilot-context";
import { randomId } from "@copilotkit/shared";
import { CoAgentStateRender } from "../types/coagent-action";
import { useToast } from "../components/toast/toast-provider";

/**
* This hook is used to render agent state with custom UI components or text. This is particularly
Expand All @@ -71,8 +72,18 @@ export function useCoAgentStateRender<T = any>(
removeCoAgentStateRender,
coAgentStateRenders,
chatComponentsCache,
availableAgents,
} = useContext(CopilotContext);
const idRef = useRef<string>(randomId());
const { addToast } = useToast();

useEffect(() => {
if (availableAgents?.length && !availableAgents.some((a) => a.name === action.name)) {
const message = `(useCoAgentStateRender): Agent "${action.name}" not found. Make sure the agent exists and is properly configured.`;
console.warn(message);
addToast({ type: "warning", message });
}
}, [availableAgents]);

const key = `${action.name}-${action.nodeName || "global"}`;

Expand Down
12 changes: 11 additions & 1 deletion CopilotKit/packages/react-core/src/hooks/use-coagent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import { useCopilotChat } from "./use-copilot-chat";
import { Message } from "@copilotkit/runtime-client-gql";
import { flushSync } from "react-dom";
import { useAsyncCallback } from "../components/error-boundary/error-utils";
import { useToast } from "../components/toast/toast-provider";

interface WithInternalStateManagementAndInitial<T> {
/**
Expand Down Expand Up @@ -203,21 +204,30 @@ export type HintFunction = (params: HintFunctionParams) => Message | undefined;
* we refer to as CoAgents, checkout the documentation at https://docs.copilotkit.ai/coagents/quickstart.
*/
export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentReturnType<T> {
const generalContext = useCopilotContext();
const { availableAgents } = generalContext;
const { addToast } = useToast();
const isExternalStateManagement = (
options: UseCoagentOptions<T>,
): options is WithExternalStateManagement<T> => {
return "state" in options && "setState" in options;
};

const { name } = options;
useEffect(() => {
if (availableAgents?.length && !availableAgents.some((a) => a.name === name)) {
const message = `(useCoAgent): Agent "${name}" not found. Make sure the agent exists and is properly configured.`;
console.warn(message);
addToast({ type: "warning", message });
}
}, [availableAgents]);

const isInternalStateManagementWithInitial = (
options: UseCoagentOptions<T>,
): options is WithInternalStateManagementAndInitial<T> => {
return "initialState" in options;
};

const generalContext = useCopilotContext();
const messagesContext = useCopilotMessagesContext();
const context = { ...generalContext, ...messagesContext };
const { coagentStates, coagentStatesRef, setCoagentStatesWithRef } = context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,31 @@ import { Client, cacheExchange, fetchExchange } from "@urql/core";
import * as packageJson from "../../package.json";

import {
AvailableAgentsQuery,
GenerateCopilotResponseMutation,
GenerateCopilotResponseMutationVariables,
} from "../graphql/@generated/graphql";
import { generateCopilotResponseMutation } from "../graphql/definitions/mutations";
import { getAvailableAgentsQuery } from "../graphql/definitions/queries";
import { OperationResultSource, OperationResult } from "urql";

const createFetchFn =
(signal?: AbortSignal) =>
async (...args: Parameters<typeof fetch>) => {
const result = await fetch(args[0], { ...(args[1] ?? {}), signal });
if (result.status !== 200) {
switch (result.status) {
case 404:
throw new Error(
"Runtime URL seems to be invalid - got 404 response. Please check the runtimeUrl passed to CopilotKit",
);
default:
throw new Error("Could not fetch copilot response");
}
}
return result;
};

export interface CopilotRuntimeClientOptions {
url: string;
publicApiKey?: string;
Expand Down Expand Up @@ -55,20 +74,7 @@ export class CopilotRuntimeClient {
properties?: GenerateCopilotResponseMutationVariables["properties"];
signal?: AbortSignal;
}) {
const fetchFn = async (...args: Parameters<typeof fetch>) => {
const result = await fetch(args[0], { ...(args[1] ?? {}), signal });
if (result.status !== 200) {
switch (result.status) {
case 404:
throw new Error(
"Runtime URL seems to be invalid - got 404 response. Please check the runtimeUrl passed to CopilotKit",
);
default:
throw new Error("Could not fetch copilot response");
}
}
return result;
};
const fetchFn = createFetchFn(signal);
const result = this.client.mutation<
GenerateCopilotResponseMutation,
GenerateCopilotResponseMutationVariables
Expand Down Expand Up @@ -97,4 +103,9 @@ export class CopilotRuntimeClient {
},
});
}

availableAgents() {
const fetchFn = createFetchFn();
return this.client.query<AvailableAgentsQuery>(getAvailableAgentsQuery, {}, { fetch: fetchFn });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { graphql } from "../@generated/gql";

export const getAvailableAgentsQuery = graphql(/** GraphQL **/ `
query availableAgents {
availableAgents {
agents {
name
id
description
}
}
}
`);
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import {
} from "../types/converted";
import telemetry from "../../lib/telemetry-client";
import { randomId } from "@copilotkit/shared";
import { EndpointType, LangGraphPlatformAgent } from "../../lib/runtime/remote-actions";
import { AgentsResponse } from "../types/agents-response.type";

const invokeGuardrails = async ({
baseUrl,
Expand Down Expand Up @@ -106,6 +108,20 @@ export class CopilotResolver {
return "Hello World";
}

@Query(() => AgentsResponse)
async availableAgents(@Ctx() ctx: GraphQLContext) {
let logger = ctx.logger.child({ component: "CopilotResolver.availableAgents" });

logger.debug("Processing");
const agents = await ctx._copilotkit.runtime.discoverAgentsFromEndpoints(ctx);

logger.debug("Event source created, creating response");

return {
agents,
};
}

@Mutation(() => CopilotResponse)
async generateCopilotResponse(
@Ctx() ctx: GraphQLContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Field, InterfaceType, ObjectType } from "type-graphql";
import { MessageRole } from "./enums";
import { MessageStatusUnion } from "./message-status.type";
import { ResponseStatusUnion } from "./response-status.type";

@ObjectType()
export class Agent {
@Field(() => String)
id: string;

@Field(() => String)
name: string;

@Field(() => String)
description?: string;
}

@ObjectType()
export class AgentsResponse {
@Field(() => [Agent])
agents: Agent[];
}
50 changes: 50 additions & 0 deletions CopilotKit/packages/runtime/src/lib/runtime/copilot-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { AgentSessionInput } from "../../graphql/inputs/agent-session.input";
import { from } from "rxjs";
import { AgentStateInput } from "../../graphql/inputs/agent-state.input";
import { ActionInputAvailability } from "../../graphql/types/enums";
import { createHeaders } from "./remote-action-constructors";
import { Agent } from "../../graphql/types/agents-response.type";

interface CopilotRuntimeRequest {
serviceAdapter: CopilotServiceAdapter;
Expand Down Expand Up @@ -253,6 +255,54 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
}
}

async discoverAgentsFromEndpoints(graphqlContext: GraphQLContext): Promise<Agent[]> {
const headers = createHeaders(null, graphqlContext);
const agents = this.remoteEndpointDefinitions.reduce(
async (acc: Promise<Agent[]>, endpoint) => {
const agents = await acc;
if (endpoint.type === EndpointType.LangGraphPlatform) {
const response = await fetch(
`${(endpoint as LangGraphPlatformEndpoint).deploymentUrl}/assistants/search`,
{
method: "POST",
headers,
},
);

const data: Array<{ assistant_id: string; graph_id: string }> = await response.json();
const endpointAgents = (data ?? []).map((entry) => ({
name: entry.graph_id,
id: entry.assistant_id,
}));
return [...agents, ...endpointAgents];
}

interface InfoResponse {
agents?: Array<{
name: string;
description: string;
}>;
}

const response = await fetch(`${(endpoint as CopilotKitEndpoint).url}/info`, {
method: "POST",
headers,
body: JSON.stringify({ properties: graphqlContext.properties }),
});
const data: InfoResponse = await response.json();
const endpointAgents = (data?.agents ?? []).map((agent) => ({
name: agent.name,
description: agent.description,
id: randomId(), // Required by Agent type
}));
return [...agents, ...endpointAgents];
},
Promise.resolve([]),
);

return agents;
}

private async processAgentRequest(
request: CopilotRuntimeRequest,
): Promise<CopilotRuntimeResponse> {
Expand Down
9 changes: 9 additions & 0 deletions docs/content/docs/reference/classes/CopilotRuntime.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,12 @@ An array of LangServer URLs.

</PropertyReference>

<PropertyReference name="discoverAgentsFromEndpoints" type="graphqlContext: GraphQLContext">


<PropertyReference name="graphqlContext" type="GraphQLContext" required>

</PropertyReference>

</PropertyReference>

0 comments on commit 649ebcc

Please sign in to comment.