add parser.py

This commit is contained in:
2026-06-04 16:22:17 +00:00
parent 9a8b5ba919
commit 4a990628a0
+102
View File
@@ -0,0 +1,102 @@
import os
from typing import List
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
# Для локальной разработки без OpenAI:
# from langchain_ollama import ChatOllama
from models import ApiEvent, HttpOkEvent, HttpErrorEvent, ApiEventAdapter
# Системный промпт для извлечения структурированных событий
SYSTEM_PROMPT = """Ты — парсер логов HTTP API. Из входного текста извлеки структурированные события.
Правила:
1. Каждая строка лога — отдельное событие
2. Если статус 2xx (200, 201 и т.д.) — создавай HttpOkEvent (kind='ok')
3. Если статус 4xx или 5xx — создавай HttpErrorEvent (kind='error')
4. Всегда заполняй все поля: kind, status, path, duration_ms (для ok) или error_message (для error)
5. Если длительности нет в логе, укажи 0.0
6. Если сообщения об ошибке нет, составь стандартное: "HTTP {status} error"
Входной формат лога может быть разным, но старайся извлечь:
- status: число 200, 404, 500 и т.д.
- path: строка типа /api/users, /products/123
- duration_ms: число
- error_message: текст ошибки (если есть)
"""
class LogEventParser:
def __init__(self, model_name: str = "gpt-3.5-turbo", temperature: float = 0.0, openai_api_key: str = None):
"""Инициализация парсера с LangChain structured output"""
# Настройка OpenAI (можно заменить на Ollama)
kwargs = {
"model": model_name,
"temperature": temperature,
}
if openai_api_key:
kwargs["api_key"] = openai_api_key
elif os.getenv("OPENAI_API_KEY"):
kwargs["api_key"] = os.getenv("OPENAI_API_KEY")
self.llm = ChatOpenAI(**kwargs)
# Привязываем структуру вывода напрямую к LLM (LangChain >=0.2)
self.structured_llm = self.llm.with_structured_output(ApiEvent, method="function_calling")
def parse_single_event(self, log_line: str) -> ApiEvent:
"""Парсит одну строку лога в ApiEvent"""
messages = [
SystemMessage(content=SYSTEM_PROMPT),
HumanMessage(content=f"Извлеки событие из строки лога:\n{log_line}")
]
# LangChain автоматически вернёт нужный объект благодаря discriminated union
event = self.structured_llm.invoke(messages)
return event
def parse_multiple_events(self, log_text: str, separator: str = "\n") -> List[ApiEvent]:
"""Парсит несколько событий из текста (по строкам или разделителю)"""
events = []
# Разбиваем на блоки
blocks = log_text.strip().split(separator)
for block in blocks:
if not block.strip():
continue
try:
event = self.parse_single_event(block)
events.append(event)
except Exception as e:
print(f"⚠️ Ошибка парсинга строки '{block[:50]}...': {e}")
continue
return events
# Альтернативный парсер через PydanticOutputParser (без function calling)
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
class LogEventParserWithParser:
"""Версия с PydanticOutputParser (работает без function calling)"""
def __init__(self, model_name: str = "gpt-3.5-turbo", temperature: float = 0.0):
self.llm = ChatOpenAI(model=model_name, temperature=temperature)
self.parser = PydanticOutputParser(pydantic_object=ApiEventAdapter) # Используем адаптер
self.prompt = PromptTemplate(
template="""Извлеки событие из строки лога.
{format_instructions}
Лог: {log_line}
Верни только JSON с полем kind и соответствующими полями для события.""",
input_variables=["log_line"],
partial_variables={"format_instructions": self.parser.get_format_instructions()}
)
self.chain = self.prompt | self.llm | self.parser
def parse_single_event(self, log_line: str) -> ApiEvent:
result = self.chain.invoke({"log_line": log_line})
return result