From 30a6912938291a7a5430060f2ef2483d585079e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=205f1b81b8-4f5d-11e8-9c2d-fa7ae01?= =?UTF-8?q?bbebc?= Date: Sat, 27 Jun 2026 13:45:16 +0000 Subject: [PATCH] add: main.py --- main.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..a5ce2fc --- /dev/null +++ b/main.py @@ -0,0 +1,116 @@ +import os +import asyncio +import argparse +from typing import List, Union, Annotated + +from pydantic import BaseModel, Field, Literal +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 + +# ----------------- 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 (4xx/5xx)") + path: str = Field(..., description="Request path") + error_message: str = Field(..., description="Error message") + +ApiEvent = Annotated[Union[HttpOkEvent, HttpErrorEvent], Field(discriminator="kind")] + +# ----------------- LLM ----------------- +llm = ChatOpenAI( + model="openai/gpt-oss-20b:free", + base_url="https://openrouter.ai/api/v1", + api_key=os.getenv("OPENAI_API_KEY"), + temperature=0.0, +) + +# ----------------- Backend ----------------- +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +# ----------------- Tools ----------------- +@tool +def parse_block(block: str) -> ApiEvent: + """Parse a single log block into a typed ApiEvent using LLM structured output.""" + # Use the LLM to produce structured output + result = llm.with_structured_output(ApiEvent).invoke({"messages": [HumanMessage(content=block)]}) + # The LLM returns an ApiEvent instance directly + return result + +@tool +def parse_log(log_text: str) -> str: + """Parse the entire log text and return a formatted table of events.""" + # Split into blocks (empty lines or '---' separators) + raw_blocks = [b.strip() for b in log_text.split("\n") if b.strip()] + events: List[ApiEvent] = [] + for block in raw_blocks: + try: + event = parse_block(block) + events.append(event) + except Exception as e: + # If parsing fails, skip the block but keep a note + events.append( + HttpErrorEvent( + kind="error", + status=0, + path="", + error_message=f"Failed to parse block: {e}", + ) + ) + # Build table + header = f"{'kind':<6} | {'path':<20} | {'status':<6} | {'detail':<30}" + lines = [header, "-" * len(header)] + for ev in events: + if isinstance(ev, HttpOkEvent): + detail = f"duration {ev.duration_ms}ms" + else: + detail = ev.error_message + line = f"{ev.kind:<6} | {ev.path:<20} | {ev.status:<6} | {detail:<30}" + lines.append(line) + return "\n".join(lines) + +# ----------------- Agent ----------------- +agent = create_deep_agent( + model=llm, + tools=[parse_block, parse_log], + backend=backend, + system_prompt="You are a log parsing agent. Use the provided tools to parse the log and return a formatted table.", +) + +# ----------------- CLI ----------------- +async def run_agent(log_text: str): + result = await agent.ainvoke( + {"messages": [HumanMessage(content=log_text)]}, + {"configurable": {"thread_id": "log-session"}}, + ) + # The tool output is in the last message + print(result["messages"][-1].content) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Parse raw log into structured events.") + parser.add_argument("--file", type=str, help="Path to a log file. If omitted, uses default sample.") + args = parser.parse_args() + + if args.file: + with open(args.file, "r", encoding="utf-8") as f: + log = f.read() + else: + # Default sample log + log = """ +2023-10-01 12:00:00 INFO /api/users 200 123ms +2023-10-01 12:00:01 ERROR /api/orders 404 Not Found +2023-10-01 12:00:02 INFO /api/products 200 98ms +2023-10-01 12:00:03 ERROR /api/payments 500 Internal Server Error +""" + asyncio.run(run_agent(log))