Spaces:
Running
Running
ai-puppy
commited on
Commit
·
3495357
1
Parent(s):
9861912
save
Browse files- agent.py +227 -0
- 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==
|
35 |
orjson==3.10.18
|
36 |
ormsgpack==1.10.0
|
37 |
packaging==24.2
|
38 |
-
pandas==2.3
|
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
|