Add agent.py
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import os
|
||||
import argparse
|
||||
from typing import Annotated, Union
|
||||
from pydantic import BaseModel, Field, ValidationError, Literal
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.output_parsers import PydanticOutputParser
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables for API key
|
||||
load_dotenv()
|
||||
|
||||
# Define Pydantic models for log events
|
||||
class HttpOkEvent(BaseModel):
|
||||
kind: Literal["ok"] = "ok"
|
||||
status: int = Field(..., description="HTTP status code")
|
||||
path: str
|
||||
|
||||
class HttpErrorEvent(BaseModel):
|
||||
kind: Literal["error"] = "error"
|
||||
status: int = Field(..., description="HTTP status code")
|
||||
path: str
|
||||
error: str
|
||||
|
||||
# Union type with discriminator
|
||||
ApiEvent = Annotated[
|
||||
Union[HttpOkEvent, HttpErrorEvent],
|
||||
Field(discriminator="kind")
|
||||
]
|
||||
|
||||
# Structured output parser
|
||||
parser = PydanticOutputParser(pydantic_object=ApiEvent)
|
||||
|
||||
# LLM instance
|
||||
llm = ChatOpenAI(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
base_url=os.getenv("OPENAI_BASE_URL"),
|
||||
model=os.getenv("OPENAI_MODEL", "gpt-3.5-turbo"),
|
||||
)
|
||||
|
||||
|
||||
def parse_line(line: str) -> ApiEvent:
|
||||
"""Parse a single log line using LLM and structured output parser."""
|
||||
prompt = (
|
||||
f"You are a log parser. {parser.get_format_instructions()}\n"
|
||||
f"Log line: {line}\n"
|
||||
)
|
||||
try:
|
||||
raw_output = llm.invoke(prompt)
|
||||
if hasattr(raw_output, "content"):
|
||||
raw_text = raw_output.content
|
||||
else:
|
||||
raw_text = str(raw_output)
|
||||
event = parser.parse(raw_text)
|
||||
return event
|
||||
except Exception as e:
|
||||
# Fallback: simple regex parsing
|
||||
# This fallback is only for safety and not part of primary implementation.
|
||||
# It attempts to extract status, path, and error if present.
|
||||
# If parsing fails, raise the original exception.
|
||||
import re
|
||||
error_pattern = r"(?P<status>\d{3})\s+(?P<path>\S+)(?:\s+error:\s+(?P<error>.+))?"
|
||||
match = re.search(error_pattern, line)
|
||||
if match:
|
||||
groups = match.groupdict()
|
||||
status = int(groups["status"])
|
||||
path = groups["path"]
|
||||
if status >= 400:
|
||||
return HttpErrorEvent(kind="error", status=status, path=path, error=groups.get("error", ""))
|
||||
else:
|
||||
return HttpOkEvent(kind="ok", status=status, path=path)
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def summarize_events(events: list[ApiEvent]) -> None:
|
||||
"""Print a summary table of events."""
|
||||
header = f"{'Kind':<6} {'Status':<6} {'Path':<20} {'Error':<30}"
|
||||
print(header)
|
||||
print('-' * len(header))
|
||||
for e in events:
|
||||
if isinstance(e, HttpOkEvent):
|
||||
print(f"{e.kind:<6} {e.status:<6} {e.path:<20} {'':<30}")
|
||||
elif isinstance(e, HttpErrorEvent):
|
||||
print(f"{e.kind:<6} {e.status:<6} {e.path:<20} {e.error:<30}")
|
||||
|
||||
|
||||
def main():
|
||||
parser_cli = argparse.ArgumentParser(description="Parse raw log lines into typed API events.")
|
||||
parser_cli.add_argument(
|
||||
"--input",
|
||||
type=str,
|
||||
help="Path to a file containing log lines or raw log string. If omitted, a sample log is used.",
|
||||
)
|
||||
args = parser_cli.parse_args()
|
||||
|
||||
if args.input:
|
||||
if os.path.isfile(args.input):
|
||||
with open(args.input, "r", encoding="utf-8") as f:
|
||||
data = f.read()
|
||||
else:
|
||||
data = args.input
|
||||
else:
|
||||
# sample log
|
||||
data = (
|
||||
"200 /api/v1/users\n"
|
||||
"404 /api/v1/items error: Not Found\n"
|
||||
"500 /api/v1/orders error: Internal Server Error"
|
||||
)
|
||||
|
||||
# Split into lines or blocks
|
||||
lines = [line for line in data.splitlines() if line.strip()]
|
||||
events: list[ApiEvent] = []
|
||||
for line in lines:
|
||||
try:
|
||||
event = parse_line(line)
|
||||
events.append(event)
|
||||
except Exception as exc:
|
||||
print(f"Failed to parse line: {line!r}. Error: {exc}")
|
||||
|
||||
if events:
|
||||
summarize_events(events)
|
||||
else:
|
||||
print("No events parsed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user