add parser.py
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user