add: main.py
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import os
|
||||
import argparse
|
||||
import asyncio
|
||||
from typing import Union, Annotated
|
||||
from pydantic import BaseModel, Field
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain.tools import tool
|
||||
from deepagents import create_deep_agent
|
||||
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
|
||||
from langchain_core.output_parsers import PydanticOutputParser
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load API key from .env
|
||||
load_dotenv()
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
# Pydantic models
|
||||
class HttpOkEvent(BaseModel):
|
||||
kind: Literal["ok"] = Field(description="Event kind: ok")
|
||||
status: Literal[200] = Field(description="HTTP status code")
|
||||
path: str = Field(description="Request path")
|
||||
duration_ms: int = Field(description="Duration in milliseconds")
|
||||
|
||||
class HttpErrorEvent(BaseModel):
|
||||
kind: Literal["error"] = Field(description="Event kind: error")
|
||||
status: int = Field(description="HTTP status code")
|
||||
path: str = Field(description="Request path")
|
||||
error_message: str = Field(description="Error message")
|
||||
|
||||
ApiEvent = Annotated[
|
||||
Union[HttpOkEvent, HttpErrorEvent],
|
||||
Field(discriminator="kind")
|
||||
]
|
||||
|
||||
# Structured output parser
|
||||
parser = PydanticOutputParser(pydantic_object=ApiEvent)
|
||||
|
||||
# LLM configuration
|
||||
llm = ChatOpenAI(
|
||||
model="openai/gpt-oss-20b:free",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=OPENAI_API_KEY,
|
||||
temperature=0.0,
|
||||
)
|
||||
|
||||
# Backend for deepagents
|
||||
backend = CompositeBackend([
|
||||
LocalShellBackend(workspace_dir="./workspace"),
|
||||
FilesystemBackend(),
|
||||
])
|
||||
|
||||
# System prompt for the agent
|
||||
system_prompt = (
|
||||
"You are a log parser. "
|
||||
"Parse the given log line and output JSON that matches the following schema. "
|
||||
f"{parser.get_format_instructions()} "
|
||||
"Return only the JSON."
|
||||
)
|
||||
|
||||
# Create the agent
|
||||
agent = create_deep_agent(
|
||||
model=llm,
|
||||
tools=[],
|
||||
backend=backend,
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
|
||||
async def parse_line(line: str) -> ApiEvent:
|
||||
"""Parse a single log line using the agent and return a typed event."""
|
||||
result = await agent.ainvoke(
|
||||
{"messages": [HumanMessage(content=line)]},
|
||||
{"configurable": {"thread_id": "parser"}},
|
||||
)
|
||||
content = result["messages"][-1].content
|
||||
try:
|
||||
event = parser.parse(content)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to parse line: {line!r}. Error: {e}") from e
|
||||
return event
|
||||
|
||||
def print_table(events):
|
||||
"""Print a simple table of events."""
|
||||
header = f"{'kind':<6} | {'path':<20} | {'status':<6} | {'detail':<30}"
|
||||
print(header)
|
||||
print("-" * len(header))
|
||||
for ev in events:
|
||||
if ev.kind == "ok":
|
||||
detail = f"duration {ev.duration_ms}ms"
|
||||
else:
|
||||
detail = ev.error_message
|
||||
print(f"{ev.kind:<6} | {ev.path:<20} | {ev.status:<6} | {detail:<30}")
|
||||
|
||||
async def main():
|
||||
parser_cli = argparse.ArgumentParser(description="Parse raw log lines into typed events.")
|
||||
parser_cli.add_argument(
|
||||
"--log",
|
||||
type=str,
|
||||
help="Raw log string (multiple lines). If omitted, a sample log is used.",
|
||||
)
|
||||
parser_cli.add_argument(
|
||||
"--file",
|
||||
type=str,
|
||||
help="Path to a file containing raw log lines.",
|
||||
)
|
||||
args = parser_cli.parse_args()
|
||||
|
||||
if args.file:
|
||||
with open(args.file, "r", encoding="utf-8") as f:
|
||||
raw_log = f.read()
|
||||
elif args.log:
|
||||
raw_log = args.log
|
||||
else:
|
||||
raw_log = (
|
||||
"GET /api/users 200 123ms\n"
|
||||
"POST /api/login 404 Not Found\n"
|
||||
"GET /api/data 200 45ms\n"
|
||||
"DELETE /api/item/42 500 Internal Server Error\n"
|
||||
"PUT /api/update 200 78ms"
|
||||
)
|
||||
|
||||
lines = [line.strip() for line in raw_log.splitlines() if line.strip()]
|
||||
events = []
|
||||
for line in lines:
|
||||
try:
|
||||
event = await parse_line(line)
|
||||
events.append(event)
|
||||
print(event.model_dump())
|
||||
except Exception as e:
|
||||
print(f"Error parsing line: {line!r}. {e}")
|
||||
|
||||
print("\nParsed events table:")
|
||||
print_table(events)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user