3.2 🔗 Chains - собери свою цепь

🤔 Кажется, это что-то на LLM-ском? 🧐

В этой задаче вы получаете датасет с отрывками художественных текстов на разных языках. Но тексты вам пришли после сканирования книг, с ошибками в распознавании, так как язык всё время менялся, а настройки распознавания нет.

Из-за этого в текстах иногда могут встречаться странные символы: ¿, ¡, £.

Например, в немецком слове St£raß¿en этих символов сразу два.

Задача:

  • Собрать цепочку, которая будет сначала очищать текст от странных символов.
  • Потом определять язык текста и имя главного героя произведения.

Формат сдачи:

csv- файл со столбцами

  1. text - очищенный текст.
  2. language - язык, на котором написан текст. Указать на английском языке (например, не Русский, а Russian. Не Deutsch, а German)
  3. main_character - имя главного героя произведения. На том же языке, как оно указано в тексте. (Hans, Pierrot, Анна)

 Пример:

Colab ноутбук, в котором всё готово для удобного начала решения задачи!


Добрый вечер! У меня чет совсем туго. 🙏 Не получается решить: Создадим цепочку с помощью LCEL https://colab.research.google.com/drive/1i5v-lWsmLr47w80WudY1sD_DY4oH2rtr?usp=sharing 

@Евгения_Александровна_Мосеева, а какой текст ошибки? Сейчас проверил - отрабатывает.

@Иван_Александров,  На шаге: Создадим цепочку с помощью LCEL: 1) 

# Создание цепочки с использованием llm, prompt_template и output_parser

chain = LLMChain(llm=llm, prompt=prompt_template, output_parser=output_parser)  2) 

from tqdm import tqdm

import pandas as pd

 

# Подготовка DataFrame для результатов

df['language'] = ""

df['main_character'] = ""

 

# Проходим по всем текстам в DataFrame

for index, row in tqdm(df.iterrows(), total=df.shape[0]):

    text = row['cleaned_text']

    try:

        # Используем метод invoke для обработки текста и получения результатов

        result = chain.invoke(input={"text": text})

       

        # Обновляем DataFrame с полученными данными

        df.at[index, 'language'] = result.get('language', 'unknown')

        df.at[index, 'main_character'] = result.get('main_character', 'unknown')

    except Exception as e:

        print(f"Ошибка при обработке текста: {e}")

        # В случае возникновения ошибки присваиваем значения по умолчанию

        df.at[index, 'language'] = 'unknown'

        df.at[index, 'main_character'] = 'unknown'

 

# Просмотр обновлённых данных

print(df[['raw_text', 'language', 'main_character']])  Выдало ответ:  

8%|▊         | 1/13 [00:01<00:13,  1.11s/it]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 15%|█▌        | 2/13 [00:01<00:10,  1.04it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 23%|██▎       | 3/13 [00:02<00:09,  1.11it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 31%|███       | 4/13 [00:03<00:08,  1.05it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 38%|███▊      | 5/13 [00:04<00:07,  1.06it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 46%|████▌     | 6/13 [00:05<00:06,  1.07it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 54%|█████▍    | 7/13 [00:06<00:05,  1.07it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 62%|██████▏   | 8/13 [00:07<00:04,  1.06it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 69%|██████▉   | 9/13 [00:08<00:03,  1.06it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 77%|███████▋  | 10/13 [00:09<00:02,  1.05it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 85%|████████▍ | 11/13 [00:10<00:01,  1.05it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
 92%|█████████▏| 12/13 [00:11<00:00,  1.07it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
100%|██████████| 13/13 [00:12<00:00,  1.07it/s]
Ошибка при обработке текста: Got invalid JSON object. Error: Expecting value: line 1 column 1 (char 0)
                                             raw_text language main_character
0   The sun was setting, casting long shadows over...  unknown        unknown
1   Le soleil se couchait, jetant de longues ombre...  unknown        unknown
2   El sol se estaba poniendo, proyectando largas ...  unknown        unknown
3   La ciudad estaba llena de vida, sus calles lle...  unknown        unknown
4   La ville était pleine de vie, ses rues remplie...  unknown        unknown
5   Die Stadt¿ war voller Leben, ihre St£raß¿en ge...  unknown        unknown
6   Die Sonne g¿ing unter und warf lange Schat£ten...  unknown        unknown
7   В тихом уголке старого города, где узкие уло¿ч...  unknown        unknown
8   In a small town nestled between the mountains ...  unknown        unknown
9   En un pequeño pueblo situado entre la£s m¿onta...  unknown        unknown
10  Dans un petit village niché entre les montagne...  unknown        unknown
11  В городе было множество лю£дей, каждый из ко¿т...  unknown        unknown
12  £La città era piena di vita, le strade piene d...  unknown        unknown

@Евгения_Александровна_Мосеева, Удалось ли побороть проблему? Можете сохранить колаб версию ноутбука, чтобы воспроизводилась ошибка и прислать ссылку, а то так трудно понять в чём проблема.

@Евгения_Александровна_Мосеева, у меня была такая же ошибка. Я добавила такую строку в промпт "Extract the following information from the given text and return a JSON string with keys 'cleaned_text', 'language' and 'main_character'." и у меня заработало 

Добрый вечер. При попытке загрузить решение получаю ошибку: Ошибка в столбце text ! При этом визуально в выходном файле структура соблюдена, в файл запись без индекса

Неверное решение #1179925285

Ошибка в столбце text !

@Ivan_I, А вы очищенный текст туда сохраняете?

@Ivan_I, Вы слишком очистили текст - все знаки препинания пропали. На вашем скриншоте это видно, сравните с отрывком в примере

@Иван_Александров, Спасибо!

Добрый день!
Подскажите пожалуйста, как можно достать очищенный текст, если создать такую цепочку? Сюда я передаю неочищенный текст, возвращаемый res имеет 2 поля: language и main_character

self.llm_chain = self.clean_text | prompt | self.llm | output_parserres = self.llm_chain.invoke({'text': text, 'format_instructions': self.format_instructions})

@Andrey_Galitsin, можно использовать Passthrough как в ноутбуке урока.  Ещё можно в чат-боте в доке лэнгчейна спросить.

Помогите пожалуйста понять, что я не так делаю. Уже несколько вечеров сижу дебажу:

from langchain_community.chat_models import GigaChat
import re
import pandas as pd
from tqdm import tqdm
from getpass import getpass
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, TransformChain
from langchain.output_parsers import ResponseSchema, StructuredOutputParser

giga_token = ***

# Инициализируем языковую модель
giga = GigaChat(credentials=giga_token, verify_ssl_certs=False)
giga.temperature = 0.1

path = 'https://stepik.org/media/attachments/lesson/1110883/raw_texts.csv'
df = pd.read_csv('raw_texts.csv')
df.head()


# Напишем функцию, которая очистит текст от ненужных символов: ¿, ¡, £
def clean_text(inputs: dict) -> dict:
dirty_text = inputs["text"]
cleaned_text = re.sub(r'[¿¡£]', '', dirty_text)
return {"text": cleaned_text}


# Будем просить у модели определять язык и имя главного персонажа и выдавать ответ в виде словаря
# Определим схемы ответа
language_schema = ResponseSchema(
name="language",
description="Язык, на котором написан текст. Указать на английском языке (например, не Русский, а Russian. Не Deutsch, а German)",
type="string"
)

person_schema = ResponseSchema(
name="main_character",
description="Имя главного героя произведения. На том же языке, как оно указано в тексте. (Hans, Pierrot, Анна)",
type="string"
)

response_schemas = [language_schema, person_schema]
output_parser = StructuredOutputParser.from_response_schemas(
response_schemas) # Создаём парсер и подаём в него список со схемами
format_instructions = output_parser.get_format_instructions() # Получаем инструкции по форматированию ответа

template = '''Из следующего текста извлеки информацию:
1. Язык на котором написан текст. Указать на английском языке (например, не Русский, а Russian. Не Deutsch, а German)
2. Главный герой рассказа. Имя главного героя произведения. На том же языке, как оно указано в тексте. (Hans, Pierrot, Анна)
text: {text}

{format_instructions}
Результат:'''

# Напишем шаблон промпта со своим вопросом и инструкциями по форматированию ответа. Будем передавать в этот промпт сырой текст
prompt = PromptTemplate(input_variables=['text', 'format_instructions'], template=template)
# prompt = PromptTemplate(input_variables=['text'], template=template)

# Создадим цепочку с помощью LCEL
chain = clean_text | prompt | giga

# Сохраним всё в итоговый файл. Убедитесь, что на этом этапе у вас в столбцах
# text - очищенный текст (без символов ¿, ¡, £)
# language - язык, на котором написан текст (название языка указать на английском языке)
# main_character - имя главного персонажа в тексте (указать на том языке, на котором и написан сам текст)

# language = df['language'] = ''
# main_character = df['main_character'] = ''

for text in tqdm(df['raw_text']):
# cleaned_text = clean_text({"text": text})
# print(f'cleaned_text: {cleaned_text}')

# message = prompt.format(text=text, format_instructions=format_instructions)
# print(f'message - {message}')

result = chain.invoke({'format_instructions': format_instructions, 'text': text})
print(f'result:{result}')
#
# parsed_result = output_parser.parse(result)
# print(parsed_result)
# df.loc[len(df)] = [result[0]['text'], result[0]['language'], result[0]['main_character']]
#
# df[['text', 'language', 'main_character']].to_csv('3_2.2.9_solution.csv', index=False)

 

В данной конфигурации выдаёт:

C:\Users\K\PycharmProjects\LLM_Course\venv\Scripts\python.exe C:\Users\K\PycharmProjects\LLM_Course\3_2\3.2.py 
  0%|          | 0/1 [00:00<?, ?it/s]
Traceback (most recent call last):
  File "C:\Users\K\PycharmProjects\LLM_Course\3_2\3.2.py", line 77, in <module>
    result = chain.invoke({'format_instructions': format_instructions, 'text': text})
  File "C:\Users\K\PycharmProjects\LLM_Course\venv\lib\site-packages\langchain_core\runnables\base.py", line 2075, in invoke
    input = step.invoke(
  File "C:\Users\K\PycharmProjects\LLM_Course\venv\lib\site-packages\langchain_core\prompts\base.py", line 113, in invoke
    return self._call_with_config(
  File "C:\Users\K\PycharmProjects\LLM_Course\venv\lib\site-packages\langchain_core\runnables\base.py", line 1262, in _call_with_config
    context.run(
  File "C:\Users\K\PycharmProjects\LLM_Course\venv\lib\site-packages\langchain_core\runnables\config.py", line 326, in call_func_with_variable_args
    return func(input, **kwargs)  # type: ignore[call-arg]
  File "C:\Users\K\PycharmProjects\LLM_Course\venv\lib\site-packages\langchain_core\prompts\base.py", line 98, in _format_prompt_with_error_handling
    raise KeyError(
KeyError: "Input to PromptTemplate is missing variables {'format_instructions'}.  Expected: ['format_instructions', 'text'] Received: ['text']"

Process finished with exit code 1
 

Хотя format_instructions и text я передаю

У вас из функции cleaned_text выходит только 'text'. И дальше передается в промпт. Надо формат инструкшнс тоже прокидывать в промпт

Здравствуйте! Закончились учебные токены. В телеграм-чате не отвечают.

я перепутал символы ¡ и i -- потому вычищал i и 2 раза решение не туда( задание классное!

Символы заменять на "" не на ' ' как в колабе.  

lang_schema = ResponseSchema(name='lang',  description='What language is the text written in?\nWrite the answer in one word.')

hero_schema = ResponseSchema(name='hero',  description='Напиши имя главного героя текста?')

response_schema = [lang_schema, hero_schema]

output_parser = StructuredOutputParser.from_response_schemas(response_schema)

template = PromptTemplate(template = 'Ответь на вопросы после текста. \n\ntext: {input_text}\n\n{format_instructions}',

                           input_variables=['input_text'],

                           partial_variables='format_instructions':output_parser.get_format_instructions()})

​​​​​​​def del_chars(inputs: dict) -> dict:

  text = inputs['input_text']

  text = re.sub(r'[¿¡£]', '', text)

  result = inputs | {'input_text': text}

  return result

seq = del_chars | \

           RunnableParallel(

                  clear_text=RunnablePassthrough(itemgetter('input_text')),

                  answer = template | llm | output_parser)

res = []

for txt in df['raw_text']:

  a = seq.invoke({'input_text': txt})

  res.append({'text': a['clear_text']['input_text'],

              'language': a['answer']['lang'],

              'main_character': a['answer']['hero']})

result =  pd.DataFrame.from_dict(res)

result.to_csv('3.2.9.csv', index=False)

Вcё верно

def clean_text(inputs: dict) -> dict:

    text = inputs["text"]

    # Удаляем ненужные символы

    text = text.replace("¿", "").replace("¡", "").replace("£", "")

return {"text": text}

 

# Определим схемы ответа

language_schema = ResponseSchema(name='language', description='The language of main character')

person_schema = ResponseSchema(name='name', description='The name of main character')

response_schemas = [language_schema, person_schema]

output_parser = StructuredOutputParser.from_response_schemas(response_schemas) # Создаём парсер и подаём в него список со схемами

format_instructions = output_parser.get_format_instructions() # Получаем инструкции по форматированию ответа

 

template="""

Ты как эксперт в области анализа данных извлеки мне имя главного персонажа и определи язык.

###

Context:

{text}

###

{format_instructions}

"""

 

prompt = PromptTemplate(

template=template,

input_variables=['text'],

partial_variables={"format_instructions": format_instructions}

)

 

chain = clean_text | prompt | llm

 

from langchain.chains import TransformChain

df['text'] = ''

df['language'] = ''

df['main_character'] = ''

 

for i, text in enumerate(tqdm(df['raw_text'])):

    # YOUR CODE HERE

    text_clean_chain = TransformChain(

    input_variables=["text"],

    output_variables=["text"],

    transform=clean_text

    )

    text = text_clean_chain.invoke(text)['text']

    result = chain.invoke({'text': text})

    parsed_result = output_parser.parse(result.content)

 

    df.at[i, 'text'] = text

    df.at[i, 'language'] = parsed_result.get('language', '')

    df.at[i, 'main_character'] = parsed_result.get('name', '')

Вcё верно

answers = []

for i,row in df.iterrows():

chat_history = ChatHistory()

 

prompt = f"""

{row['raw_text']}

text above is text on some real language but some signs could be replaced with another.

Из-за этого в текстах иногда могут встречаться странные символы: ¿, ¡, £.

Например, в немецком слове St£raß¿en этих символов сразу два.

your goal is to produce cleaned text, write what language it is written ( name language using English language), write main chararcter name

 

your goal is to return ONLY json with keys: text - очищенный текст.

language - язык, на котором написан текст. Указать на английском языке (например, не Русский, а Russian. Не Deutsch, а German)

main_character - имя главного героя произведения. На том же языке, как оно указано в тексте. (Hans, Pierrot, Анна)

"""

chat_history.add_message("user", prompt)

response = llm(chat_history,MODEL_TYPE_ ='chatgpt-4o-latest', SYSTEM_PROMPT_='',MAX_TOKENS_=2000, TEMPERATURE_= 0)

# print(response)

answers += [response]

Вcё верно

import re
import pandas as pd
from tqdm import tqdm
from langchain_community.llms import Ollama
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import ResponseSchema, StructuredOutputParser
model = Ollama(model="llama3:8b-instruct-q8_0", temperature=0.0)
df = pd.read_csv("https://stepik.org/media/attachments/lesson/1110883/raw_texts.csv")
def clean_text(text: str) -> str:
    return re.sub(r"[¿¡£]", '', text)
response_schemas = [
    ResponseSchema(name="language",
                   description="Language of the text. Indicate in English (e.g., not Русский, but Russian. Not Deutsch, but German)."),
    ResponseSchema(name="main_character",
                   description="Name of the main character in the text, as it appears, on the same language (e.g., Hans, Pierrot, Анна).")
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
format_instructions = output_parser.get_format_instructions()
template = '''Из следующего текста извлеки информацию. Верни только JSON string with keys 'language' and 'main_character'.
text: {text}
{format_instructions}
Результат:'''
prompt = ChatPromptTemplate.from_template(template)
result_data = []
chain_with_parser = prompt | model | output_parser
for text in tqdm(df['raw_text']):
    cleaned_text = clean_text(text)
    response = chain_with_parser.invoke({'text': cleaned_text, 'format_instructions': format_instructions})
    print("Raw result content:", response)
    result_data.append({
        "text": cleaned_text,
        "language": response["language"],
        "main_character": response["main_character"]
    })
pd.DataFrame(result_data).to_csv('3.2.9_solution.csv', index=False)

Вcё верно

Такое решение получилось. 
Обратная связь приветствуется - минусы, плюсы, рекомендации?
По правде говоря, использовал одну ручную правку - в одном в столбце 'language' для одного текста было 'ru' вместо 'Russian', хотя русских текстов было два.
import re
import pandas as pd
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.output_parsers import ResponseSchema, StructuredOutputParser
from utils import ChatOpenAI
from getpass import getpass
course_api_key= "course_api_key"
llm = ChatOpenAI(temperature=0.0, course_api_key=course_api_key)

 

df = pd.read_csv("raw_texts.csv")
def clean_text(text) -> str:
    return re.sub("[¿¡£]", "", text)
df['text'] = df['raw_text'].apply(clean_text)

 

language_schema = ResponseSchema(
    name="language",
    description="Язык, на котором написан текст. Указать на английском языке. Например: не Русский, а Russian, не Deutsch, а German",
    type="string"
)
person_schema = ResponseSchema(
    name="main_character",
    description="Имя главного героя произведения. На том же языке, как оно указано в тексте. Например: Hans, Pierrot, Анна",
    type="string"
)
response_schemas = [language_schema, person_schema]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
prompt = PromptTemplate(
    input_variables=["text"],
    template="Проанализируйте следующий текст: {text}. Верни JSON string with keys 'language' and 'main_character'."
)
chain = LLMChain(llm=llm, prompt=prompt, output_parser=output_parser)
texts = df['text'].tolist()
inputs = [{"text": t} for t in texts]
results = chain.batch(inputs)
df['language'] = [res['text']['language'] for res in results]
df['main_character'] = [res['text']['main_character'] for res in results]
df[['text', 'language', 'main_character']].to_csv('3.2.9_solution.csv', index=False)

Вcё верно

itemgetter чтобы получить отдельную колонку "text" не беря её из выхода llm

chain_lang_mnchar = (prompt
                    | llm
                    | output_parser)

chain_combined = (
    RunnableLambda(clean_text)
    | {"text": itemgetter("text"),
       'lang_mnchar': chain_lang_mnchar}
    # here can be corrector if chain_lang_author is bad AI message
    | RunnableLambda(lambda input: {'text': input['text'],
                                   'language': input['lang_mnchar']['language'],
                                   'main_character':input['lang_mnchar']['main_character']
                                   })
)

Вcё верно