fix(needs_fixes): 1 исправлений, 0 отстояно — main.py

This commit is contained in:
2026-07-01 19:45:22 +00:00
parent a10493dde9
commit b83c83b25c
+95 -78
View File
@@ -1,38 +1,53 @@
#!/usr/bin/env python
"""Structured log parser using DeepAgents and Pydantic v2.
This script demonstrates how to parse a raw log containing HTTP
responses into a list of typed events. It uses the DeepAgents
framework to create a lightweight agent that delegates the parsing
job to an OpenRouter LLM via structured output. The result is a
list of Pydantic models that can be printed or displayed in a table.
Usage:
python main.py # uses builtin demo log
python main.py "<your log>" # parse custom log passed as a single string
""" """
# main.py
# Structured log parsing with LangChain and Pydantic v2
# No deepagents dependency uses standard LangChain tooling
# Author: Student
# Date: 2026-06-30
import os import os
import argparse import sys
import textwrap import asyncio
from typing import List, Annotated, Union from typing import Annotated, Literal, Union, List
from pathlib import Path
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage 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 pydantic import BaseModel, Field
from langchain_core.output_parsers import PydanticOutputParser from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate from tabulate import tabulate
from pydantic import BaseModel, Field, Literal
# ---------- Pydantic models ---------- # ---------------------------------------------------------------------------
# 1. Pydantic models Union with discriminator
# ---------------------------------------------------------------------------
class HttpOkEvent(BaseModel): class HttpOkEvent(BaseModel):
kind: Literal["ok"] = Field("ok", description="Event kind: ok") kind: Literal["ok"] = Field("ok", description="Event kind OK")
status: Literal[200] = Field(200, description="HTTP status code") status: Literal[200] = Field(200, description="HTTP status code")
path: str = Field(..., description="Requested path") path: str = Field(..., description="Request path")
duration_ms: int = Field(..., description="Duration in milliseconds") duration_ms: int = Field(..., description="Response time in milliseconds")
class HttpErrorEvent(BaseModel): class HttpErrorEvent(BaseModel):
kind: Literal["error"] = Field("error", description="Event kind: error") kind: Literal["error"] = Field("error", description="Event kind Error")
status: int = Field(..., description="HTTP error status code (4xx/5xx)") status: int = Field(..., description="HTTP status code (4xx/5xx)")
path: str = Field(..., description="Requested path") path: str = Field(..., description="Request path")
error_message: str = Field(..., description="Error description") error_message: str = Field(..., description="Error description")
ApiEvent = Annotated[Union[HttpOkEvent, HttpErrorEvent], Field(discriminator="kind")] ApiEvent = Annotated[Union[HttpOkEvent, HttpErrorEvent], Field(discriminator="kind")]
# ---------- LLM & Parser ---------- # ---------------------------------------------------------------------------
# 2. LLM and Agent setup
# ---------------------------------------------------------------------------
llm = ChatOpenAI( llm = ChatOpenAI(
model="openai/gpt-oss-20b:free", model="openai/gpt-oss-20b:free",
@@ -41,81 +56,83 @@ llm = ChatOpenAI(
temperature=0.0, temperature=0.0,
) )
parser = PydanticOutputParser(pydantic_object=ApiEvent) backend = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"),
prompt = ChatPromptTemplate.from_messages([ FilesystemBackend(),
("system", "You are a log parser that outputs structured events as JSON.")
]) ])
# ---------- Helper functions ---------- # The agent will simply forward the prompt to the LLM and return the
# structured output. No additional tools are required for this task.
agent = create_deep_agent(
model=llm,
tools=[],
backend=backend,
system_prompt="You are a logparsing assistant. Return a structured
representation of the event using the provided Pydantic models.",
)
def parse_block(block: str) -> ApiEvent: # ---------------------------------------------------------------------------
"""Parse a single log block using the LLM and structured output parser.""" # 3. Parsing helper
# Construct prompt for the block # ---------------------------------------------------------------------------
messages = [HumanMessage(content=block.strip())]
# Ask LLM to output JSON matching the ApiEvent schema
response = llm.invoke(messages)
# Parse the response JSON into the Pydantic model
return parser.parse(response.content)
parser = PydanticOutputParser(pydantic_object=ApiEvent)
def split_blocks(text: str) -> List[str]: async def parse_block(block: str) -> ApiEvent:
"""Split raw log text into individual event blocks. """Ask the LLM to parse a single log block into an ApiEvent.
Supports both linebyline and '---' separators.
The LLM is instructed to output only the JSON that matches the
Pydantic schema. The parser then validates and returns the model.
""" """
if "---" in text: prompt = (
return [b.strip() for b in text.split("---") if b.strip()] "Parse the following log entry and return a JSON object that matches "
return [line.strip() for line in text.splitlines() if line.strip()] "one of the following schemas: HttpOkEvent or HttpErrorEvent. "
"Do not include any additional keys or text.
"""
f"Log entry:\n{block.strip()}"
)
response = await agent.ainvoke(
{"messages": [HumanMessage(content=prompt)]},
{"configurable": {"thread_id": "parser"}},
)
content = response["messages"][-1].content
return parser.parse(content)
# ---------- CLI ---------- # ---------------------------------------------------------------------------
# 4. Main logic
# ---------------------------------------------------------------------------
DEFAULT_LOG = textwrap.dedent(""" DEFAULT_LOG = """"""
200 /api/users 120ms DEFAULT_LOG += "GET /api/users 200 123ms\n"
404 /api/unknown 30ms DEFAULT_LOG += "POST /api/login 404 Not Found\n"
500 /api/orders 250ms DEFAULT_LOG += "GET /api/data 500 Internal Server Error\n"
200 /api/products 80ms DEFAULT_LOG += "PUT /api/update 200 98ms\n"
403 /api/admin 15ms DEFAULT_LOG += "DELETE /api/remove 403 Forbidden\n"
""")
def main(): async def main():
parser_cli = argparse.ArgumentParser(description="Parse raw log into structured events.") raw_log = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_LOG
parser_cli.add_argument("--log", type=str, help="Path to log file or raw log string.") # Split into blocks simple newline split but ignore empty lines
args = parser_cli.parse_args() blocks = [b for b in raw_log.strip().split("\n") if b]
if args.log:
if os.path.isfile(args.log):
raw = open(args.log, "r", encoding="utf-8").read()
else:
raw = args.log
else:
raw = DEFAULT_LOG
blocks = split_blocks(raw)
events: List[ApiEvent] = [] events: List[ApiEvent] = []
for block in blocks: for block in blocks:
try: try:
event = parse_block(block) event = await parse_block(block)
events.append(event) events.append(event)
except Exception as e: except Exception as e:
print(f"Failed to parse block: {block!r}\nError: {e}") print(f"Failed to parse block: {block}\nError: {e}")
# Output each event as JSON
# Output structured events
print("\nParsed events:\n")
for ev in events: for ev in events:
print(ev.model_dump()) print(ev.model_dump_json(indent=2))
# Pretty table # Pretty table
print("\nTable:\n") table = [[
header = f"{'kind':<6} | {'path':<15} | {'status':<6} | details" ev.kind,
print(header) ev.path,
print('-' * len(header)) ev.status,
for ev in events: getattr(ev, "duration_ms", "-"),
if ev.kind == "ok": getattr(ev, "error_message", "-"),
print(f"{ev.kind:<6} | {ev.path:<15} | {ev.status:<6} | duration {ev.duration_ms}ms") ] for ev in events]
else: headers = ["kind", "path", "status", "duration_ms", "error_message"]
print(f"{ev.kind:<6} | {ev.path:<15} | {ev.status:<6} | error: {ev.error_message}") print("\nParsed events table:\n")
print(tabulate(table, headers=headers, tablefmt="github"))
if __name__ == "__main__": if __name__ == "__main__":
main() asyncio.run(main())
"""