netdocai 0.1.0__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.
- netdocai/__init__.py +0 -0
- netdocai/agent.py +320 -0
- netdocai/config.py +44 -0
- netdocai/gemini.py +44 -0
- netdocai/knowledge/dns.md +55 -0
- netdocai/knowledge/http.md +46 -0
- netdocai/knowledge/linux_networking.md +51 -0
- netdocai/knowledge/tcp.md +42 -0
- netdocai/main.py +85 -0
- netdocai/mcp_server.py +58 -0
- netdocai/onboarding.py +148 -0
- netdocai/prompts.py +29 -0
- netdocai/rag.py +148 -0
- netdocai/report.py +78 -0
- netdocai/tools.py +88 -0
- netdocai-0.1.0.dist-info/METADATA +585 -0
- netdocai-0.1.0.dist-info/RECORD +20 -0
- netdocai-0.1.0.dist-info/WHEEL +5 -0
- netdocai-0.1.0.dist-info/entry_points.txt +2 -0
- netdocai-0.1.0.dist-info/top_level.txt +1 -0
netdocai/__init__.py
ADDED
|
File without changes
|
netdocai/agent.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Any, TypedDict
|
|
4
|
+
|
|
5
|
+
from google.genai import types
|
|
6
|
+
from .gemini import generate_content
|
|
7
|
+
from langgraph.graph import END, START, StateGraph
|
|
8
|
+
from mcp import ClientSession, StdioServerParameters
|
|
9
|
+
from mcp.client.stdio import stdio_client
|
|
10
|
+
|
|
11
|
+
from .prompts import SYSTEM_PROMPT, build_user_prompt
|
|
12
|
+
from .report import build_report
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
MAX_STEPS = 8
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AgentState(TypedDict):
|
|
19
|
+
problem: str
|
|
20
|
+
history: list[dict[str, Any]]
|
|
21
|
+
tool_definitions: list[dict[str, Any]]
|
|
22
|
+
pending_calls: list[dict[str, Any]]
|
|
23
|
+
observations: list[dict[str, Any]]
|
|
24
|
+
hypothesis: str
|
|
25
|
+
response: str
|
|
26
|
+
steps: int
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_mcp_tools(
|
|
30
|
+
tool_definitions: list[dict[str, Any]],
|
|
31
|
+
) -> list[types.Tool]:
|
|
32
|
+
declarations = []
|
|
33
|
+
|
|
34
|
+
for tool in tool_definitions:
|
|
35
|
+
declarations.append(
|
|
36
|
+
types.FunctionDeclaration(
|
|
37
|
+
name=tool["name"],
|
|
38
|
+
description=tool["description"],
|
|
39
|
+
parameters_json_schema=tool["input_schema"],
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return [types.Tool(function_declarations=declarations)]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def get_tool_definitions() -> list[dict[str, Any]]:
|
|
47
|
+
server_params = StdioServerParameters(
|
|
48
|
+
command=sys.executable,
|
|
49
|
+
args=["-m", "netdocai.mcp_server"],
|
|
50
|
+
env=os.environ.copy(),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
async with stdio_client(server_params) as (read, write):
|
|
54
|
+
async with ClientSession(read, write) as session:
|
|
55
|
+
await session.initialize()
|
|
56
|
+
|
|
57
|
+
result = await session.list_tools()
|
|
58
|
+
|
|
59
|
+
return [
|
|
60
|
+
{
|
|
61
|
+
"name": tool.name,
|
|
62
|
+
"description": tool.description or "",
|
|
63
|
+
"input_schema": tool.input_schema,
|
|
64
|
+
}
|
|
65
|
+
for tool in result.tools
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def plan(state: AgentState) -> AgentState:
|
|
70
|
+
contents = [
|
|
71
|
+
types.Content.model_validate(message)
|
|
72
|
+
for message in state["history"]
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
gemini_tools = build_mcp_tools(state["tool_definitions"])
|
|
76
|
+
|
|
77
|
+
response = await generate_content(
|
|
78
|
+
contents=contents,
|
|
79
|
+
config=types.GenerateContentConfig(
|
|
80
|
+
tools=gemini_tools,
|
|
81
|
+
automatic_function_calling=types.AutomaticFunctionCallingConfig(
|
|
82
|
+
disable=True
|
|
83
|
+
),
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
function_calls = response.function_calls
|
|
88
|
+
|
|
89
|
+
if not function_calls:
|
|
90
|
+
print("[Agent] Finalizing diagnosis...")
|
|
91
|
+
|
|
92
|
+
report = build_report(
|
|
93
|
+
problem=state["problem"],
|
|
94
|
+
observations=state["observations"],
|
|
95
|
+
hypothesis=state["hypothesis"],
|
|
96
|
+
conclusion=response.text or "",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
**state,
|
|
101
|
+
"response": report,
|
|
102
|
+
"pending_calls": [],
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
model_content = response.candidates[0].content
|
|
106
|
+
|
|
107
|
+
updated_history = [
|
|
108
|
+
*state["history"],
|
|
109
|
+
model_content.model_dump(exclude_none=True),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
pending_calls = [
|
|
113
|
+
{
|
|
114
|
+
"name": call.name,
|
|
115
|
+
"args": call.args or {},
|
|
116
|
+
}
|
|
117
|
+
for call in function_calls
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
print(
|
|
121
|
+
"[Agent] "
|
|
122
|
+
+ ", ".join(call["name"] for call in pending_calls)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
**state,
|
|
127
|
+
"history": updated_history,
|
|
128
|
+
"pending_calls": pending_calls,
|
|
129
|
+
"steps": state["steps"] + 1,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def execute_tools(state: AgentState) -> AgentState:
|
|
134
|
+
server_params = StdioServerParameters(
|
|
135
|
+
command=sys.executable,
|
|
136
|
+
args=["-m", "netdocai.mcp_server"],
|
|
137
|
+
env=os.environ.copy(),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
updated_history = list(state["history"])
|
|
141
|
+
observations = list(state["observations"])
|
|
142
|
+
|
|
143
|
+
async with stdio_client(server_params) as (read, write):
|
|
144
|
+
async with ClientSession(read, write) as session:
|
|
145
|
+
await session.initialize()
|
|
146
|
+
|
|
147
|
+
for call in state["pending_calls"]:
|
|
148
|
+
name = call["name"]
|
|
149
|
+
arguments = call["args"]
|
|
150
|
+
|
|
151
|
+
print(f"[Tool] Executing {name}...")
|
|
152
|
+
|
|
153
|
+
result = await session.call_tool(
|
|
154
|
+
name,
|
|
155
|
+
arguments=arguments,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
result_text = "\n".join(
|
|
159
|
+
content.text
|
|
160
|
+
for content in result.content
|
|
161
|
+
if hasattr(content, "text") and content.text
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if not result_text:
|
|
165
|
+
result_text = "Tool returned no text result."
|
|
166
|
+
|
|
167
|
+
print(f"[Tool] {name} completed.")
|
|
168
|
+
|
|
169
|
+
observations.append(
|
|
170
|
+
{
|
|
171
|
+
"tool": name,
|
|
172
|
+
"arguments": arguments,
|
|
173
|
+
"result": result_text,
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
function_response = types.Part.from_function_response(
|
|
178
|
+
name=name,
|
|
179
|
+
response={"result": result_text},
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
updated_history.append(
|
|
183
|
+
types.Content(
|
|
184
|
+
role="user",
|
|
185
|
+
parts=[function_response],
|
|
186
|
+
).model_dump(exclude_none=True)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
**state,
|
|
191
|
+
"history": updated_history,
|
|
192
|
+
"observations": observations,
|
|
193
|
+
"pending_calls": [],
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
async def assess(state: AgentState) -> AgentState:
|
|
198
|
+
observation_text = "\n\n".join(
|
|
199
|
+
f"Tool: {item['tool']}\n"
|
|
200
|
+
f"Arguments: {item['arguments']}\n"
|
|
201
|
+
f"Result:\n{item['result']}"
|
|
202
|
+
for item in state["observations"]
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
prompt = f"""
|
|
206
|
+
You are assessing an ongoing network troubleshooting investigation.
|
|
207
|
+
|
|
208
|
+
Problem:
|
|
209
|
+
{state["problem"]}
|
|
210
|
+
|
|
211
|
+
Observed evidence:
|
|
212
|
+
{observation_text}
|
|
213
|
+
|
|
214
|
+
Current hypothesis:
|
|
215
|
+
{state["hypothesis"] or "None yet."}
|
|
216
|
+
|
|
217
|
+
Based only on the available evidence:
|
|
218
|
+
|
|
219
|
+
1. State the most likely current hypothesis.
|
|
220
|
+
2. Identify the strongest supporting evidence.
|
|
221
|
+
3. Identify what remains uncertain.
|
|
222
|
+
4. Do not claim that a test was performed unless it appears in the evidence.
|
|
223
|
+
|
|
224
|
+
Keep the assessment concise.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
response = await generate_content(
|
|
228
|
+
contents=prompt,
|
|
229
|
+
config=types.GenerateContentConfig(),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
**state,
|
|
234
|
+
"hypothesis": response.text or "",
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
async def finalize(state: AgentState) -> dict[str, Any]:
|
|
239
|
+
report = build_report(
|
|
240
|
+
problem=state["problem"],
|
|
241
|
+
observations=state["observations"],
|
|
242
|
+
hypothesis=state["hypothesis"],
|
|
243
|
+
conclusion=(
|
|
244
|
+
"The investigation reached the maximum number of diagnostic "
|
|
245
|
+
"steps before a final diagnosis was produced."
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
"response": report,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def route_after_plan(state: AgentState) -> str:
|
|
255
|
+
if state["response"]:
|
|
256
|
+
return "done"
|
|
257
|
+
|
|
258
|
+
if state["steps"] > MAX_STEPS:
|
|
259
|
+
return "finalize"
|
|
260
|
+
|
|
261
|
+
return "execute"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
graph_builder = StateGraph(AgentState)
|
|
265
|
+
|
|
266
|
+
graph_builder.add_node("plan", plan)
|
|
267
|
+
graph_builder.add_node("execute_tools", execute_tools)
|
|
268
|
+
graph_builder.add_node("assess", assess)
|
|
269
|
+
graph_builder.add_node("finalize", finalize)
|
|
270
|
+
|
|
271
|
+
graph_builder.add_edge(START, "plan")
|
|
272
|
+
|
|
273
|
+
graph_builder.add_conditional_edges(
|
|
274
|
+
"plan",
|
|
275
|
+
route_after_plan,
|
|
276
|
+
{
|
|
277
|
+
"execute": "execute_tools",
|
|
278
|
+
"finalize": "finalize",
|
|
279
|
+
"done": END,
|
|
280
|
+
},
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
graph_builder.add_edge("execute_tools", "assess")
|
|
284
|
+
graph_builder.add_edge("assess", "plan")
|
|
285
|
+
graph_builder.add_edge("finalize", END)
|
|
286
|
+
|
|
287
|
+
graph = graph_builder.compile()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
async def run_agent(problem: str) -> str:
|
|
291
|
+
tool_definitions = await get_tool_definitions()
|
|
292
|
+
|
|
293
|
+
initial_history = [
|
|
294
|
+
{
|
|
295
|
+
"role": "user",
|
|
296
|
+
"parts": [
|
|
297
|
+
{
|
|
298
|
+
"text": (
|
|
299
|
+
f"{SYSTEM_PROMPT}\n\n"
|
|
300
|
+
f"{build_user_prompt(problem)}"
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
result = await graph.ainvoke(
|
|
308
|
+
{
|
|
309
|
+
"problem": problem,
|
|
310
|
+
"history": initial_history,
|
|
311
|
+
"tool_definitions": tool_definitions,
|
|
312
|
+
"pending_calls": [],
|
|
313
|
+
"observations": [],
|
|
314
|
+
"hypothesis": "",
|
|
315
|
+
"response": "",
|
|
316
|
+
"steps": 0,
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return result["response"]
|
netdocai/config.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DEFAULT_GEMINI_MODEL = "gemini-3.6-flash"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class Settings:
|
|
12
|
+
gemini_api_key: str
|
|
13
|
+
gemini_model: str = DEFAULT_GEMINI_MODEL
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_env(cls) -> "Settings":
|
|
17
|
+
load_dotenv()
|
|
18
|
+
|
|
19
|
+
api_key = os.getenv("GEMINI_API_KEY", "").strip()
|
|
20
|
+
|
|
21
|
+
if not api_key:
|
|
22
|
+
raise RuntimeError(
|
|
23
|
+
"Missing required environment variable: GEMINI_API_KEY"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
model = os.getenv(
|
|
27
|
+
"GEMINI_MODEL",
|
|
28
|
+
DEFAULT_GEMINI_MODEL,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return cls(
|
|
32
|
+
gemini_api_key=api_key,
|
|
33
|
+
gemini_model=model,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LazySettings:
|
|
38
|
+
def __getattr__(self, name: str):
|
|
39
|
+
return getattr(Settings.from_env(), name)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
settings = LazySettings()
|
|
43
|
+
|
|
44
|
+
__all__ = ["Settings", "settings"]
|
netdocai/gemini.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from google import genai
|
|
5
|
+
from google.genai import types
|
|
6
|
+
from google.genai.errors import ClientError, ServerError
|
|
7
|
+
|
|
8
|
+
from .config import settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def generate_content(
|
|
12
|
+
contents: Any,
|
|
13
|
+
config: types.GenerateContentConfig,
|
|
14
|
+
retries: int = 3,
|
|
15
|
+
) -> Any:
|
|
16
|
+
async with genai.Client(api_key=settings.gemini_api_key).aio as client:
|
|
17
|
+
for attempt in range(retries):
|
|
18
|
+
try:
|
|
19
|
+
return await client.models.generate_content(
|
|
20
|
+
model=settings.gemini_model,
|
|
21
|
+
contents=contents,
|
|
22
|
+
config=config,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
except ServerError:
|
|
26
|
+
if attempt == retries - 1:
|
|
27
|
+
raise
|
|
28
|
+
|
|
29
|
+
except ClientError as exc:
|
|
30
|
+
status_code = getattr(exc, "code", None)
|
|
31
|
+
|
|
32
|
+
if status_code != 429 or attempt == retries - 1:
|
|
33
|
+
raise
|
|
34
|
+
|
|
35
|
+
delay = 2 ** attempt
|
|
36
|
+
|
|
37
|
+
print(
|
|
38
|
+
f"[Gemini] Temporary API failure. "
|
|
39
|
+
f"Retrying in {delay}s..."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
await asyncio.sleep(delay)
|
|
43
|
+
|
|
44
|
+
raise RuntimeError("Gemini request failed after all retries.")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# DNS Troubleshooting
|
|
2
|
+
|
|
3
|
+
DNS translates domain names into IP addresses.
|
|
4
|
+
|
|
5
|
+
## Common Outcomes
|
|
6
|
+
|
|
7
|
+
### Successful Resolution
|
|
8
|
+
|
|
9
|
+
A hostname resolves when the DNS resolver returns one or more valid IP addresses.
|
|
10
|
+
|
|
11
|
+
Check both IPv4 and IPv6 when troubleshooting connectivity:
|
|
12
|
+
|
|
13
|
+
- A record -> IPv4 address
|
|
14
|
+
- AAAA record -> IPv6 address
|
|
15
|
+
|
|
16
|
+
### NXDOMAIN
|
|
17
|
+
|
|
18
|
+
NXDOMAIN means the queried domain name does not exist according to the DNS server.
|
|
19
|
+
|
|
20
|
+
Possible causes:
|
|
21
|
+
- Typo in hostname
|
|
22
|
+
- Missing DNS record
|
|
23
|
+
- Incorrect domain configuration
|
|
24
|
+
|
|
25
|
+
### SERVFAIL
|
|
26
|
+
|
|
27
|
+
SERVFAIL means the DNS server failed to provide a valid answer.
|
|
28
|
+
|
|
29
|
+
Possible causes:
|
|
30
|
+
- Upstream DNS failure
|
|
31
|
+
- DNSSEC validation failure
|
|
32
|
+
- Authoritative nameserver problems
|
|
33
|
+
- Temporary resolver problems
|
|
34
|
+
|
|
35
|
+
### DNS Timeouts
|
|
36
|
+
|
|
37
|
+
A timeout means the DNS query did not receive a response within the expected period.
|
|
38
|
+
|
|
39
|
+
Possible causes:
|
|
40
|
+
- Network connectivity problems
|
|
41
|
+
- Unreachable resolver
|
|
42
|
+
- Firewall filtering
|
|
43
|
+
- Resolver overload
|
|
44
|
+
|
|
45
|
+
### Troubleshooting
|
|
46
|
+
|
|
47
|
+
When investigating DNS problems:
|
|
48
|
+
|
|
49
|
+
1. Check whether the hostname resolves.
|
|
50
|
+
2. Check both A and AAAA records.
|
|
51
|
+
3. Measure DNS response time.
|
|
52
|
+
4. Compare results using another resolver.
|
|
53
|
+
5. Check whether the problem is intermittent or persistent.
|
|
54
|
+
|
|
55
|
+
DNS problems can appear as application failures even when TCP and HTTP connectivity are otherwise healthy.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# HTTP Troubleshooting
|
|
2
|
+
|
|
3
|
+
HTTP operates above the transport layer and is used by web applications and APIs.
|
|
4
|
+
|
|
5
|
+
## HTTP Status Codes
|
|
6
|
+
|
|
7
|
+
### 2xx
|
|
8
|
+
|
|
9
|
+
Successful request.
|
|
10
|
+
|
|
11
|
+
### 4xx
|
|
12
|
+
|
|
13
|
+
Client-side request error.
|
|
14
|
+
|
|
15
|
+
### 5xx
|
|
16
|
+
|
|
17
|
+
Server-side error.
|
|
18
|
+
|
|
19
|
+
## Response Time
|
|
20
|
+
|
|
21
|
+
HTTP response time measures how long the client waits for the server response.
|
|
22
|
+
|
|
23
|
+
High response time can result from:
|
|
24
|
+
|
|
25
|
+
- Slow application processing
|
|
26
|
+
- Slow upstream services
|
|
27
|
+
- Server overload
|
|
28
|
+
- Network latency
|
|
29
|
+
- Backend database delays
|
|
30
|
+
|
|
31
|
+
## Time To First Byte
|
|
32
|
+
|
|
33
|
+
TTFB measures the time until the first byte of the response is received.
|
|
34
|
+
|
|
35
|
+
High TTFB can indicate that the server or an upstream dependency is taking a long time to begin generating the response.
|
|
36
|
+
|
|
37
|
+
## Troubleshooting
|
|
38
|
+
|
|
39
|
+
When investigating HTTP slowness:
|
|
40
|
+
|
|
41
|
+
1. Check the HTTP status code.
|
|
42
|
+
2. Measure total response time.
|
|
43
|
+
3. Measure TTFB when available.
|
|
44
|
+
4. Compare multiple requests.
|
|
45
|
+
5. Compare HTTP and HTTPS behavior.
|
|
46
|
+
6. Determine whether the issue is persistent or intermittent.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Linux Networking Troubleshooting
|
|
2
|
+
|
|
3
|
+
Linux provides several tools for investigating network connectivity.
|
|
4
|
+
|
|
5
|
+
## Ping
|
|
6
|
+
|
|
7
|
+
Ping measures ICMP reachability and round-trip latency.
|
|
8
|
+
|
|
9
|
+
Useful metrics include:
|
|
10
|
+
|
|
11
|
+
- Packet loss
|
|
12
|
+
- Minimum latency
|
|
13
|
+
- Average latency
|
|
14
|
+
- Maximum latency
|
|
15
|
+
- Jitter
|
|
16
|
+
|
|
17
|
+
Ping does not prove that TCP or HTTP services are working.
|
|
18
|
+
|
|
19
|
+
## DNS
|
|
20
|
+
|
|
21
|
+
Common commands include:
|
|
22
|
+
|
|
23
|
+
- getent hosts
|
|
24
|
+
- dig
|
|
25
|
+
- nslookup
|
|
26
|
+
|
|
27
|
+
Use DNS tools to determine whether hostname resolution is working correctly.
|
|
28
|
+
|
|
29
|
+
## TCP
|
|
30
|
+
|
|
31
|
+
Useful tools include:
|
|
32
|
+
|
|
33
|
+
- nc
|
|
34
|
+
- curl
|
|
35
|
+
- ss
|
|
36
|
+
|
|
37
|
+
A TCP connection test checks whether a destination port can be reached.
|
|
38
|
+
|
|
39
|
+
## HTTP
|
|
40
|
+
|
|
41
|
+
curl can be used to test HTTP and HTTPS endpoints and inspect:
|
|
42
|
+
|
|
43
|
+
- Status code
|
|
44
|
+
- Response headers
|
|
45
|
+
- Response timing
|
|
46
|
+
|
|
47
|
+
## Traceroute
|
|
48
|
+
|
|
49
|
+
Traceroute can help identify routing paths and where latency or packet loss may occur.
|
|
50
|
+
|
|
51
|
+
A single failed or missing traceroute hop does not necessarily mean that the network path is broken because routers may filter or deprioritize diagnostic traffic.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# TCP Troubleshooting
|
|
2
|
+
|
|
3
|
+
TCP provides reliable, connection-oriented communication between applications.
|
|
4
|
+
|
|
5
|
+
## TCP Connection
|
|
6
|
+
|
|
7
|
+
A successful TCP connection indicates that a client was able to establish a connection to the destination host and port.
|
|
8
|
+
|
|
9
|
+
For HTTPS, the usual destination port is 443.
|
|
10
|
+
|
|
11
|
+
## Connection Failures
|
|
12
|
+
|
|
13
|
+
Common causes include:
|
|
14
|
+
|
|
15
|
+
- Destination service is not listening
|
|
16
|
+
- Firewall filtering
|
|
17
|
+
- Network routing problems
|
|
18
|
+
- Server unavailable
|
|
19
|
+
- Incorrect port
|
|
20
|
+
|
|
21
|
+
## Connection Latency
|
|
22
|
+
|
|
23
|
+
Measure the time required to establish a TCP connection.
|
|
24
|
+
|
|
25
|
+
High TCP connection latency can indicate:
|
|
26
|
+
|
|
27
|
+
- Network congestion
|
|
28
|
+
- Routing problems
|
|
29
|
+
- High server load
|
|
30
|
+
- Packet loss and retransmissions
|
|
31
|
+
|
|
32
|
+
## Troubleshooting
|
|
33
|
+
|
|
34
|
+
When investigating TCP problems:
|
|
35
|
+
|
|
36
|
+
1. Confirm the destination hostname resolves.
|
|
37
|
+
2. Attempt a TCP connection to the expected port.
|
|
38
|
+
3. Measure connection latency.
|
|
39
|
+
4. Compare results across repeated attempts.
|
|
40
|
+
5. Check whether failures are intermittent or persistent.
|
|
41
|
+
|
|
42
|
+
A successful TCP connection does not prove that the application itself is healthy.
|
netdocai/main.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
from rich.prompt import Prompt
|
|
6
|
+
|
|
7
|
+
from .onboarding import get_api_key
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> None:
|
|
14
|
+
console.print(
|
|
15
|
+
Panel(
|
|
16
|
+
"[bold]NetDocAI[/bold]\n"
|
|
17
|
+
"Autonomous Network Troubleshooter",
|
|
18
|
+
expand=False,
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
console.print()
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
get_api_key()
|
|
26
|
+
except (KeyboardInterrupt, EOFError):
|
|
27
|
+
console.print("\n[yellow]Setup cancelled.[/yellow]")
|
|
28
|
+
return
|
|
29
|
+
except OSError as exc:
|
|
30
|
+
console.print(f"\n[red]Configuration error: {exc}[/red]")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
from .agent import run_agent
|
|
34
|
+
|
|
35
|
+
console.print()
|
|
36
|
+
|
|
37
|
+
problem = Prompt.ask(
|
|
38
|
+
"[bold]Describe your network problem[/bold]"
|
|
39
|
+
).strip()
|
|
40
|
+
|
|
41
|
+
if not problem:
|
|
42
|
+
console.print("[red]No problem provided.[/red]")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
console.print()
|
|
46
|
+
console.print(
|
|
47
|
+
Panel(
|
|
48
|
+
problem,
|
|
49
|
+
title="Problem",
|
|
50
|
+
expand=False,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
console.print()
|
|
55
|
+
console.print("[bold cyan]Investigating...[/bold cyan]")
|
|
56
|
+
console.print()
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
with console.status(
|
|
60
|
+
"[bold cyan]NetDocAI is investigating...[/bold cyan]",
|
|
61
|
+
spinner="dots",
|
|
62
|
+
):
|
|
63
|
+
response = asyncio.run(run_agent(problem))
|
|
64
|
+
|
|
65
|
+
console.print()
|
|
66
|
+
console.print(
|
|
67
|
+
Panel(
|
|
68
|
+
response,
|
|
69
|
+
title="NetDocAI Diagnostic Report",
|
|
70
|
+
border_style="green",
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
console.print()
|
|
75
|
+
console.print(
|
|
76
|
+
Panel(
|
|
77
|
+
f"[red]{exc}[/red]",
|
|
78
|
+
title="Agent Error",
|
|
79
|
+
border_style="red",
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
main()
|