copilotkit 0.1.87__tar.gz → 0.1.88__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {copilotkit-0.1.87 → copilotkit-0.1.88}/PKG-INFO +8 -5
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/__init__.py +0 -2
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/a2ui.py +5 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/copilotkit_lg_middleware.py +97 -3
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/crewai/crewai_sdk.py +39 -13
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/langchain.py +0 -2
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/langgraph.py +17 -84
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/langgraph_agui_agent.py +2 -2
- copilotkit-0.1.88/copilotkit/py.typed +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/sdk.py +4 -11
- {copilotkit-0.1.87 → copilotkit-0.1.88}/pyproject.toml +16 -6
- copilotkit-0.1.87/copilotkit/langgraph_agent.py +0 -840
- {copilotkit-0.1.87 → copilotkit-0.1.88}/README.md +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/action.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/agent.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/crewai/__init__.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/crewai/copilotkit_integration.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/crewai/crewai_agent.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/exc.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/html.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/integrations/__init__.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/integrations/fastapi.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/logging.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/parameter.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/protocol.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/runloop.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/types.py +0 -0
- {copilotkit-0.1.87 → copilotkit-0.1.88}/copilotkit/utils.py +0 -0
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: copilotkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.88
|
|
4
4
|
Summary: CopilotKit python SDK
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: copilot,copilotkit,langgraph,langchain,ai,langsmith,langserve
|
|
7
7
|
Author: Markus Ecker
|
|
8
8
|
Author-email: markus.ecker@gmail.com
|
|
9
|
-
Requires-Python: >=3.10,<3.
|
|
9
|
+
Requires-Python: >=3.10,<3.15
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
17
|
Provides-Extra: crewai
|
|
16
|
-
Requires-Dist: ag-ui-langgraph[fastapi] (>=0.0.
|
|
17
|
-
Requires-Dist: ag-ui-protocol (>=0.1.
|
|
18
|
-
Requires-Dist: crewai (
|
|
18
|
+
Requires-Dist: ag-ui-langgraph[fastapi] (>=0.0.35)
|
|
19
|
+
Requires-Dist: ag-ui-protocol (>=0.1.15)
|
|
20
|
+
Requires-Dist: crewai (>=0.118.0) ; (python_version >= "3.10" and python_version < "3.14") and (extra == "crewai")
|
|
19
21
|
Requires-Dist: fastapi (>=0.115.0,<1.0.0)
|
|
20
22
|
Requires-Dist: langchain (>=0.3.0)
|
|
21
23
|
Requires-Dist: langgraph (>=0.3.25,<2)
|
|
22
24
|
Requires-Dist: partialjson (>=0.0.8,<0.0.9)
|
|
25
|
+
Requires-Dist: pydantic-core (>=2.35.0)
|
|
23
26
|
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
24
27
|
Project-URL: Homepage, https://copilotkit.ai
|
|
25
28
|
Project-URL: Repository, https://github.com/CopilotKit/CopilotKit/tree/main/sdk-python
|
|
@@ -4,7 +4,6 @@ from .action import Action
|
|
|
4
4
|
from .langgraph import CopilotKitState
|
|
5
5
|
from .parameter import Parameter
|
|
6
6
|
from .agent import Agent
|
|
7
|
-
from .langgraph_agent import LangGraphAgent
|
|
8
7
|
from .langgraph_agui_agent import LangGraphAGUIAgent
|
|
9
8
|
from .copilotkit_lg_middleware import CopilotKitMiddleware
|
|
10
9
|
from ag_ui_langgraph.middlewares.state_streaming import StateStreamingMiddleware, StateItem
|
|
@@ -21,7 +20,6 @@ __all__ = [
|
|
|
21
20
|
'CopilotKitContext',
|
|
22
21
|
'CopilotKitSDKContext',
|
|
23
22
|
'CrewAIAgent', # pyright: ignore[reportUnsupportedDunderAll] pylint: disable=undefined-all-variable
|
|
24
|
-
'LangGraphAgent', # pyright: ignore[reportUnsupportedDunderAll] pylint: disable=undefined-all-variable
|
|
25
23
|
"LangGraphAGUIAgent",
|
|
26
24
|
"CopilotKitMiddleware",
|
|
27
25
|
"StateStreamingMiddleware",
|
|
@@ -117,6 +117,11 @@ CRITICAL: You MUST call the render_a2ui tool with ALL of these arguments:
|
|
|
117
117
|
- every component must have the "component" field specifying the component type (e.g. "Text", "Image", "Row", "Column", "List", "Button", etc.)
|
|
118
118
|
|
|
119
119
|
COMPONENT ID RULES:
|
|
120
|
+
- Exactly one component MUST have id="root". This is the surface's entry
|
|
121
|
+
point — the renderer begins at "root" and walks the child/children tree
|
|
122
|
+
from there. Every other component must be reachable from "root". If no
|
|
123
|
+
component has id="root", the surface renders an empty loading placeholder
|
|
124
|
+
and none of your components will be shown.
|
|
120
125
|
- Every component ID must be unique within the surface.
|
|
121
126
|
- A component MUST NOT reference itself as child/children. This causes a
|
|
122
127
|
circular dependency error. For example, if a component has id="avatar",
|
|
@@ -16,7 +16,7 @@ Example:
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import re
|
|
19
|
-
from typing import Any, Callable, Awaitable, ClassVar, List
|
|
19
|
+
from typing import Any, Callable, Awaitable, ClassVar, Iterable, List, Union
|
|
20
20
|
|
|
21
21
|
from langchain_core.messages import AIMessage, SystemMessage, ToolMessage
|
|
22
22
|
from langchain.agents.middleware import (
|
|
@@ -33,25 +33,118 @@ class StateSchema(AgentState):
|
|
|
33
33
|
copilotkit: CopilotKitProperties
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
# Internal/framework keys that should never be surfaced to the LLM as
|
|
37
|
+
# user-facing state. These are either reducer-managed message buckets,
|
|
38
|
+
# CopilotKit/AG-UI plumbing, or graph-internal scaffolding.
|
|
39
|
+
_RESERVED_STATE_KEYS = frozenset({
|
|
40
|
+
"messages",
|
|
41
|
+
"copilotkit",
|
|
42
|
+
"ag-ui",
|
|
43
|
+
"tools",
|
|
44
|
+
"structured_response",
|
|
45
|
+
"thread_id",
|
|
46
|
+
"remaining_steps",
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
|
|
36
50
|
class CopilotKitMiddleware(AgentMiddleware[StateSchema, Any]):
|
|
37
51
|
"""CopilotKit Middleware for LangGraph agents.
|
|
38
52
|
|
|
39
|
-
Handles frontend tool injection
|
|
53
|
+
Handles frontend tool injection, interception for CopilotKit, and
|
|
54
|
+
automatic exposure of agent state to the LLM so values written via
|
|
55
|
+
``agent.setState`` on the frontend (or via ``Command(update=...)`` in a
|
|
56
|
+
tool) are visible in the next model call without needing a custom
|
|
57
|
+
``get_state`` tool.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
expose_state: Controls how user-defined state keys are surfaced into
|
|
61
|
+
``request.system_message`` on every model call. Off by default
|
|
62
|
+
to avoid leaking arbitrary state into prompts; opt in explicitly.
|
|
63
|
+
|
|
64
|
+
- ``False`` (default) — never surface state.
|
|
65
|
+
- ``True`` — every state key that is not in the reserved
|
|
66
|
+
internal set and does not start with an underscore is
|
|
67
|
+
JSON-serialized into a "Current agent state:" note appended
|
|
68
|
+
to the system message.
|
|
69
|
+
- ``list``/``tuple``/``set[str]`` — only surface the named keys.
|
|
70
|
+
Use this when you want explicit control over what the LLM
|
|
71
|
+
sees (e.g. ``["liked", "todos"]``).
|
|
40
72
|
"""
|
|
41
73
|
|
|
42
74
|
state_schema = StateSchema
|
|
43
75
|
tools: ClassVar[list] = []
|
|
44
76
|
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
*,
|
|
80
|
+
expose_state: Union[bool, Iterable[str]] = False,
|
|
81
|
+
):
|
|
82
|
+
super().__init__()
|
|
83
|
+
if isinstance(expose_state, bool):
|
|
84
|
+
self._expose_state: Union[bool, frozenset[str]] = expose_state
|
|
85
|
+
else:
|
|
86
|
+
self._expose_state = frozenset(expose_state)
|
|
87
|
+
|
|
45
88
|
@property
|
|
46
89
|
def name(self) -> str:
|
|
47
90
|
return "CopilotKitMiddleware"
|
|
48
91
|
|
|
49
|
-
#
|
|
92
|
+
# ------------------------------------------------------------------
|
|
93
|
+
# State-to-prompt surfacing
|
|
94
|
+
# ------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
def _build_state_note(self, state: dict) -> str | None:
|
|
97
|
+
"""Serialize a snapshot of user state into a system-prompt note.
|
|
98
|
+
|
|
99
|
+
Returns ``None`` when nothing should be appended (feature disabled
|
|
100
|
+
or no non-empty user keys present).
|
|
101
|
+
"""
|
|
102
|
+
if self._expose_state is False:
|
|
103
|
+
return None
|
|
104
|
+
if isinstance(self._expose_state, frozenset):
|
|
105
|
+
keys: list[str] = [k for k in self._expose_state if k in state]
|
|
106
|
+
else:
|
|
107
|
+
keys = [
|
|
108
|
+
k for k in state
|
|
109
|
+
if k not in _RESERVED_STATE_KEYS and not str(k).startswith("_")
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
snapshot: dict[str, Any] = {}
|
|
113
|
+
for k in keys:
|
|
114
|
+
v = state.get(k)
|
|
115
|
+
# Skip empty / no-op values to keep the note tight.
|
|
116
|
+
if v in (None, "", [], {}):
|
|
117
|
+
continue
|
|
118
|
+
snapshot[k] = v
|
|
119
|
+
|
|
120
|
+
if not snapshot:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
body = json.dumps(snapshot, default=str, ensure_ascii=False, indent=2)
|
|
125
|
+
except (TypeError, ValueError):
|
|
126
|
+
body = str(snapshot)
|
|
127
|
+
return f"Current agent state:\n{body}"
|
|
128
|
+
|
|
129
|
+
def _apply_state_note(self, request: ModelRequest) -> ModelRequest:
|
|
130
|
+
note = self._build_state_note(request.state or {})
|
|
131
|
+
if not note:
|
|
132
|
+
return request
|
|
133
|
+
existing = request.system_message
|
|
134
|
+
if existing is None:
|
|
135
|
+
return request.override(system_message=SystemMessage(content=note))
|
|
136
|
+
base = existing.content if isinstance(existing.content, str) else str(existing.content)
|
|
137
|
+
return request.override(
|
|
138
|
+
system_message=SystemMessage(content=f"{base}\n\n{note}")
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Inject frontend tools and surface user state before model call
|
|
50
142
|
def wrap_model_call(
|
|
51
143
|
self,
|
|
52
144
|
request: ModelRequest,
|
|
53
145
|
handler: Callable[[ModelRequest], ModelResponse],
|
|
54
146
|
) -> ModelResponse:
|
|
147
|
+
request = self._apply_state_note(request)
|
|
55
148
|
frontend_tools = request.state.get("copilotkit", {}).get("actions", [])
|
|
56
149
|
|
|
57
150
|
if not frontend_tools:
|
|
@@ -224,6 +317,7 @@ class CopilotKitMiddleware(AgentMiddleware[StateSchema, Any]):
|
|
|
224
317
|
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
|
225
318
|
) -> ModelResponse:
|
|
226
319
|
self._fix_messages_for_bedrock(request.messages)
|
|
320
|
+
request = self._apply_state_note(request)
|
|
227
321
|
|
|
228
322
|
frontend_tools = request.state.get("copilotkit", {}).get("actions", [])
|
|
229
323
|
|
|
@@ -16,13 +16,22 @@ from litellm.types.utils import (
|
|
|
16
16
|
)
|
|
17
17
|
from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper
|
|
18
18
|
from crewai.flow.flow import FlowState, Flow
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
try:
|
|
20
|
+
from crewai.utilities.events.flow_events import (
|
|
21
|
+
FlowEvent as CrewAIFlowEvent,
|
|
22
|
+
FlowStartedEvent,
|
|
23
|
+
MethodExecutionStartedEvent,
|
|
24
|
+
MethodExecutionFinishedEvent,
|
|
25
|
+
FlowFinishedEvent,
|
|
26
|
+
)
|
|
27
|
+
except ImportError:
|
|
28
|
+
from crewai.events.types.flow_events import ( # type: ignore[no-redef]
|
|
29
|
+
FlowEvent as CrewAIFlowEvent,
|
|
30
|
+
FlowStartedEvent,
|
|
31
|
+
MethodExecutionStartedEvent,
|
|
32
|
+
MethodExecutionFinishedEvent,
|
|
33
|
+
FlowFinishedEvent,
|
|
34
|
+
)
|
|
26
35
|
from crewai.utilities.events import crewai_event_bus as _crewai_event_bus
|
|
27
36
|
|
|
28
37
|
from copilotkit.types import Message
|
|
@@ -552,7 +561,13 @@ def crewai_flow_messages_to_copilotkit(messages: List[Dict]) -> List[Message]: #
|
|
|
552
561
|
if "content" in message and message.get("role") == "assistant":
|
|
553
562
|
if message.get("tool_calls"):
|
|
554
563
|
for tool_call in message["tool_calls"]:
|
|
555
|
-
|
|
564
|
+
tc_id = tool_call.get("id")
|
|
565
|
+
if tc_id is None:
|
|
566
|
+
continue
|
|
567
|
+
if tool_call.get("function"):
|
|
568
|
+
tool_call_names[tc_id] = tool_call["function"].get("name", "")
|
|
569
|
+
else:
|
|
570
|
+
tool_call_names[tc_id] = tool_call.get("name", "")
|
|
556
571
|
|
|
557
572
|
for message in messages:
|
|
558
573
|
message_id = message_ids[id(message)]
|
|
@@ -565,19 +580,30 @@ def crewai_flow_messages_to_copilotkit(messages: List[Dict]) -> List[Message]: #
|
|
|
565
580
|
"id": message_id,
|
|
566
581
|
})
|
|
567
582
|
elif message.get("tool_calls"):
|
|
583
|
+
# Always emit the assistant message, even with empty content.
|
|
584
|
+
# Tool call entries reference it via parentMessageId; omitting it
|
|
585
|
+
# orphans tool calls and breaks frontend thread reconstruction.
|
|
586
|
+
result.append({
|
|
587
|
+
"role": message["role"],
|
|
588
|
+
"content": message.get("content") if message.get("content") is not None else "",
|
|
589
|
+
"id": message_id,
|
|
590
|
+
})
|
|
568
591
|
for tool_call in message["tool_calls"]:
|
|
592
|
+
tc_id = tool_call.get("id")
|
|
593
|
+
if tc_id is None:
|
|
594
|
+
continue
|
|
569
595
|
if tool_call.get("function"):
|
|
570
596
|
result.append({
|
|
571
|
-
"id":
|
|
572
|
-
"name": tool_call["function"]
|
|
597
|
+
"id": tc_id,
|
|
598
|
+
"name": tool_call["function"].get("name", ""),
|
|
573
599
|
"arguments": json.loads(tool_call["function"]["arguments"]),
|
|
574
600
|
"parentMessageId": message_id,
|
|
575
601
|
})
|
|
576
602
|
else:
|
|
577
603
|
result.append({
|
|
578
|
-
"id":
|
|
579
|
-
"name": tool_call
|
|
580
|
-
"arguments": tool_call
|
|
604
|
+
"id": tc_id,
|
|
605
|
+
"name": tool_call.get("name", ""),
|
|
606
|
+
"arguments": tool_call.get("arguments", {}),
|
|
581
607
|
"parentMessageId": message_id,
|
|
582
608
|
})
|
|
583
609
|
elif message.get("content"):
|
|
@@ -4,7 +4,6 @@ copilotkit.langchain is deprecated. Use copilotkit.langgraph instead.
|
|
|
4
4
|
import warnings
|
|
5
5
|
from copilotkit.langgraph import (
|
|
6
6
|
langchain_messages_to_copilotkit,
|
|
7
|
-
copilotkit_messages_to_langchain,
|
|
8
7
|
copilotkit_customize_config,
|
|
9
8
|
copilotkit_exit,
|
|
10
9
|
copilotkit_emit_state,
|
|
@@ -21,7 +20,6 @@ warnings.warn(
|
|
|
21
20
|
|
|
22
21
|
__all__ = [
|
|
23
22
|
"langchain_messages_to_copilotkit",
|
|
24
|
-
"copilotkit_messages_to_langchain",
|
|
25
23
|
"copilotkit_customize_config",
|
|
26
24
|
"copilotkit_exit",
|
|
27
25
|
"copilotkit_emit_state",
|
|
@@ -6,7 +6,7 @@ import uuid
|
|
|
6
6
|
import json
|
|
7
7
|
import warnings
|
|
8
8
|
import asyncio
|
|
9
|
-
from typing import List, Optional, Any, Union, Dict
|
|
9
|
+
from typing import List, Optional, Any, Union, Dict
|
|
10
10
|
from typing_extensions import TypedDict
|
|
11
11
|
from langgraph.graph import MessagesState
|
|
12
12
|
|
|
@@ -46,82 +46,6 @@ class CopilotKitState(MessagesState):
|
|
|
46
46
|
copilotkit: CopilotKitProperties
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def copilotkit_messages_to_langchain(
|
|
50
|
-
use_function_call: bool = False
|
|
51
|
-
) -> Callable[[List[Message]], List[BaseMessage]]:
|
|
52
|
-
"""
|
|
53
|
-
Convert CopilotKit messages to LangChain messages
|
|
54
|
-
"""
|
|
55
|
-
def _copilotkit_messages_to_langchain(messages: List[Message]) -> List[BaseMessage]:
|
|
56
|
-
result = []
|
|
57
|
-
processed_action_executions = set()
|
|
58
|
-
for message in cast(Any, messages):
|
|
59
|
-
if message["type"] == "TextMessage":
|
|
60
|
-
if message["role"] == "user":
|
|
61
|
-
result.append(HumanMessage(content=message["content"], id=message["id"]))
|
|
62
|
-
elif message["role"] == "system":
|
|
63
|
-
result.append(SystemMessage(content=message["content"], id=message["id"]))
|
|
64
|
-
elif message["role"] == "assistant":
|
|
65
|
-
result.append(AIMessage(content=message["content"], id=message["id"]))
|
|
66
|
-
elif message["type"] == "ActionExecutionMessage":
|
|
67
|
-
if use_function_call:
|
|
68
|
-
result.append(AIMessage(
|
|
69
|
-
id=message["id"],
|
|
70
|
-
content="",
|
|
71
|
-
additional_kwargs={
|
|
72
|
-
'function_call':{
|
|
73
|
-
'name': message["name"],
|
|
74
|
-
'arguments': json.dumps(message["arguments"]),
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
))
|
|
78
|
-
else:
|
|
79
|
-
# convert multiple tool calls to a single message
|
|
80
|
-
message_id = message.get("parentMessageId")
|
|
81
|
-
if message_id is None:
|
|
82
|
-
message_id = message["id"]
|
|
83
|
-
|
|
84
|
-
if message_id in processed_action_executions:
|
|
85
|
-
continue
|
|
86
|
-
|
|
87
|
-
processed_action_executions.add(message_id)
|
|
88
|
-
|
|
89
|
-
all_tool_calls = []
|
|
90
|
-
|
|
91
|
-
# Find all tool calls for this message (only ActionExecutionMessage type)
|
|
92
|
-
for msg in messages:
|
|
93
|
-
if msg.get("type") != "ActionExecutionMessage":
|
|
94
|
-
continue
|
|
95
|
-
if msg.get("parentMessageId", None) == message_id or msg["id"] == message_id:
|
|
96
|
-
all_tool_calls.append(msg)
|
|
97
|
-
|
|
98
|
-
tool_calls = [{
|
|
99
|
-
"name": t["name"],
|
|
100
|
-
"args": t["arguments"],
|
|
101
|
-
"id": t["id"],
|
|
102
|
-
} for t in all_tool_calls]
|
|
103
|
-
|
|
104
|
-
result.append(
|
|
105
|
-
AIMessage(
|
|
106
|
-
id=message_id,
|
|
107
|
-
content="",
|
|
108
|
-
tool_calls=tool_calls
|
|
109
|
-
)
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
elif message["type"] == "ResultMessage":
|
|
113
|
-
result.append(ToolMessage(
|
|
114
|
-
id=message["id"],
|
|
115
|
-
content=message["result"],
|
|
116
|
-
name=message["actionName"],
|
|
117
|
-
tool_call_id=message["actionExecutionId"]
|
|
118
|
-
))
|
|
119
|
-
|
|
120
|
-
return result
|
|
121
|
-
|
|
122
|
-
return _copilotkit_messages_to_langchain
|
|
123
|
-
|
|
124
|
-
|
|
125
49
|
def langchain_messages_to_copilotkit(
|
|
126
50
|
messages: List[BaseMessage]
|
|
127
51
|
) -> List[Message]:
|
|
@@ -173,12 +97,14 @@ def langchain_messages_to_copilotkit(
|
|
|
173
97
|
"id": message.id,
|
|
174
98
|
})
|
|
175
99
|
elif isinstance(message, AIMessage):
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
100
|
+
# Always emit the assistant message, even with empty content.
|
|
101
|
+
# Tool call entries reference it via parentMessageId; omitting it
|
|
102
|
+
# orphans tool calls and breaks frontend thread reconstruction.
|
|
103
|
+
result.append({
|
|
104
|
+
"role": "assistant",
|
|
105
|
+
"content": content if content is not None else "",
|
|
106
|
+
"id": message.id,
|
|
107
|
+
})
|
|
182
108
|
if message.tool_calls:
|
|
183
109
|
for tool_call in message.tool_calls:
|
|
184
110
|
result.append({
|
|
@@ -507,6 +433,13 @@ def copilotkit_interrupt(
|
|
|
507
433
|
"__copilotkit_interrupt_value__": interrupt_values,
|
|
508
434
|
"__copilotkit_messages__": [interrupt_message]
|
|
509
435
|
})
|
|
510
|
-
|
|
436
|
+
if isinstance(response, str):
|
|
437
|
+
answer = response
|
|
438
|
+
elif isinstance(response, dict):
|
|
439
|
+
answer = json.dumps(response)
|
|
440
|
+
elif isinstance(response, list):
|
|
441
|
+
answer = response[-1].content
|
|
442
|
+
else:
|
|
443
|
+
answer = str(response)
|
|
511
444
|
|
|
512
445
|
return answer, response
|
|
@@ -198,8 +198,8 @@ class LangGraphAGUIAgent(LangGraphAgent):
|
|
|
198
198
|
return {
|
|
199
199
|
**merged_state,
|
|
200
200
|
'copilotkit': {
|
|
201
|
-
'actions': agui_properties.get('tools', []),
|
|
202
|
-
'context': agui_properties.get('context', [])
|
|
201
|
+
'actions': [a.model_dump() if hasattr(a, 'model_dump') else a for a in agui_properties.get('tools', [])],
|
|
202
|
+
'context': [c.model_dump() if hasattr(c, 'model_dump') else c for c in agui_properties.get('context', [])]
|
|
203
203
|
},
|
|
204
204
|
}
|
|
205
205
|
|
|
File without changes
|
|
@@ -130,12 +130,12 @@ class CopilotKitRemoteEndpoint:
|
|
|
130
130
|
Serving agents works in a similar way to serving actions:
|
|
131
131
|
|
|
132
132
|
```python
|
|
133
|
-
from copilotkit import CopilotKitRemoteEndpoint,
|
|
133
|
+
from copilotkit import CopilotKitRemoteEndpoint, LangGraphAGUIAgent
|
|
134
134
|
from my_agent.agent import graph
|
|
135
135
|
|
|
136
136
|
sdk = CopilotKitRemoteEndpoint(
|
|
137
137
|
agents=[
|
|
138
|
-
|
|
138
|
+
LangGraphAGUIAgent(
|
|
139
139
|
name="email_agent",
|
|
140
140
|
description="This agent sends emails",
|
|
141
141
|
graph=graph,
|
|
@@ -147,12 +147,12 @@ class CopilotKitRemoteEndpoint:
|
|
|
147
147
|
To dynamically build agents, provide a callable that returns a list of agents:
|
|
148
148
|
|
|
149
149
|
```python
|
|
150
|
-
from copilotkit import CopilotKitRemoteEndpoint,
|
|
150
|
+
from copilotkit import CopilotKitRemoteEndpoint, LangGraphAGUIAgent
|
|
151
151
|
from my_agent.agent import graph
|
|
152
152
|
|
|
153
153
|
sdk = CopilotKitRemoteEndpoint(
|
|
154
154
|
agents=lambda context: [
|
|
155
|
-
|
|
155
|
+
LangGraphAGUIAgent(
|
|
156
156
|
name="email_agent",
|
|
157
157
|
description="This agent sends emails",
|
|
158
158
|
graph=graph,
|
|
@@ -226,13 +226,6 @@ class CopilotKitRemoteEndpoint:
|
|
|
226
226
|
self.agents = agents or []
|
|
227
227
|
self.actions = actions or []
|
|
228
228
|
|
|
229
|
-
if isinstance(agents, list):
|
|
230
|
-
from .langgraph_agent import LangGraphAgent
|
|
231
|
-
for agent in agents:
|
|
232
|
-
if isinstance(agent, LangGraphAgent):
|
|
233
|
-
raise ValueError(
|
|
234
|
-
"LangGraphAgent should be instantiated using LangGraphAGUIAgent. Refer to https://docs.copilotkit.ai/langgraph for more information.")
|
|
235
|
-
|
|
236
229
|
|
|
237
230
|
def info(
|
|
238
231
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "copilotkit"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.88"
|
|
4
4
|
description = "CopilotKit python SDK"
|
|
5
5
|
authors = ["Markus Ecker <markus.ecker@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -16,19 +16,29 @@ keywords = [
|
|
|
16
16
|
"langsmith",
|
|
17
17
|
"langserve",
|
|
18
18
|
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
]
|
|
19
26
|
packages = [{ include = "copilotkit" }]
|
|
20
|
-
include = ["copilotkit/*.json"]
|
|
27
|
+
include = ["copilotkit/*.json", "copilotkit/py.typed"]
|
|
21
28
|
|
|
22
29
|
[tool.poetry.dependencies]
|
|
23
|
-
python = ">=3.10,<3.
|
|
30
|
+
python = ">=3.10,<3.15"
|
|
24
31
|
langgraph = { version = ">=0.3.25,<2" }
|
|
25
32
|
langchain = { version = ">=0.3.0" }
|
|
26
|
-
crewai = { version = "0.118.0", optional = true }
|
|
27
|
-
ag-ui-langgraph = { version = ">=0.0.
|
|
28
|
-
ag-ui-protocol = ">=0.1.
|
|
33
|
+
crewai = { version = ">=0.118.0", optional = true, python = ">=3.10,<3.14" }
|
|
34
|
+
ag-ui-langgraph = { version = ">=0.0.35", extras = ["fastapi"] }
|
|
35
|
+
ag-ui-protocol = ">=0.1.15"
|
|
29
36
|
fastapi = ">=0.115.0,<1.0.0"
|
|
30
37
|
partialjson = "^0.0.8"
|
|
31
38
|
toml = "^0.10.2"
|
|
39
|
+
# Pin transitive: cp314 wheels first appear in 2.35.0; older versions
|
|
40
|
+
# would force a Rust source build that fails on PyO3 0.24 (capped at 3.13).
|
|
41
|
+
pydantic-core = ">=2.35.0"
|
|
32
42
|
|
|
33
43
|
[tool.poetry.extras]
|
|
34
44
|
crewai = ["crewai"]
|