From b6839a0c4ae2e6b0cc630a2738205cd52b97ee2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=92=D0=B8=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= Date: Tue, 30 Jun 2026 07:58:51 +0000 Subject: [PATCH] add: main.py --- main.py | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..136a7fe --- /dev/null +++ b/main.py @@ -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()) \ No newline at end of file