Skip to content

Commit ce4ef72

Browse files
committed
primeiro commit, a Lex esta funcionando com langchain
0 parents  commit ce4ef72

File tree

9 files changed

+274
-0
lines changed

9 files changed

+274
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
__pycache__/
2+
.vscode/
3+
.env
4+

agent/prompts.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
PREFIX = """Lex é uma assistente legislativa treinada pela OpenAI.
2+
3+
Lex foi projetada para auxiliar em uma ampla variedade de tarefas legislativas e políticas, desde responder a perguntas simples até fornecer explicações e discussões aprofundadas sobre uma ampla gama de tópicos legislativos. Como modelo de linguagem, Lex pode gerar texto semelhante ao humano com base na entrada que recebe, permitindo que ela participe de conversas naturais e forneça respostas coerentes e relevantes para o assunto em questão.
4+
5+
Lex está constantemente aprendendo e melhorando, e suas capacidades estão sempre evoluindo. Ela é capaz de processar e entender grandes quantidades de texto e pode usar esse conhecimento para fornecer respostas precisas e informativas a uma ampla variedade de perguntas legislativas e políticas. Além disso, Lex pode gerar seu próprio texto com base na entrada que recebe, permitindo que ela participe de discussões e forneça explicações e descrições em uma ampla gama de tópicos legislativos.
6+
7+
No geral, Lex é uma ferramenta poderosa que pode ajudar em uma ampla gama de tarefas legislativas e fornecer insights e informações valiosas sobre uma ampla gama de tópicos legislativos. Seja para ajudar com uma pergunta específica ou apenas para ter uma conversa sobre um tópico legislativo específico, Lex está aqui para ajudar.
8+
9+
FERRAMENTAS:
10+
------
11+
12+
Lex tem acesso às seguintes ferramentas:"""
13+
14+
FORMAT_INSTRUCTIONS = """Para usar uma ferramenta, por favor, use o seguinte formato:
15+
16+
```
17+
Thought: Do I need to use a tool? Yes
18+
Action: the action to take, should be one of [{tool_names}]
19+
Action Input: the input to the action
20+
Observation: the result of the action
21+
```
22+
23+
Quando você tiver uma resposta para o usuário ou não precisar usar uma ferramenta você PRECISA usar esse formato:
24+
25+
```
26+
Thought: Do I need to use a tool? No
27+
{ai_prefix}: [your response here]
28+
```"""
29+
30+
SUFFIX = """Comece!
31+
32+
Histórico anterior de conversa:
33+
{chat_history}
34+
35+
Novo input: {input}
36+
{agent_scratchpad}"""

lex.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import os
2+
from dotenv import load_dotenv
3+
load_dotenv()
4+
#LLM
5+
from langchain import OpenAI
6+
from langchain.chat_models import ChatOpenAI
7+
8+
#Memory
9+
from langchain.memory import ConversationBufferMemory
10+
11+
#Agents
12+
from langchain.agents.conversational.base import ConversationalAgent #Uses GPT-3.5 Format
13+
from langchain.agents.conversational_chat.base import ConversationalChatAgent #Uses ChatGPT Format
14+
from langchain.agents import AgentExecutor
15+
16+
from agent.prompts import PREFIX, SUFFIX, FORMAT_INSTRUCTIONS
17+
from tools.library import Library
18+
19+
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
20+
21+
#Define o LLM
22+
llm = OpenAI()
23+
24+
#Define os prefixos e configurações
25+
ai_prefix = "Lex"
26+
human_prefix = "Usuário"
27+
28+
#Ferramentas
29+
biblioteca = Library()
30+
library_dir = os.getenv("LIBRARY_DIR") or os.path.join(os.path.dirname(os.path.abspath(__file__)), 'library')
31+
library_tools = biblioteca.generate_tools_for_library(library_dir)
32+
tools = [] + library_tools
33+
34+
#Memória
35+
memory = ConversationBufferMemory(ai_prefix=ai_prefix, human_prefix=human_prefix, memory_key="chat_history")
36+
37+
#Agente
38+
agent_chain = AgentExecutor.from_agent_and_tools(
39+
agent=ConversationalAgent.from_llm_and_tools(llm=llm, tools=tools, prefix=PREFIX, format_instructions=FORMAT_INSTRUCTIONS, suffix=SUFFIX,
40+
ai_prefix=ai_prefix, human_prefix=human_prefix),
41+
tools=library_tools,
42+
memory=memory,
43+
verbose=True,
44+
tags=['conversational-react-description']
45+
)
46+
47+
agent_chain.run("Ola Mundo!!")

library/legislativo/24708_arquivo.pdf

963 KB
Binary file not shown.

library/legislativo/25008_arquivo.pdf

2.35 MB
Binary file not shown.
4.26 MB
Binary file not shown.

library/legislativo/description.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Essa ferramenta permite investigar livros e conteúdos legislativos, incluindo a constituição federal e estadual, a lei orgânica municipal e regimentos internos.

requirements.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#langchain
2+
langchain
3+
openai
4+
5+
#para usar o Tool SERP - a API grátis é bem limitada e o custo talvez não compense.
6+
google-search-results
7+
8+
#biblioteca para converter qualquer arquivo não estruturado em um treco mais estruturado
9+
unstructured
10+
11+
#pode ser usado para gerar embeddins localmente; mas agora estamos usando a api do openai pra isso tb
12+
tiktoken
13+
14+
#weaviate (armazenamento de embeddings)
15+
weaviate-client
16+
17+
#partition pdf
18+
pdf2image
19+
pdfminer.six
20+
21+
#etc
22+
python-dotenv

tools/library.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import os
2+
from tqdm import tqdm
3+
from langchain import OpenAI
4+
5+
from langchain.tools import Tool
6+
7+
#Memory Document Loader (Unstructured)
8+
from langchain.embeddings.openai import OpenAIEmbeddings
9+
from langchain.text_splitter import CharacterTextSplitter
10+
from langchain.chains import RetrievalQA
11+
12+
from langchain.document_loaders.unstructured import UnstructuredBaseLoader
13+
from unstructured.cleaners.core import clean_extra_whitespace
14+
from unstructured.partition.html import partition_html
15+
from unstructured.partition.pdf import partition_pdf
16+
from typing import IO, Any, Callable, Dict, List, Optional, Sequence, Union
17+
18+
import weaviate
19+
from langchain.vectorstores import Weaviate
20+
21+
WEAVIATE_URL = os.getenv('WEAVIATE_URL')
22+
WEAVIATE_API_KEY = os.getenv('WEAVIATE_API_KEY')
23+
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
24+
25+
# Essa versão da classe aplica corretamente os _post_processors.
26+
# Talvez valha um pull request depois.
27+
class FixedUnstructuredFileLoader(UnstructuredBaseLoader):
28+
def __init__(
29+
self,
30+
file_path: Union[str, List[str]],
31+
mode: str = "paged",
32+
**unstructured_kwargs: Any,
33+
):
34+
"""Initialize with file path."""
35+
self.file_path = file_path
36+
super().__init__(mode=mode, **unstructured_kwargs)
37+
38+
39+
def _get_elements(self) -> List:
40+
from unstructured.partition.auto import partition
41+
elements = partition(filename=self.file_path, **self.unstructured_kwargs)
42+
return self._post_process_elements(elements)
43+
44+
def _post_process_elements(self, elements):
45+
"""Applies post-processing functions to extracted unstructured elements.
46+
Post-processing functions are Element -> Element callables passed
47+
in using the post_processors kwarg when the loader is instantiated."""
48+
print("Post processing...")
49+
for element in elements:
50+
for post_processor in self.post_processors:
51+
element.apply(post_processor)
52+
return elements
53+
54+
def _get_metadata(self) -> dict:
55+
return {"source": self.file_path}
56+
57+
58+
59+
class Library:
60+
def __init__(self, llm=OpenAI()):
61+
self.client = weaviate.Client(
62+
url=WEAVIATE_URL,
63+
auth_client_secret=weaviate.AuthApiKey(WEAVIATE_API_KEY),
64+
additional_headers={"X-OpenAI-Api-Key": OPENAI_API_KEY}
65+
)
66+
self.llm = llm
67+
self.tools = []
68+
69+
def create_index(self, index_name):
70+
print(f"Criando índice {index_name}")
71+
self.client.schema.create_class({
72+
"class": index_name,
73+
"properties": [
74+
{
75+
"name": "text",
76+
"dataType": ["text"],
77+
},
78+
{
79+
"name": "source",
80+
"dataType": ["text"],
81+
"description": "The source path of the file"
82+
}
83+
],
84+
"vectorizer": "text2vec-openai"
85+
})
86+
87+
def check_existing_file(self, filename, index_name):
88+
#checa se o arquivo já esta indexado
89+
print(f"Verificando se o arquivo {filename} já está indexado...")
90+
file_indexed = self.client.query.get(index_name, "source").with_where({
91+
"path": ["source"],
92+
"operator": "Equal",
93+
"valueText": filename
94+
}).with_limit(1).do()
95+
96+
check = file_indexed and len(file_indexed["data"]["Get"][index_name]) == 1
97+
return(check)
98+
99+
def load_file_embeddings(self, filename, index_name):
100+
if self.check_existing_file(filename, index_name):
101+
print(f"Arquivo {filename} já carregado")
102+
return None
103+
print(f"Carregando {filename}")
104+
loader = FixedUnstructuredFileLoader(filename, mode="paged", post_processors = [])#replace_art, clean_extra_whitespace
105+
documents = loader.load()
106+
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
107+
texts = text_splitter.split_documents(documents)
108+
109+
#dirty fix for pdf coordinates
110+
#ao invés disso ele precisa iterar e criar os datatypes correspondentes pra classe
111+
clear_texts = []
112+
for index,text in enumerate(texts):
113+
if text.metadata.get('coordinates'):
114+
texts[index].metadata['coordinates'] = None
115+
116+
embeddings = OpenAIEmbeddings()
117+
118+
119+
print("Subindo no Weaviate...")
120+
db = Weaviate.from_documents(texts, embedding=None, index_name=index_name, client=self.client, by_text=False)
121+
122+
def build_tool(self, index_name, description):
123+
print("Construindo ferramenta")
124+
from langchain.retrievers import WeaviateHybridSearchRetriever
125+
retriever = WeaviateHybridSearchRetriever(
126+
client=self.client,
127+
index_name=index_name,
128+
text_key="text",
129+
attributes=[],
130+
create_schema_if_missing=False
131+
)
132+
133+
tool_fn = RetrievalQA.from_chain_type(
134+
llm=self.llm, chain_type="stuff", retriever=retriever
135+
)
136+
137+
tool = Tool(name=f"Biblioteca {index_name}",
138+
func=tool_fn.run,
139+
description=description)
140+
141+
return tool
142+
143+
def generate_tools_for_library(self, library_path):
144+
print("Generating tools for library...")
145+
subfolders = [f for f in os.listdir(library_path) if os.path.isdir(os.path.join(library_path, f)) and not f.startswith('.')]
146+
for index_name in tqdm(subfolders, desc="Processing subfolders"):
147+
index_path = os.path.join(library_path, index_name)
148+
index_name_camel_case = ''.join(word.capitalize() for word in index_name.split('_'))
149+
description_file_path = os.path.join(index_path, 'description.txt')
150+
if os.path.exists(description_file_path):
151+
with open(description_file_path, 'r') as description_file:
152+
description = description_file.read().strip()
153+
else:
154+
print(f"Warning: description.txt not found in {index_path}. Skipping.")
155+
continue
156+
for filename in os.listdir(index_path):
157+
file_path = os.path.join(index_path, filename)
158+
if os.path.isfile(file_path) and filename != 'description.txt':
159+
self.load_file_embeddings(file_path, index_name_camel_case)
160+
tool = self.build_tool(index_name_camel_case, description)
161+
self.tools.append(tool)
162+
return self.tools
163+
164+

0 commit comments

Comments
 (0)