copilotagent 0.1.6__py3-none-any.whl

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.
@@ -0,0 +1,35 @@
1
+ """CopilotAgent package."""
2
+
3
+ __author__ = """Harrison Chase"""
4
+ __email__ = "harrison@langchain.dev"
5
+ __version__ = "0.1.6"
6
+
7
+ from copilotagent.cloud_subagents import (
8
+ get_cute_finish_itp_subagent,
9
+ get_cute_linear_subagent,
10
+ )
11
+ from copilotagent.graph import (
12
+ create_deep_agent,
13
+ DEFAULT_AGENT_TYPE,
14
+ DEFAULT_STARTING_MESSAGES,
15
+ get_default_starting_message,
16
+ )
17
+ from copilotagent.middleware.filesystem import FilesystemMiddleware
18
+ from copilotagent.middleware.initial_message import InitialMessageMiddleware
19
+ from copilotagent.middleware.planning import PlanningMiddleware
20
+ from copilotagent.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
21
+
22
+ __all__ = [
23
+ "CompiledSubAgent",
24
+ "DEFAULT_AGENT_TYPE",
25
+ "DEFAULT_STARTING_MESSAGES",
26
+ "FilesystemMiddleware",
27
+ "InitialMessageMiddleware",
28
+ "PlanningMiddleware",
29
+ "SubAgent",
30
+ "SubAgentMiddleware",
31
+ "create_deep_agent",
32
+ "get_cute_finish_itp_subagent",
33
+ "get_cute_linear_subagent",
34
+ "get_default_starting_message",
35
+ ]
@@ -0,0 +1,220 @@
1
+ """Cloud subagents using LangGraph Cloud deployed services.
2
+
3
+ This module provides factory functions for creating CompiledSubAgent instances
4
+ that connect to deployed LangGraph Cloud services via LangGraph SDK client.
5
+ """
6
+
7
+ import logging
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Any, TypedDict
11
+
12
+ from dotenv import load_dotenv
13
+ from langchain_core.runnables import Runnable
14
+ from langgraph_sdk import get_client
15
+
16
+ # Set up logging
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Try to load .env from multiple possible locations
20
+ env_locations = [
21
+ Path.cwd() / ".env", # Current working directory
22
+ Path(__file__).parent.parent.parent.parent / ".env", # copilot/.env
23
+ Path(__file__).parent.parent.parent.parent / "agents" / "ITP-Princeton" / ".env", # ITP-Princeton/.env
24
+ ]
25
+
26
+ for env_path in env_locations:
27
+ if env_path.exists():
28
+ logger.info(f"Loading .env from: {env_path}")
29
+ load_dotenv(env_path)
30
+ break
31
+ else:
32
+ logger.warning(f"No .env file found in any of these locations: {env_locations}")
33
+
34
+
35
+ class RemoteGraphRunnable(Runnable):
36
+ """Wrapper to make LangGraph SDK client work as a Runnable.
37
+
38
+ This adapter allows the new LangGraph SDK client API to work with
39
+ the existing Runnable interface expected by SubAgentMiddleware.
40
+ """
41
+
42
+ def __init__(self, url: str, api_key: str, graph_id: str):
43
+ """Initialize the remote graph runnable.
44
+
45
+ Args:
46
+ url: The URL of the deployed LangGraph service
47
+ api_key: The API key for authentication
48
+ graph_id: The graph ID to use (assistant_id)
49
+ """
50
+ self.url = url
51
+ self.api_key = api_key
52
+ self.graph_id = graph_id
53
+ self._client = None
54
+
55
+ @property
56
+ def client(self):
57
+ """Lazy initialization of the client."""
58
+ if self._client is None:
59
+ self._client = get_client(url=self.url, api_key=self.api_key)
60
+ return self._client
61
+
62
+ def invoke(self, input: dict[str, Any], config: dict[str, Any] | None = None) -> dict[str, Any]:
63
+ """Synchronously invoke the remote graph.
64
+
65
+ Args:
66
+ input: The input state dictionary
67
+ config: Optional configuration
68
+
69
+ Returns:
70
+ The final state from the remote graph execution
71
+ """
72
+ import asyncio
73
+ return asyncio.run(self.ainvoke(input, config))
74
+
75
+ async def ainvoke(self, input: dict[str, Any], config: dict[str, Any] | None = None) -> dict[str, Any]:
76
+ """Asynchronously invoke the remote graph.
77
+
78
+ Args:
79
+ input: The input state dictionary
80
+ config: Optional configuration
81
+
82
+ Returns:
83
+ The final state from the remote graph execution
84
+ """
85
+ # Use the runs.wait method to execute and wait for completion
86
+ result = await self.client.runs.wait(
87
+ thread_id=None, # Create a new thread for each invocation
88
+ assistant_id=self.graph_id,
89
+ input=input,
90
+ )
91
+ # Return the final state from the result
92
+ return result
93
+
94
+
95
+ class CompiledSubAgent(TypedDict):
96
+ """A pre-compiled agent spec."""
97
+
98
+ name: str
99
+ """The name of the agent."""
100
+
101
+ description: str
102
+ """The description of the agent."""
103
+
104
+ runnable: Runnable
105
+ """The Runnable to use for the agent."""
106
+
107
+
108
+ def get_cute_linear_subagent() -> CompiledSubAgent:
109
+ """Get cuteLinear cloud subagent for GUI data extraction.
110
+
111
+ This agent performs automated GUI data extraction including:
112
+ - Multi-step GUI navigation
113
+ - Screenshot capture
114
+ - OCR via AWS Textract
115
+ - AI-powered name normalization
116
+ - Human-in-the-loop review
117
+ - SharedState reporting
118
+
119
+ Returns:
120
+ CompiledSubAgent dictionary with RemoteGraph runnable.
121
+
122
+ Raises:
123
+ ValueError: If API key environment variable is not set.
124
+ """
125
+ # Try multiple environment variable names (LangChain uses different names in different contexts)
126
+ api_key = (
127
+ os.getenv("LANGCHAIN_API_KEY") or
128
+ os.getenv("LANGSMITH_API_KEY") or
129
+ os.getenv("LANGGRAPH_API_KEY")
130
+ )
131
+
132
+ # Log environment variable status for debugging
133
+ logger.info(f"LANGCHAIN_API_KEY present: {bool(os.getenv('LANGCHAIN_API_KEY'))}")
134
+ logger.info(f"LANGSMITH_API_KEY present: {bool(os.getenv('LANGSMITH_API_KEY'))}")
135
+ logger.info(f"LANGGRAPH_API_KEY present: {bool(os.getenv('LANGGRAPH_API_KEY'))}")
136
+ logger.info(f"Current working directory: {Path.cwd()}")
137
+ logger.info(f"All env vars with 'LANG': {[k for k in os.environ.keys() if 'LANG' in k]}")
138
+
139
+ if not api_key:
140
+ msg = (
141
+ "API key environment variable is required for cloud subagents. "
142
+ "Please ensure .env file exists with one of: LANGCHAIN_API_KEY, LANGSMITH_API_KEY, or LANGGRAPH_API_KEY. "
143
+ f"Checked locations: {env_locations}"
144
+ )
145
+ logger.error(msg)
146
+ raise ValueError(msg)
147
+
148
+ logger.info(f"Creating RemoteGraphRunnable for cute-linear with API key: {api_key[:10]}...")
149
+ client = RemoteGraphRunnable(
150
+ url="https://cutelineargraph-ef1ae523c24e51ef94e330929a65833b.us.langgraph.app",
151
+ api_key=api_key,
152
+ graph_id="cuteLinearGraph",
153
+ )
154
+
155
+ return {
156
+ "name": "cute-linear",
157
+ "description": (
158
+ "Coordinated Data Extraction Agent - Performs automated GUI data extraction "
159
+ "including navigation, OCR via AWS Textract, and AI-powered name normalization. "
160
+ "Extracts borrower names from GUI interface, sends for human review, and reports "
161
+ "results back. Use this agent when you need to extract borrower names from the "
162
+ "GUI system."
163
+ ),
164
+ "runnable": client,
165
+ }
166
+
167
+
168
+ def get_cute_finish_itp_subagent() -> CompiledSubAgent:
169
+ """Get cuteFinishITP cloud subagent for GUI automation.
170
+
171
+ This agent executes a 25-step workflow for ITP document processing including:
172
+ - Popup detection and handling
173
+ - Form field navigation and filling
174
+ - Document verification
175
+ - Final document submission
176
+ - Screenshot capture for verification
177
+
178
+ Returns:
179
+ CompiledSubAgent dictionary with RemoteGraph runnable.
180
+
181
+ Raises:
182
+ ValueError: If API key environment variable is not set.
183
+ """
184
+ # Try multiple environment variable names (LangChain uses different names in different contexts)
185
+ api_key = (
186
+ os.getenv("LANGCHAIN_API_KEY") or
187
+ os.getenv("LANGSMITH_API_KEY") or
188
+ os.getenv("LANGGRAPH_API_KEY")
189
+ )
190
+
191
+ # Log environment variable status for debugging
192
+ logger.info(f"API key present: {bool(api_key)}")
193
+
194
+ if not api_key:
195
+ msg = (
196
+ "API key environment variable is required for cloud subagents. "
197
+ "Please ensure .env file exists with one of: LANGCHAIN_API_KEY, LANGSMITH_API_KEY, or LANGGRAPH_API_KEY. "
198
+ f"Checked locations: {env_locations}"
199
+ )
200
+ logger.error(msg)
201
+ raise ValueError(msg)
202
+
203
+ logger.info(f"Creating RemoteGraphRunnable for cute-finish-itp with API key: {api_key[:10]}...")
204
+ client = RemoteGraphRunnable(
205
+ url="https://cutefinishitp-2d3fcf81a7e55dd09a66354c5c2b567c.us.langgraph.app",
206
+ api_key=api_key,
207
+ graph_id="cuteFinishITP",
208
+ )
209
+
210
+ return {
211
+ "name": "cute-finish-itp",
212
+ "description": (
213
+ "Encompass GUI Automation Agent - Executes a 25-step workflow for ITP document "
214
+ "processing including popup detection, form filling, document verification, and "
215
+ "final submission. Use this agent when you need to complete the ITP document "
216
+ "processing workflow in the Encompass GUI system."
217
+ ),
218
+ "runnable": client,
219
+ }
220
+
copilotagent/graph.py ADDED
@@ -0,0 +1,192 @@
1
+ """Deepagents come with planning, filesystem, and subagents."""
2
+
3
+ from collections.abc import Callable, Sequence
4
+ from typing import Any, Literal
5
+
6
+ from langchain.agents import create_agent
7
+ from langchain.agents.middleware import HumanInTheLoopMiddleware, InterruptOnConfig
8
+ from langchain.agents.middleware.summarization import SummarizationMiddleware
9
+ from langchain.agents.middleware.types import AgentMiddleware
10
+ from langchain.agents.structured_output import ResponseFormat
11
+ from langchain_anthropic import ChatAnthropic
12
+ from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
13
+ from langchain_core.language_models import BaseChatModel
14
+ from langchain_core.tools import BaseTool
15
+ from langgraph.cache.base import BaseCache
16
+ from langgraph.graph.state import CompiledStateGraph
17
+ from langgraph.store.base import BaseStore
18
+ from langgraph.types import Checkpointer
19
+
20
+ from copilotagent.middleware.filesystem import FilesystemMiddleware
21
+ from copilotagent.middleware.initial_message import InitialMessageMiddleware
22
+ from copilotagent.middleware.patch_tool_calls import PatchToolCallsMiddleware
23
+ from copilotagent.middleware.planning import PlanningMiddleware
24
+ from copilotagent.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
25
+
26
+ # Global default agent type for planning middleware
27
+ DEFAULT_AGENT_TYPE: Literal["ITP-Princeton", "DrawDoc-AWM", "research"] = "ITP-Princeton"
28
+
29
+ BASE_AGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools."
30
+
31
+ # Default starting messages for different agent types
32
+ DEFAULT_STARTING_MESSAGES = {
33
+ "ITP-Princeton": "Let's review and approve Intent to Proceed for Princeton mortgage",
34
+ "DrawDoc-AWM": "Let's draw the docs for AWM",
35
+ "research": None, # Research agents still get human input
36
+ }
37
+
38
+
39
+ def get_default_model() -> ChatAnthropic:
40
+ """Get the default model for deep agents.
41
+
42
+ Returns:
43
+ ChatAnthropic instance configured with Claude Sonnet 4.
44
+ """
45
+ return ChatAnthropic(
46
+ model_name="claude-sonnet-4-5-20250929",
47
+ max_tokens=20000,
48
+ )
49
+
50
+
51
+ def get_default_starting_message(
52
+ agent_type: Literal["ITP-Princeton", "DrawDoc-AWM", "research"]
53
+ ) -> str | None:
54
+ """Get the default starting message for an agent type.
55
+
56
+ Args:
57
+ agent_type: The type of agent.
58
+
59
+ Returns:
60
+ Default starting message for the agent type, or None if no default.
61
+ """
62
+ return DEFAULT_STARTING_MESSAGES.get(agent_type)
63
+
64
+
65
+ def create_deep_agent(
66
+ model: str | BaseChatModel | None = None,
67
+ tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,
68
+ *,
69
+ system_prompt: str | None = None,
70
+ middleware: Sequence[AgentMiddleware] = (),
71
+ subagents: list[SubAgent | CompiledSubAgent] | None = None,
72
+ response_format: ResponseFormat | None = None,
73
+ context_schema: type[Any] | None = None,
74
+ checkpointer: Checkpointer | None = None,
75
+ store: BaseStore | None = None,
76
+ use_longterm_memory: bool = False,
77
+ interrupt_on: dict[str, bool | InterruptOnConfig] | None = None,
78
+ agent_type: Literal["ITP-Princeton", "DrawDoc-AWM", "research"] | None = None,
79
+ debug: bool = False,
80
+ name: str | None = None,
81
+ cache: BaseCache | None = None,
82
+ ) -> CompiledStateGraph:
83
+ """Create a deep agent.
84
+
85
+ This agent will by default have access to a tool to write todos (write_todos),
86
+ four file editing tools: write_file, ls, read_file, edit_file, and a tool to call
87
+ subagents.
88
+
89
+ Args:
90
+ tools: The tools the agent should have access to.
91
+ system_prompt: The additional instructions the agent should have. Will go in
92
+ the system prompt.
93
+ middleware: Additional middleware to apply after standard middleware.
94
+ model: The model to use.
95
+ subagents: The subagents to use. Each subagent should be a dictionary with the
96
+ following keys:
97
+ - `name`
98
+ - `description` (used by the main agent to decide whether to call the
99
+ sub agent)
100
+ - `prompt` (used as the system prompt in the subagent)
101
+ - (optional) `tools`
102
+ - (optional) `model` (either a LanguageModelLike instance or dict
103
+ settings)
104
+ - (optional) `middleware` (list of AgentMiddleware)
105
+ response_format: A structured output response format to use for the agent.
106
+ context_schema: The schema of the deep agent.
107
+ checkpointer: Optional checkpointer for persisting agent state between runs.
108
+ store: Optional store for persisting longterm memories.
109
+ use_longterm_memory: Whether to use longterm memory - you must provide a store
110
+ in order to use longterm memory.
111
+ interrupt_on: Optional Dict[str, bool | InterruptOnConfig] mapping tool names to
112
+ interrupt configs.
113
+ agent_type: Type of agent for planning prompts. Options: "ITP-Princeton",
114
+ "DrawDoc-AWM", "research". Defaults to the global DEFAULT_AGENT_TYPE.
115
+ debug: Whether to enable debug mode. Passed through to create_agent.
116
+ name: The name of the agent. Passed through to create_agent.
117
+ cache: The cache to use for the agent. Passed through to create_agent.
118
+
119
+ Returns:
120
+ A configured deep agent.
121
+ """
122
+ if model is None:
123
+ model = get_default_model()
124
+
125
+ # Use the provided agent_type or fall back to the global default
126
+ if agent_type is None:
127
+ agent_type = DEFAULT_AGENT_TYPE
128
+
129
+ # Get the default starting message for this agent type
130
+ default_starting_message = get_default_starting_message(agent_type)
131
+
132
+ deepagent_middleware = [
133
+ # Add initial message middleware first, so it can present default message
134
+ # before any other processing happens
135
+ InitialMessageMiddleware(
136
+ agent_type=agent_type,
137
+ default_message=default_starting_message,
138
+ ),
139
+ PlanningMiddleware(
140
+ agent_type=agent_type,
141
+ ),
142
+ FilesystemMiddleware(
143
+ long_term_memory=use_longterm_memory,
144
+ ),
145
+ SubAgentMiddleware(
146
+ default_model=model,
147
+ default_tools=tools,
148
+ subagents=subagents if subagents is not None else [],
149
+ default_middleware=[
150
+ PlanningMiddleware(
151
+ agent_type=agent_type,
152
+ ),
153
+ FilesystemMiddleware(
154
+ long_term_memory=use_longterm_memory,
155
+ ),
156
+ SummarizationMiddleware(
157
+ model=model,
158
+ max_tokens_before_summary=170000,
159
+ messages_to_keep=6,
160
+ ),
161
+ AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
162
+ PatchToolCallsMiddleware(),
163
+ ],
164
+ default_interrupt_on=interrupt_on,
165
+ general_purpose_agent=True,
166
+ ),
167
+ SummarizationMiddleware(
168
+ model=model,
169
+ max_tokens_before_summary=170000,
170
+ messages_to_keep=6,
171
+ ),
172
+ AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
173
+ PatchToolCallsMiddleware(),
174
+ ]
175
+ if interrupt_on is not None:
176
+ deepagent_middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))
177
+ if middleware is not None:
178
+ deepagent_middleware.extend(middleware)
179
+
180
+ return create_agent(
181
+ model,
182
+ system_prompt=system_prompt + "\n\n" + BASE_AGENT_PROMPT if system_prompt else BASE_AGENT_PROMPT,
183
+ tools=tools,
184
+ middleware=deepagent_middleware,
185
+ response_format=response_format,
186
+ context_schema=context_schema,
187
+ checkpointer=checkpointer,
188
+ store=store,
189
+ debug=debug,
190
+ name=name,
191
+ cache=cache,
192
+ ).with_config({"recursion_limit": 1000})
@@ -0,0 +1,15 @@
1
+ """Middleware for the CopilotAgent."""
2
+
3
+ from copilotagent.middleware.filesystem import FilesystemMiddleware
4
+ from copilotagent.middleware.initial_message import InitialMessageMiddleware
5
+ from copilotagent.middleware.planning import PlanningMiddleware
6
+ from copilotagent.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
7
+
8
+ __all__ = [
9
+ "CompiledSubAgent",
10
+ "FilesystemMiddleware",
11
+ "InitialMessageMiddleware",
12
+ "PlanningMiddleware",
13
+ "SubAgent",
14
+ "SubAgentMiddleware",
15
+ ]