Создание испанского налогового поиска на основе GPT: революция в доступе к налоговой информации

Прошли те времена, когда нужно было ориентироваться в сложном лабиринте налоговой информации на государственных веб-сайтах. В марте 2023 года Грег Брокман (соучредитель и президент OpenAI) представил пример использования GPT-4 для уплаты налогов, и я решил создать специальную версию для Renta — годовой декларации по подоходному налогу для резидентов Испании. Имя? РентаГПТ 😅

RentaGPT — это простое решение для упрощения налогового поиска Renta для граждан Испании, основанное на информации на веб-сайте AEAT, сочетающее в себе мощь технологии OpenAI GPT-3 с веб-скрапингом и передовыми лингвистическими инструментами, такими как Chroma и LangChain. ».

В этом сообщении в блоге я расскажу вам о создании этого приложения и о том, как этот инструмент может революционизировать способ доступа пользователей к (налоговой) информации.

Шаг 1. Сбор данных с веб-сайта AEAT.

Первым шагом в создании приложения для налогового поиска был сбор соответствующей информации с веб-сайта AEAT (Agencia Estatal de Administración Tributaria). Используя методы парсинга, я систематически извлекал данные из определенного раздела о Ренте 2022.

def crawl_aeat() -> List[str]:
    starting_url = "https://sede.agenciatributaria.gob.es/Sede/Ayuda/22Manual/100.html"
    url_prefix = "https://sede.agenciatributaria.gob.es/Sede/ayuda/manuales-videos-folletos/manuales-practicos/irpf-2022"
    links = get_all_links(starting_url, url_prefix)
    leafs_strings = delete_substrings([link.replace(".html", "") for link in links])
    no_duplicated_links = [link + ".html" for link in leafs_strings]
    links_to_skip = (
        "presentacion.html",
        "normativa.html",
    )
    final_list = list(
      filter(
        lambda x: not x.endswith(links_to_skip), 
        no_duplicated_links
      )
    )
    return final_list

Результатом этого процесса стал всеобъемлющий набор данных, содержащий всю необходимую налоговую информацию, такую ​​как законы, налоговые ставки, сроки, формы и часто задаваемые вопросы.

def load_links(links: List[str]) -> List[Document]:
    """Get documents from web pages."""
    pickle_docs_file = f"{cfg.data_directory}/documents.pkl"

    if os.path.exists(pickle_docs_file):
        print("Pickle version of docs found. Loading it")
        with open(pickle_docs_file, "rb") as fp:
            documents = pickle.load(fp)
    else:
        print("No pickle version of docs found. Downloading links")
        loader = UnstructuredURLLoader(urls=links)
        downloaded_docs = loader.load()
        print("Downloaded docs", len(downloaded_docs))
        documents = [fix_document(doc) for doc in downloaded_docs]
        with open(pickle_docs_file, "wb") as fp:
            pickle.dump(documents, fp)
    return documents

Шаг 2: Создание базы данных векторного поиска встраивания с помощью Chroma

Чтобы обеспечить эффективный поиск в приложении, я использовал Chroma, мощный движок базы данных на основе DuckDB, который создает базы данных векторного поиска встраивания.

Преобразовывая извлеченную налоговую информацию в многомерное векторное пространство, Chroma позволяет пользователям быстро и эффективно выполнять поиск в наборе данных.

Такой подход не только гарантирует быстрое получение релевантных результатов, но и помогает уточнить поисковые запросы, чтобы предоставить пользователям самую точную и актуальную налоговую информацию.

def create_embeddings(documents: List[Document]) -> List[Document]:
    split_documents = []

    embeddings_file = f"{cfg.chroma.persist_directory}/chroma-embeddings.parquet"
    embeddings = OpenAIEmbeddings(
        openai_api_key=cfg.providers.openai.api_key,
        model="text-embedding-ada-002",
    )

    if not os.path.exists(embeddings_file):
        print("No embeddings file found. Calculating embeddings.")
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=50,
            length_function=len,
        )
        split_documents = text_splitter.split_documents(documents)
        client = Chroma.from_documents(
            documents=split_documents,
            embedding=embeddings,
            collection_name=cfg.chroma.collection_name,
            persist_directory=cfg.chroma.persist_directory,
        )
        client.persist()
    return split_documents

Шаг 3: Склеивание шагов LLM с помощью LangChain

Как только набор данных был преобразован в базу данных векторного поиска встраивания, мне понадобился способ подключить его к OpenAI GPT-3 API, механизму обработки естественного языка.

LangChain послужил идеальным инструментом для этой цели, поскольку он специализируется на связывании шагов языковой модели и оптимизации потока информации между различными компонентами.

Интегрировав LangChain, я обеспечил бесперебойную связь между GPT-3 и поисковой базой данных Chroma, что позволило пользователям взаимодействовать с приложением, используя запросы на естественном языке.

def get_filter_documents_chain(api_key: str) -> LLMChain:
    llm = OpenAI(
        openai_api_key=api_key,
        streaming=False,
        verbose=True,
        temperature=0.0,
        max_tokens=1000,
    )
    prompt = PromptTemplate(
        template=FILTER_DOCUMENTS_PROMPT_TEMPLATE,
        input_variables=["question", "documents"]
    )
    return LLMChain(llm=llm, prompt=prompt)

Шаг 4. Внедрение понимания естественного языка (NLU)

Это одна из «волшебных» частей, в которой приложение использует мощные возможности понимания и генерации естественного языка OpenAI GPT.

Он состоит из нескольких шагов, необходимых для обработки и анализа налоговых запросов. Каждый шаг служит определенной цели, и все они работают вместе, чтобы обеспечить точные и надежные результаты.

  • Во-первых, вопрос пользователя преобразуется во встраивание.
  • Во-вторых, база данных Chroma запрашивается с использованием этих вложений, возвращая до пяти релевантных результатов. Затем эти результаты объединяются в один текст.
  • В-третьих, GPT используется для возврата файла JSON с наиболее релевантными документами, относящимися к данному вопросу.
  • В-четвертых, предыдущий список результатов добавляется к исходному вопросу и используется, чтобы попросить GPT сгенерировать хороший ответ пользователю, который ссылается на документы и всесторонне и точно охватывает заданный вопрос. (Здесь необходимы некоторые преобразования, чтобы убедиться, что возвращенный JSON полезен).
query_results = docsearch.max_marginal_relevance_search(
    query=question,
    k=4,
)

filter_documents_chain = get_filter_documents_chain(api_key)
documents = build_yaml_documents(query_results)
result = await filter_documents_chain.acall({
    "question": question,
    "documents": documents,
})

try:
    result_text = result['text'].replace("```json", "").replace("```", "")
    result_results = json.loads(result_text)["results"]
except Exception as e:
    logging.error(e)
    result_results = []

sources = [source['source'] for source in result_results]
filtered_query_results = list(filter(lambda x: x.metadata['source'] in sources, query_results))
filtered_documents = build_yaml_documents(filtered_query_results)
streaming_chain = get_streaming_chain(stream_handler, api_key)
result = await streaming_chain.acall({
    "question": question,
    "documents": filtered_documents,
})

Шаг 5. Создание API и внешнего интерфейса с помощью FastAPI и Vite

Чтобы создать удобный интерфейс для приложения, я выбрал FastAPI и Vite в качестве основных технологий для создания API и внешнего интерфейса соответственно.

FastAPI — это современная и высокопроизводительная среда Python для создания API, которая позволила мне разработать надежное и надежное соединение между серверной системой и пользовательским интерфейсом. На фронтенд меня вдохновил проект Clarity-AI (🎉 респект Маккею! 🎉)

Учитывая, что целью была простота, я решил настроить Vite с некоторыми плагинами, чтобы создать один файл index.html, содержащий весь контент javascript и CSS, который обслуживается FastAPI. Процесс сборки выполняется во время действий GitHub, и только один HTML-файл развертывается на fly.io.

Шаг 6. Развертывание приложения с помощью GitHub Actions и Fly.io

Чтобы упростить процесс развертывания и сделать приложение доступным для пользователей по всему миру, я использовал GitHub Actions и fly.io в качестве облачного провайдера. GitHub Actions предоставил автоматизированный рабочий процесс для непрерывной интеграции и развертывания, гарантируя, что приложение всегда будет в курсе последних изменений и улучшений.

Fly.io, глобальный облачный провайдер, позволяет мне размещать приложение с минимальной задержкой, предлагая пользователям быстрый и надежный опыт. Используя GitHub Actions и fly.io, я упростил процесс развертывания и обеспечил бесперебойную работу для пользователей, независимо от их местоположения.

Заключение

Это испанское приложение для налогового поиска на базе GPT является ярким примером того, как технологии LLM революционизируют способы доступа пользователей к информации. Упростив процесс поиска и включив запросы на естественном языке, я сделал налоговую информацию более доступной и удобной для пользователя, чем когда-либо прежде.

Открытый исходный код и доступность исходного кода

Я твердо верю в силу разработки и сотрудничества с открытым исходным кодом. Свидетельством этого является то, что исходный код RentaGPT находится в открытом доступе на GitHub.

Я приглашаю разработчиков и пользователей исследовать, вносить свой вклад и улучшать мою работу. Делясь кодом и идеями, я надеюсь вдохновить на дальнейшие инновации и разработки в области обработки естественного языка (и в области налоговой информации 😎).

Репозиторий GitHub: https://github.com/mpuig/rentagpt