Экзамен: Структурированный вывод (Pydantic): solution.py
This commit is contained in:
+149
@@ -0,0 +1,149 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from langchain_core.output_parsers import PydanticOutputParser
|
||||||
|
from langchain_core.prompts import PromptTemplate
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 1. Pydantic модели
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class PersonInfo(BaseModel):
|
||||||
|
name: str = Field(..., description="Имя человека")
|
||||||
|
age: Optional[int] = Field(None, description="Возраст (если известен)")
|
||||||
|
profession: str = Field(..., description="Профессия или должность")
|
||||||
|
skills: List[str] = Field(..., description="Список навыков")
|
||||||
|
|
||||||
|
|
||||||
|
class MeetingNotes(BaseModel):
|
||||||
|
date: str = Field(..., description="Дата встречи в формате YYYY-MM-DD")
|
||||||
|
participants: List[str] = Field(
|
||||||
|
..., description="Имена участников встречи"
|
||||||
|
)
|
||||||
|
topics: List[str] = Field(..., description="Темы обсуждения")
|
||||||
|
decisions: List[str] = Field(
|
||||||
|
..., description="Принятые решения и выводы"
|
||||||
|
)
|
||||||
|
next_steps: List[str] = Field(
|
||||||
|
...,
|
||||||
|
description="Следующие шаги после встречи",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 2. Создание парсера
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def get_parser(model_cls):
|
||||||
|
return PydanticOutputParser(pydantic_object=model_cls)
|
||||||
|
|
||||||
|
|
||||||
|
person_parser = get_parser(PersonInfo)
|
||||||
|
meeting_parser = get_parser(MeetingNotes)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 3. Prompt шаблоны (один общий, но с разными инструкциями)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
prompt_template = """
|
||||||
|
{format_instructions}
|
||||||
|
|
||||||
|
Входной текст:
|
||||||
|
"{text}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
prompt = PromptTemplate(
|
||||||
|
input_variables=["text", "format_instructions"],
|
||||||
|
template=prompt_template,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 4. LLM
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Для работы нужен API‑ключ OpenAI в переменной окружения OPENAI_API_KEY
|
||||||
|
llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 5. Функция выбора схемы и выполнения цепочки
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def extract_structured(text: str):
|
||||||
|
"""
|
||||||
|
Определяем тип текста (person vs meeting) по эвристике
|
||||||
|
и возвращаем валидированный объект Pydantic.
|
||||||
|
"""
|
||||||
|
# простая эвристика: наличие слова "встреча" или "meeting"
|
||||||
|
if any(word in text.lower() for word in ["встреча", "meeting"]):
|
||||||
|
parser = meeting_parser
|
||||||
|
else:
|
||||||
|
parser = person_parser
|
||||||
|
|
||||||
|
chain = prompt | llm | parser
|
||||||
|
result = chain.invoke(
|
||||||
|
{
|
||||||
|
"text": text,
|
||||||
|
"format_instructions": parser.get_format_instructions(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 6. CLI
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
examples = [
|
||||||
|
(
|
||||||
|
"Анна, 28 лет, Python-разработчик. Навыки: FastAPI, Docker.",
|
||||||
|
PersonInfo,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"""Встреча 2023-08-15
|
||||||
|
|
||||||
|
Участники:
|
||||||
|
- Иванов И.И.
|
||||||
|
- Петров П.П.
|
||||||
|
|
||||||
|
Темы:
|
||||||
|
1. Обновление проекта
|
||||||
|
2. Распределение задач
|
||||||
|
|
||||||
|
Решения:
|
||||||
|
- Завершить модуль X к 01/09
|
||||||
|
- Назначить ответственных за Y
|
||||||
|
|
||||||
|
Следующие шаги:
|
||||||
|
- Подготовить презентацию
|
||||||
|
- Отправить отчёт команде""",
|
||||||
|
MeetingNotes,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == "--example":
|
||||||
|
for idx, (txt, model_cls) in enumerate(examples, start=1):
|
||||||
|
print(f"\n=== Пример {idx} ===")
|
||||||
|
obj = extract_structured(txt)
|
||||||
|
print(obj.model_dump(indent=2))
|
||||||
|
else:
|
||||||
|
# пользовательский ввод
|
||||||
|
print("Введите текст (Ctrl-D/Enter для завершения):")
|
||||||
|
user_text = sys.stdin.read().strip()
|
||||||
|
if not user_text:
|
||||||
|
print("Пустой ввод.")
|
||||||
|
return
|
||||||
|
obj = extract_structured(user_text)
|
||||||
|
print("\nРезультат:")
|
||||||
|
print(obj.model_dump(indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user