Updated main.py with LangChain and removed deepagents

This commit is contained in:
2026-07-02 17:22:18 +00:00
parent ab786ec7a7
commit 1e62e81892
+52 -101
View File
@@ -1,138 +1,89 @@
#!/usr/bin/env python import argparse
"""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
"""
import os import os
import sys
import asyncio
from typing import Annotated, Literal, Union, List from typing import Annotated, Literal, Union, List
from pathlib import Path
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 pydantic import BaseModel, Field
from langchain_core.output_parsers import PydanticOutputParser from langchain_core.output_parsers import PydanticOutputParser
from tabulate import tabulate from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 1. Pydantic models Union with discriminator # 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(..., description="Event kind")
status: Literal[200] = Field(200, description="HTTP status code") status: Literal[200] = Field(..., description="HTTP OK status")
path: str = Field(..., description="Request path") path: str = Field(..., description="Request path")
duration_ms: int = Field(..., description="Response time in milliseconds") duration_ms: int = Field(..., description="Duration in milliseconds")
class HttpErrorEvent(BaseModel): class HttpErrorEvent(BaseModel):
kind: Literal["error"] = Field("error", description="Event kind Error") kind: Literal["error"] = Field(..., description="Event kind")
status: int = Field(..., description="HTTP status code (4xx/5xx)") status: int = Field(..., description="HTTP error status")
path: str = Field(..., description="Request path") path: str = Field(..., description="Request path")
error_message: str = Field(..., description="Error description") error_message: str = Field(..., description="Error message")
ApiEvent = Annotated[Union[HttpOkEvent, HttpErrorEvent], Field(discriminator="kind")] ApiEvent = Annotated[Union[HttpOkEvent, HttpErrorEvent], Field(discriminator="kind")]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 2. LLM and Agent setup # 2. LLM and Parser setup
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
llm = ChatOpenAI( load_dotenv()
model="openai/gpt-oss-20b:free", llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, max_output_tokens=512)
base_url="https://openrouter.ai/api/v1", parser = PydanticOutputParser(pydantic_object=ApiEvent)
api_key=os.getenv("OPENAI_API_KEY"), chain = RunnablePassthrough() | llm | parser
temperature=0.0,
)
backend = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"),
FilesystemBackend(),
])
# 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.",
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 3. Parsing helper # 3. Parsing helper
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
parser = PydanticOutputParser(pydantic_object=ApiEvent) def parse_log_line(line: str) -> ApiEvent:
prompt = f"Parse the following log line into JSON:\n{line}\nThe JSON should match one of the following schemas:\n- ok event: {{\"kind\": \"ok\", status: 200, path: string, duration_ms: int}}\n- error event: {{\"kind\": \"error\", status: int, path: string, error_message: string}}\nReturn only the JSON."
async def parse_block(block: str) -> ApiEvent: result = chain.invoke(prompt)
"""Ask the LLM to parse a single log block into an ApiEvent. return result
The LLM is instructed to output only the JSON that matches the
Pydantic schema. The parser then validates and returns the model.
"""
prompt = (
"Parse the following log entry and return a JSON object that matches "
"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)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 4. Main logic # 4. Main logic
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
DEFAULT_LOG = """""" def main():
DEFAULT_LOG += "GET /api/users 200 123ms\n" parser_cli = argparse.ArgumentParser(description="Parse log events into typed objects.")
DEFAULT_LOG += "POST /api/login 404 Not Found\n" parser_cli.add_argument("--log", type=str, help="Path to log file or raw log string.")
DEFAULT_LOG += "GET /api/data 500 Internal Server Error\n" args = parser_cli.parse_args()
DEFAULT_LOG += "PUT /api/update 200 98ms\n"
DEFAULT_LOG += "DELETE /api/remove 403 Forbidden\n"
async def main(): if args.log:
raw_log = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_LOG if os.path.exists(args.log):
# Split into blocks simple newline split but ignore empty lines with open(args.log, "r", encoding="utf-8") as f:
blocks = [b for b in raw_log.strip().split("\n") if b] raw = f.read()
else:
raw = args.log
else:
raw = """GET /api/users 200 123ms
POST /api/users 404 Not Found
GET /api/orders 500 Internal Server Error
"""
lines = [l.strip() for l in raw.splitlines() if l.strip()]
events: List[ApiEvent] = [] events: List[ApiEvent] = []
for block in blocks: for line in lines:
try: try:
event = await parse_block(block) event = parse_log_line(line)
events.append(event) events.append(event)
except Exception as e: except Exception as e:
print(f"Failed to parse block: {block}\nError: {e}") print(f"Failed to parse line: {line}\nError: {e}")
# Output each event as JSON
print("\nParsed Events:")
for ev in events: for ev in events:
print(ev.model_dump_json(indent=2)) print(ev.model_dump())
# Pretty table
table = [[ print("\nTable:\")
ev.kind, header = ["kind", "path", "status"]
ev.path, print("{:<6} {:<20} {:<6}".format(*header))
ev.status, for ev in events:
getattr(ev, "duration_ms", "-"), kind = ev.kind
getattr(ev, "error_message", "-"), path = getattr(ev, "path", "")
] for ev in events] status = getattr(ev, "status", "")
headers = ["kind", "path", "status", "duration_ms", "error_message"] print("{:<6} {:<20} {:<6}".format(kind, path, status))
print("\nParsed events table:\n")
print(tabulate(table, headers=headers, tablefmt="github"))
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) main()