ai-puppy commited on
Commit
3495357
·
1 Parent(s): 9861912
Files changed (2) hide show
  1. agent.py +227 -0
  2. requirements.txt +2 -2
agent.py CHANGED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import inspect
3
+ import uuid
4
+ import os
5
+ from typing import Any
6
+
7
+ from langchain.chat_models import init_chat_model
8
+ from langchain_sandbox import PyodideSandbox
9
+
10
+ from langgraph_codeact import EvalCoroutine, create_codeact
11
+ from dotenv import find_dotenv, load_dotenv
12
+
13
+ load_dotenv(find_dotenv())
14
+
15
+ class FileInjectedPyodideSandbox(PyodideSandbox):
16
+ """Custom PyodideSandbox that can inject files into the virtual filesystem."""
17
+
18
+ def __init__(self, file_path: str = None, virtual_path: str = "/server.log", **kwargs):
19
+ super().__init__(**kwargs)
20
+ self.file_path = file_path
21
+ self.virtual_path = virtual_path
22
+ self._file_injected = False
23
+
24
+ async def execute(self, code: str, **kwargs):
25
+ # If we have a file to inject, prepend the injection code to the user code
26
+ if self.file_path and os.path.exists(self.file_path):
27
+ print(f"Injecting file {self.file_path} into execution")
28
+
29
+ try:
30
+ with open(self.file_path, 'r') as f:
31
+ file_content = f.read()
32
+
33
+ # Use base64 encoding to avoid string literal issues
34
+ import base64
35
+ encoded_content = base64.b64encode(file_content.encode('utf-8')).decode('ascii')
36
+
37
+ # Prepend file injection code to user code
38
+ injection_code = f'''
39
+ # File injection code - inject {self.virtual_path}
40
+ import base64
41
+ import os
42
+
43
+ # Decode the log file content from base64
44
+ encoded_content = """{encoded_content}"""
45
+ file_content = base64.b64decode(encoded_content).decode('utf-8')
46
+
47
+ # Create the file on disk for compatibility
48
+ with open("{self.virtual_path}", 'w') as f:
49
+ f.write(file_content)
50
+
51
+ # Make the content directly available as variables for analysis
52
+ log_lines = file_content.splitlines()
53
+ total_lines = len(log_lines)
54
+
55
+ print(f"[INJECTION] Successfully created {self.virtual_path} with {{len(file_content)}} characters")
56
+ print(f"[INJECTION] File content available as 'file_content' variable ({{len(file_content)}} chars)")
57
+ print(f"[INJECTION] Log lines available as 'log_lines' variable ({{total_lines}} lines)")
58
+
59
+ # Verify injection worked
60
+ if os.path.exists("{self.virtual_path}"):
61
+ print(f"[INJECTION] File {self.virtual_path} exists and ready for use")
62
+ else:
63
+ print(f"[INJECTION] ERROR: Failed to create {self.virtual_path}")
64
+
65
+ # Variables now available for analysis:
66
+ # - file_content: raw file content as string
67
+ # - log_lines: list of individual log lines
68
+ # - total_lines: number of lines in the log
69
+ # - File also available at: {self.virtual_path}
70
+
71
+ # End of injection code
72
+ '''
73
+
74
+ # Combine injection code with user code
75
+ combined_code = injection_code + "\n" + code
76
+ print(f"Combined code length: {len(combined_code)}")
77
+
78
+ return await super().execute(combined_code, **kwargs)
79
+
80
+ except Exception as e:
81
+ print(f"Error preparing file injection: {e}")
82
+ return await super().execute(code, **kwargs)
83
+ else:
84
+ return await super().execute(code, **kwargs)
85
+
86
+ def create_pyodide_eval_fn(sandbox: PyodideSandbox) -> EvalCoroutine:
87
+ """Create an eval_fn that uses PyodideSandbox.
88
+ """
89
+
90
+ async def async_eval_fn(
91
+ code: str, _locals: dict[str, Any]
92
+ ) -> tuple[str, dict[str, Any]]:
93
+ # Create a wrapper function that will execute the code and return locals
94
+ wrapper_code = f"""
95
+ def execute():
96
+ try:
97
+ # Execute the provided code
98
+ {chr(10).join(" " + line for line in code.strip().split(chr(10)))}
99
+ return locals()
100
+ except Exception as e:
101
+ return {{"error": str(e)}}
102
+
103
+ execute()
104
+ """
105
+
106
+ # Convert functions in _locals to their string representation
107
+ context_setup = ""
108
+ for key, value in _locals.items():
109
+ if callable(value):
110
+ # Get the function's source code
111
+ try:
112
+ src = inspect.getsource(value)
113
+ context_setup += f"\n{src}"
114
+ except:
115
+ # If we can't get source, skip it
116
+ pass
117
+ else:
118
+ context_setup += f"\n{key} = {repr(value)}"
119
+
120
+ try:
121
+ # Combine context setup and the actual code
122
+ full_code = context_setup + "\n\n" + wrapper_code
123
+
124
+ # Execute the code and get the result
125
+ response = await sandbox.execute(code=full_code)
126
+
127
+ # Check if execution was successful
128
+ if response.stderr:
129
+ return f"Error during execution: {response.stderr}", {}
130
+
131
+ # Get the output from stdout
132
+ output = (
133
+ response.stdout
134
+ if response.stdout
135
+ else "<Code ran, no output printed to stdout>"
136
+ )
137
+ result = response.result
138
+
139
+ # If there was an error in the result, return it
140
+ if isinstance(result, dict) and "error" in result:
141
+ return f"Error during execution: {result['error']}", {}
142
+
143
+ # Get the new variables by comparing with original locals
144
+ new_vars = {
145
+ k: v
146
+ for k, v in result.items()
147
+ if k not in _locals and not k.startswith("_")
148
+ }
149
+ return output, new_vars
150
+
151
+ except Exception as e:
152
+ return f"Error during PyodideSandbox execution: {repr(e)}", {}
153
+
154
+ return async_eval_fn
155
+
156
+
157
+ def read_file(file_path: str) -> str:
158
+ """Read a file and return its content."""
159
+ with open(file_path, "r") as file:
160
+ return file.read()
161
+
162
+
163
+ tools = []
164
+
165
+ model = init_chat_model("gpt-4.1-2025-04-14", model_provider="openai")
166
+
167
+ # Specify the log file path
168
+ log_file_path = "/Users/hw/Desktop/codeact_agent/server.log"
169
+
170
+ # Create our custom sandbox with file injection capability
171
+ sandbox = FileInjectedPyodideSandbox(
172
+ file_path=log_file_path,
173
+ virtual_path="/server.log",
174
+ allow_net=True
175
+ )
176
+
177
+ eval_fn = create_pyodide_eval_fn(sandbox)
178
+ code_act = create_codeact(model, tools, eval_fn)
179
+ agent = code_act.compile()
180
+
181
+ query = """
182
+ Analyze these server logs and provide:
183
+ 1. Security threat summary - identify attack patterns, suspicious IPs, and breach attempts
184
+ 2. Performance bottlenecks - find slow endpoints, database issues, and resource constraints
185
+ 3. User behavior analysis - login patterns, most accessed endpoints, session durations
186
+ 4. System health report - error rates, critical alerts, and infrastructure issues
187
+ 5. Recommended actions based on the analysis
188
+
189
+ LOG FORMAT INFORMATION:
190
+ The server logs follow this format:
191
+ YYYY-MM-DD HH:MM:SS [LEVEL] event_type: key=value, key=value, ...
192
+
193
+ Sample log entries:
194
+ - 2024-01-15 08:23:45 [INFO] user_login: user=john_doe, ip=192.168.1.100, success=true
195
+ - 2024-01-15 08:24:12 [INFO] api_request: endpoint=/api/users, method=GET, user=john_doe, response_time=45ms
196
+ - 2024-01-15 08:27:22 [WARN] failed_login: user=admin, ip=203.45.67.89, attempts=3
197
+ - 2024-01-15 08:38:33 [CRITICAL] security_alert: suspicious_activity, ip=185.234.72.19, pattern=sql_injection_attempt
198
+ - 2024-01-15 08:26:01 [ERROR] database_connection: host=db-primary, error=timeout, duration=30s
199
+
200
+ Key log levels: INFO, WARN, ERROR, CRITICAL
201
+ Key event types: user_login, user_logout, api_request, failed_login, security_alert, database_connection, etc.
202
+
203
+ DATA SOURCES AVAILABLE:
204
+ - `file_content`: Raw log content as a string
205
+ - `log_lines`: List of individual log lines
206
+ - `total_lines`: Number of lines in the log
207
+ - File path: `/server.log` (can be read with open('/server.log', 'r'))
208
+
209
+ Generate python code and run it in the sandbox to get the analysis.
210
+ """
211
+
212
+
213
+ async def run_agent(query: str):
214
+ # Stream agent outputs
215
+ async for typ, chunk in agent.astream(
216
+ {"messages": query},
217
+ stream_mode=["values", "messages"],
218
+ ):
219
+ if typ == "messages":
220
+ print(chunk[0].content, end="")
221
+ elif typ == "values":
222
+ print("\n\n---answer---\n\n", chunk)
223
+
224
+
225
+ if __name__ == "__main__":
226
+ # Run the agent
227
+ asyncio.run(run_agent(query))
requirements.txt CHANGED
@@ -31,11 +31,11 @@ langsmith==0.3.45
31
  markdown-it-py==3.0.0
32
  markupsafe==3.0.2
33
  mdurl==0.1.2
34
- numpy==2.3.0
35
  orjson==3.10.18
36
  ormsgpack==1.10.0
37
  packaging==24.2
38
- pandas==2.3.0
39
  pillow==11.2.1
40
  pydantic==2.11.5
41
  pydantic-core==2.33.2
 
31
  markdown-it-py==3.0.0
32
  markupsafe==3.0.2
33
  mdurl==0.1.2
34
+ numpy==1.26.4
35
  orjson==3.10.18
36
  ormsgpack==1.10.0
37
  packaging==24.2
38
+ pandas==2.0.3
39
  pillow==11.2.1
40
  pydantic==2.11.5
41
  pydantic-core==2.33.2