Mahinour1 commited on
Commit
fe72e62
·
1 Parent(s): c1fe770

replace project

Browse files
Dockerfile CHANGED
@@ -1,31 +1,73 @@
 
 
 
 
 
 
 
 
 
 
1
 
2
- # Get a distribution that has uv already installed
3
- FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
 
 
 
 
 
4
 
5
- # Add user - this is the user that will run the app
6
- # If you do not set user, the app will run as root (undesirable)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  RUN useradd -m -u 1000 user
8
- USER user
9
 
10
- # Set the home directory and path
11
  ENV HOME=/home/user \
12
- PATH=/home/user/.local/bin:$PATH
13
 
14
- ENV UVICORN_WS_PROTOCOL=websockets
 
15
 
 
 
 
 
16
 
17
- # Set the working directory
18
- WORKDIR $HOME/app
 
 
 
 
 
 
 
19
 
20
- # Copy the app to the container
21
- COPY --chown=user . $HOME/app
22
 
23
- # Install the dependencies
24
- # RUN uv sync --frozen
25
- RUN uv sync
26
 
27
- # Expose the port
28
  EXPOSE 7860
29
 
30
- # Run the app
31
- CMD ["uv", "run", "chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
1
+ # FROM python:3.10
2
+ # RUN useradd -m -u 1000 user
3
+ # USER root
4
+ # ENV HOME=/home/user \
5
+ # PATH=/home/user/.local/bin:$PATH
6
+ # WORKDIR $HOME/app
7
+ # COPY --chown=user:user . $HOME/app
8
+ # # COPY --chown=user . $HOME/app
9
+ # COPY requirements.txt .
10
+ # # COPY ./requirements.txt ~/app/requirements.txt
11
 
12
+ # RUN pip install --upgrade pip
13
+ # RUN pip install --no-cache-dir -r requirements.txt
14
+ # # RUN pip install -r requirements.
15
+ # # Expose port
16
+ # EXPOSE 7860
17
+ # # RUN pip install pydantic==2.10.1 chainlit
18
+ # # COPY . .
19
 
20
+ # # Ensure app and .local have proper permissions
21
+ # # USER root
22
+ # RUN chmod -R 755 /home/user/app
23
+ # RUN chmod -R 755 /home/user/.local
24
+ # RUN mkdir -p /home/user/app/.files && chown -R user:user /home/user/app/.files
25
+
26
+
27
+ # USER user
28
+
29
+ # CMD ["chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "7860"]
30
+
31
+ # # CMD ["chainlit", "run", "app.py", "--port", "7860"]
32
+
33
+
34
+ FROM python:3.10
35
+
36
+ # Create user with specific UID
37
  RUN useradd -m -u 1000 user
 
38
 
39
+ # Set environment variables
40
  ENV HOME=/home/user \
41
+ PATH=/home/user/.local/bin:$PATH
42
 
43
+ # Set working directory
44
+ WORKDIR $HOME/app
45
 
46
+ # Copy requirements and install dependencies as root
47
+ COPY requirements.txt .
48
+ RUN pip install --upgrade pip && \
49
+ pip install --no-cache-dir -r requirements.txt
50
 
51
+ # Copy application files
52
+ COPY . .
53
+
54
+ # Create necessary directories and set permissions
55
+ RUN mkdir -p /home/user/app/.files && \
56
+ mkdir -p /home/user/.local && \
57
+ chown -R user:user /home/user
58
+
59
+ RUN pip install pydantic==2.10.1 chainlit
60
 
61
+ COPY chainlit.md .
 
62
 
 
 
 
63
 
64
+ # Expose port
65
  EXPOSE 7860
66
 
67
+ # Switch to non-root user
68
+ USER user
69
+
70
+ # Run the application
71
+ CMD ["chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "7860", "--no-cache"]
72
+
73
+ # CMD ["chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "7860"]
Dockerfile:Zone.Identifier ADDED
File without changes
app.py CHANGED
@@ -1,139 +1,304 @@
1
  import os
2
- from typing import List
3
- from chainlit.types import AskFileResponse
4
- from aimakerspace.text_utils import CharacterTextSplitter, TextFileLoader, PDFLoader
5
- from aimakerspace.openai_utils.prompts import (
6
- UserRolePrompt,
7
- SystemRolePrompt,
8
- AssistantRolePrompt,
9
- )
10
- from aimakerspace.openai_utils.embedding import EmbeddingModel
11
- from aimakerspace.vectordatabase import VectorDatabase
12
- from aimakerspace.openai_utils.chatmodel import ChatOpenAI
13
- import chainlit as cl
14
 
15
- system_template = """\
16
- Use the following context to answer a users question. If you cannot find the answer in the context, say you don't know the answer."""
17
- system_role_prompt = SystemRolePrompt(system_template)
18
 
19
- user_prompt_template = """\
20
- Context:
21
- {context}
22
 
23
- Question:
24
- {question}
25
- """
26
- user_role_prompt = UserRolePrompt(user_prompt_template)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- class RetrievalAugmentedQAPipeline:
29
- def __init__(self, llm: ChatOpenAI(), vector_db_retriever: VectorDatabase) -> None:
30
- self.llm = llm
31
- self.vector_db_retriever = vector_db_retriever
32
 
33
- async def arun_pipeline(self, user_query: str):
34
- context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
35
 
36
- context_prompt = ""
37
- for context in context_list:
38
- context_prompt += context[0] + "\n"
39
 
40
- formatted_system_prompt = system_role_prompt.create_message()
41
 
42
- formatted_user_prompt = user_role_prompt.create_message(question=user_query, context=context_prompt)
 
 
 
 
43
 
44
- async def generate_response():
45
- async for chunk in self.llm.astream([formatted_system_prompt, formatted_user_prompt]):
46
- yield chunk
47
 
48
- return {"response": generate_response(), "context": context_list}
49
 
50
- text_splitter = CharacterTextSplitter()
 
 
 
 
 
51
 
 
 
 
52
 
53
- def process_file(file: AskFileResponse):
54
- import tempfile
55
- import shutil
56
-
57
- print(f"Processing file: {file.name}")
58
 
59
- # Create a temporary file with the correct extension
60
- suffix = f".{file.name.split('.')[-1]}"
61
- with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
62
- # Copy the uploaded file content to the temporary file
63
- shutil.copyfile(file.path, temp_file.name)
64
- print(f"Created temporary file at: {temp_file.name}")
65
-
66
- # Create appropriate loader
67
- if file.name.lower().endswith('.pdf'):
68
- loader = PDFLoader(temp_file.name)
69
- else:
70
- loader = TextFileLoader(temp_file.name)
71
-
72
- try:
73
- # Load and process the documents
74
- documents = loader.load_documents()
75
- texts = text_splitter.split_texts(documents)
76
- return texts
77
- finally:
78
- # Clean up the temporary file
 
 
 
 
 
 
79
  try:
80
- os.unlink(temp_file.name)
81
- except Exception as e:
82
- print(f"Error cleaning up temporary file: {e}")
 
83
 
 
84
 
85
- @cl.on_chat_start
86
- async def on_chat_start():
87
- files = None
 
88
 
89
- # Wait for the user to upload a file
90
- while files == None:
91
- files = await cl.AskFileMessage(
92
- content="Please upload a Text or PDF file to begin! test",
93
- accept=["text/plain", "application/pdf"],
94
- max_size_mb=2,
95
- timeout=180,
96
- ).send()
97
 
98
- file = files[0]
 
 
 
99
 
100
- msg = cl.Message(
101
- content=f"Processing `{file.name}`..."
102
- )
103
- await msg.send()
104
 
105
- # load the file
106
- texts = process_file(file)
107
 
108
- print(f"Processing {len(texts)} text chunks")
 
 
 
 
 
 
 
109
 
110
- # Create a dict vector store
111
- vector_db = VectorDatabase()
112
- vector_db = await vector_db.abuild_from_list(texts)
113
-
114
- chat_openai = ChatOpenAI()
115
 
116
- # Create a chain
117
- retrieval_augmented_qa_pipeline = RetrievalAugmentedQAPipeline(
118
- vector_db_retriever=vector_db,
119
- llm=chat_openai
120
- )
121
-
122
- # Let the user know that the system is ready
123
- msg.content = f"Processing `{file.name}` done. You can now ask questions!"
124
- await msg.update()
125
 
126
- cl.user_session.set("chain", retrieval_augmented_qa_pipeline)
 
127
 
 
128
 
129
- @cl.on_message
130
- async def main(message):
131
- chain = cl.user_session.get("chain")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- msg = cl.Message(content="")
134
- result = await chain.arun_pipeline(message.content)
135
 
136
- async for stream_resp in result["response"]:
137
- await msg.stream_token(stream_resp)
 
 
 
 
138
 
139
- await msg.send()
 
1
  import os
2
+ import getpass
3
+ from operator import itemgetter
4
+ from typing import List, Dict
5
+ import json
6
+ import requests
 
 
 
 
 
 
 
7
 
 
 
 
8
 
 
 
 
9
 
10
+ #LangChain, LangGraph
11
+ from langchain_openai import ChatOpenAI
12
+ from langgraph.graph import START, StateGraph, END
13
+ from typing_extensions import List, TypedDict
14
+ from langchain_core.documents import Document
15
+ from langchain_core.prompts import ChatPromptTemplate
16
+ from langchain.schema.output_parser import StrOutputParser
17
+ from langchain_core.tools import Tool, tool
18
+ from langgraph.prebuilt import ToolNode
19
+ from typing import TypedDict, Annotated
20
+ from langgraph.graph.message import add_messages
21
+ import operator
22
+ from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
23
+ from langchain.vectorstores import Qdrant
24
+ from langchain.embeddings import OpenAIEmbeddings
25
+ from langchain.schema import Document
26
+ from qdrant_client import QdrantClient
27
+ from qdrant_client.http.models import Distance, VectorParams
28
 
29
+ import chainlit as cl
30
+ import tempfile
31
+ import shutil
 
32
 
 
 
33
 
 
 
 
34
 
 
35
 
36
+ #helper imports
37
+ from code_analysis import *
38
+ from tools import search_pypi, write_to_docx
39
+ from prompts import describe_imports, main_prompt, documenter_prompt
40
+ from states import AgentState
41
 
42
+ os.environ["CHAINLIT_DISABLE_WEBSOCKETS"] = "true"
 
 
43
 
 
44
 
45
+ # Global variables to store processed data
46
+ processed_file_path = None
47
+ document_file_path = None
48
+ vectorstore = None
49
+ main_chain = None
50
+ qdrant_client = None
51
 
52
+ @cl.on_chat_start
53
+ async def on_chat_start():
54
+ await cl.Message(content="Welcome to the Python Code Documentation Assistant! Please upload a Python file to get started.").send()
55
 
56
+ @cl.on_message
57
+ async def on_message(message: cl.Message):
58
+ global processed_file_path, document_file_path, vectorstore, main_chain, qdrant_client
 
 
59
 
60
+ if message.elements and any(el.type == "file" for el in message.elements):
61
+ file_elements = [el for el in message.elements if el.type == "file"]
62
+ file_element = file_elements[0]
63
+ is_python_file = (
64
+ file_element.mime.startswith("text/x-python") or
65
+ file_element.name.endswith(".py") or
66
+ file_element.mime == "text/plain" # Some systems identify .py as text/plain
67
+ )
68
+ if is_python_file:
69
+ # Send processing message
70
+ msg = cl.Message(content="Processing your Python file...")
71
+ await msg.send()
72
+
73
+ print(f'file element \n {file_element} \n')
74
+
75
+ # Save uploaded file to a temporary location
76
+ temp_dir = tempfile.mkdtemp()
77
+ file_path = os.path.join(temp_dir, file_element.name)
78
+
79
+ with open(file_element.path, "rb") as source_file:
80
+ file_content_bytes = source_file.read()
81
+ with open(file_path, "wb") as destination_file:
82
+ destination_file.write(file_content_bytes)
83
+
84
+ processed_file_path = file_path
85
+
86
  try:
87
+
88
+ # read file and extract imports
89
+ file_content = read_python_file(file_path)
90
+ imports = extract_imports(file_content, file_path)
91
 
92
+ print(f'Done reading file')
93
 
94
+ # Define describe packages graph
95
+ search_packages_tools = [search_pypi]
96
+ describe_imports_llm = ChatOpenAI(model="gpt-4o-mini")
97
+ # describe_imports_llm = describe_imports_llm.bind_tools(tools = search_packages_tools, tool_choice="required")
98
 
99
+ describe_imports_prompt = ChatPromptTemplate.from_messages([
100
+ ("system", describe_imports),
101
+ ("human", "{imports}")
102
+ ])
 
 
 
 
103
 
104
+ describe_imports_chain = (
105
+ {"code_language": itemgetter("code_language"), "imports": itemgetter("imports")}
106
+ | describe_imports_prompt | describe_imports_llm | StrOutputParser()
107
+ )
108
 
109
+ print(f'done defining imports chain')
 
 
 
110
 
 
 
111
 
112
+ # Define imports chain function
113
+ def call_imports_chain(state):
114
+ last_message= state["messages"][-1]
115
+ content = json.loads(last_message.content)
116
+ chain_input = {"code_language": content['code_language'],
117
+ "imports": content['imports']}
118
+ response = describe_imports_chain.invoke(chain_input)
119
+ return {"messages": [AIMessage(content=response)]}
120
 
121
+ # bind model to tool or ToolNode
122
+ imports_tool_node = ToolNode(search_packages_tools)
 
 
 
123
 
124
+ # construct graph and compile
125
+ uncompiled_imports_graph = StateGraph(AgentState)
126
+ uncompiled_imports_graph.add_node("imports_agent", call_imports_chain)
127
+ uncompiled_imports_graph.add_node("imports_action", imports_tool_node)
128
+ uncompiled_imports_graph.set_entry_point("imports_agent")
129
+
130
+ def should_continue(state):
131
+ last_message = state["messages"][-1]
 
132
 
133
+ if last_message.tool_calls:
134
+ return "imports_action"
135
 
136
+ return END
137
 
138
+ uncompiled_imports_graph.add_conditional_edges(
139
+ "imports_agent",
140
+ should_continue
141
+ )
142
+
143
+ uncompiled_imports_graph.add_edge("imports_action", "imports_agent")
144
+
145
+ compiled_imports_graph = uncompiled_imports_graph.compile()
146
+
147
+ print(f'compiled imports graph')
148
+ # Invoke imports graph
149
+ initial_state = {
150
+ "messages": [{
151
+ "role": "human",
152
+ "content": json.dumps({
153
+ "code_language": "python",
154
+ "imports": imports
155
+ })
156
+ }]
157
+ }
158
+
159
+ # await msg.update(content="Analyzing imports and generating documentation...")
160
+ msg.content = "Analyzing your code and generating documentation..."
161
+ await msg.update()
162
+
163
+ msg = cl.Message(content="Analyzing your code and generating documentation...")
164
+ await msg.send()
165
+
166
+ result = compiled_imports_graph.invoke(initial_state)
167
+
168
+ # Define qdrant Database
169
+ qdrant_client = QdrantClient(":memory:")
170
+
171
+ embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")
172
+ embedding_dim = 1536
173
+
174
+ qdrant_client.create_collection(
175
+ collection_name="description_rag_data",
176
+ vectors_config=VectorParams(size=embedding_dim, distance=Distance.COSINE),
177
+ )
178
+
179
+ vectorstore = Qdrant(qdrant_client, collection_name="description_rag_data", embeddings=embedding_model)
180
+
181
+ # Add packages chunks
182
+ text = result['messages'][-1].content
183
+ chunks = [
184
+ {"type": "Imported Packages", "name": "Imported Packages", "content": text},
185
+ #{"type": "Source Code", "name": "Source Code", "content": file_content},
186
+
187
+ ]
188
+
189
+ docs = [
190
+ Document(
191
+ page_content=f"{chunk['type']} - {chunk['name']} - {chunk['content']}", # Content for the model
192
+ metadata={**chunk} # Store metadata, but don't put embeddings here
193
+ )
194
+ for chunk in chunks
195
+ ]
196
+ vectorstore.add_documents(docs)
197
+ qdrant_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
198
+
199
+ print('done adding docs to DB')
200
+ #define documenter chain
201
+ documenter_llm = ChatOpenAI(model="gpt-4o-mini")
202
+ documenter_llm_prompt = ChatPromptTemplate.from_messages([
203
+ ("system", documenter_prompt),
204
+ ])
205
+ documenter_chain = (
206
+ {"context": itemgetter("context")}
207
+ | documenter_llm_prompt
208
+ | documenter_llm
209
+ | StrOutputParser()
210
+ )
211
+
212
+ print('done defining documenter chain')
213
+ #extract description chunks from database
214
+ collection_name = "description_rag_data"
215
+ all_points = qdrant_client.scroll(collection_name=collection_name, limit=1000)[0] # Adjust limit if needed
216
+ one_chunk = all_points[0].payload
217
+ input_text = f"type: {one_chunk['metadata']['type']} \nname: {one_chunk['metadata']['name']} \ncontent: {one_chunk['metadata']['content']}"
218
+
219
+ print('done extracting chunks form DB')
220
+
221
+ document_response = documenter_chain.invoke({"context": input_text})
222
+
223
+ print('done invoking documenter chain and will write in docx')
224
+ # write packages description in word file
225
+ document_file_path = write_to_docx(document_response)
226
+
227
+ print('done writing docx file')
228
+ # Set up Main Chain for chat
229
+ main_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
230
+
231
+
232
+ main_llm_prompt = ChatPromptTemplate.from_messages([
233
+ ("system", main_prompt),
234
+ ("human", "{query}")
235
+ ])
236
+
237
+ main_chain = (
238
+ {"context": itemgetter("query") | qdrant_retriever, "code_language": itemgetter("code_language"), "query": itemgetter("query"), }
239
+ | main_llm_prompt
240
+ | main_llm
241
+ | StrOutputParser()
242
+ )
243
+
244
+ print('done defining main chain')
245
+ # Present download button for the document
246
+ elements = [
247
+ cl.File(
248
+ name="documentation.docx",
249
+ path=document_file_path,
250
+ display="inline"
251
+ )
252
+ ]
253
+ print('done defining elements')
254
+ msg.content = "✅ Your Python file has been processed! You can download the documentation file below. How can I help you with your code?"
255
+ msg.elements = elements
256
+ await msg.update()
257
+
258
+ except Exception as e:
259
+ msg.content = f"❌ Error processing file: {str(e)}"
260
+ await msg.update()
261
+
262
+
263
+ else:
264
+ await cl.Message(content="Please upload a Python (.py) file.").send()
265
+
266
+ # Handle chat messages if file has been processed
267
+ elif processed_file_path and main_chain:
268
+ user_input = message.content
269
+ # Send thinking message
270
+ msg = cl.Message(content="Thinking...")
271
+ await msg.send()
272
+
273
+ try:
274
+ # Use main_chain to answer the query
275
+ # invoke main chain
276
+ inputs = {
277
+ 'code_language': 'Python',
278
+ 'query': user_input
279
+ }
280
+
281
+ response = main_chain.invoke(inputs)
282
+
283
+ # Update with the response
284
+ msg.content = response
285
+ await msg.update()
286
+
287
+
288
+ except Exception as e:
289
+ msg.content = f"❌ Error processing your question: {str(e)}"
290
+ await msg.update()
291
+
292
+
293
+
294
+ else:
295
+ await cl.Message(content="Please upload a Python file first before asking questions.").send()
296
 
 
 
297
 
298
+ @cl.on_stop
299
+ def on_stop():
300
+ global processed_file_path
301
+ # Clean up temporary files
302
+ if processed_file_path and os.path.exists(os.path.dirname(processed_file_path)):
303
+ shutil.rmtree(os.path.dirname(processed_file_path))
304
 
 
code_analysis.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def read_python_file(file_path):
3
+ try:
4
+ with open(file_path, 'r', encoding='utf-8') as f:
5
+ content = f.read()
6
+ return content
7
+ except FileNotFoundError:
8
+ print(f"File not found: {file_path}")
9
+ raise
10
+ except IOError as e:
11
+ print(f"Error reading file {file_path}: {str(e)}")
12
+ raise
13
+ except Exception as e:
14
+ print(f"Unexpected error reading file {file_path}: {str(e)}")
15
+ raise
16
+
17
+ def extract_imports(code, file_path):
18
+ try:
19
+
20
+ # Split into lines and find imports
21
+ import_lines = []
22
+ for line in code.split('\n'):
23
+ line = line.strip()
24
+ if line.startswith('import ') or line.startswith('from '):
25
+ import_lines.append(line)
26
+
27
+ return import_lines
28
+
29
+ except Exception as e:
30
+ print(f"Error extracting imports from file {file_path}: {str(e)}")
31
+ return []
code_analysis.py:Zone.Identifier ADDED
File without changes
prompts.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ describe_imports = """You are an expert {code_language} developer.
2
+ Your will be given code lines that import packages.
3
+ Your role is to give a brief description of each package
4
+
5
+ You have access to the following tool and you MUST use it:
6
+ search_pypi: Use this to get information about Python packages from PyPI.
7
+
8
+ For each import:
9
+ 1. Extract the main package name
10
+ 2. Use the search_pypi tool to get package information by calling "search_pypi(package_name)"
11
+ 3. Combine the information into a clear description
12
+ 4. If the retuned value of tool is empty use your own knowledge
13
+ 5. If you have no knowledge for this package then it's description should be "I don't know details about this package"
14
+
15
+ You must respond in the following JSON format:
16
+ {{"Imported_Packages": [
17
+ {{"name": "package1", "desc": "brief description of package1"}},
18
+ {{"name": "package2", "desc": "brief description of package2"}}
19
+ ]}}
20
+
21
+ Rules for the output:
22
+ 1. Use valid JSON format
23
+ 2. Package names should be the exact names from the imports
24
+ 3. Descriptions should be brief and clear
25
+ 4. Do not include any text outside the JSON structure
26
+ """
27
+
28
+ documenter_prompt = """You are an expert code documenter.
29
+ Your role is to write a well structured document that describes code functionality.
30
+
31
+ From the given context:
32
+ 1- type: is the type of the code block (funciton, class, ..)
33
+ 2- name: is the name of the code block
34
+ 3- content: is the description of the code block
35
+
36
+ Instructions:
37
+ Write a docx document with the following structure Heading 1(type) -> Heading 2(name) -> content
38
+
39
+ Rules for the output:
40
+ 1. Don't write information out of context
41
+ 2. If needed, structure long responses in lists and sections
42
+
43
+ <context>
44
+ {context}
45
+ </context>
46
+ """
47
+
48
+ main_prompt = """You are an expert {code_language} developer.
49
+ Your role is to answer user's questions about code and its description that will be given to you in context.
50
+
51
+ Rules for the output:
52
+ 1. Don't answer out of context questions.
53
+ 2. Provide a single, clear response using only the given context.
54
+ 3. If needed, structure long responses in lists and sections.
55
+
56
+ <context>
57
+ {context}
58
+ </context>
59
+ """
prompts.py:Zone.Identifier ADDED
File without changes
requirements.txt ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==23.2.1
2
+ aiohappyeyeballs==2.4.6
3
+ aiohttp==3.11.12
4
+ aiosignal==1.3.2
5
+ annotated-types==0.7.0
6
+ anyio==4.8.0
7
+ async-timeout==4.0.3
8
+ asyncer==0.0.7
9
+ attrs==25.1.0
10
+ bidict==0.23.1
11
+ Brotli==1.1.0
12
+ certifi==2025.1.31
13
+ chainlit==2.2.1
14
+ charset-normalizer==3.4.1
15
+ chevron==0.14.0
16
+ click==8.1.8
17
+ dataclasses-json==0.6.7
18
+ Deprecated==1.2.18
19
+ distro==1.9.0
20
+ docx==0.2.4
21
+ fastapi==0.115.8
22
+ filetype==1.2.0
23
+ frozenlist==1.5.0
24
+ googleapis-common-protos==1.68.0
25
+ greenlet==3.1.1
26
+ grpcio==1.70.0
27
+ grpcio-tools==1.70.0
28
+ h11==0.14.0
29
+ h2==4.2.0
30
+ hpack==4.1.0
31
+ httpcore==1.0.7
32
+ httpx==0.28.1
33
+ httpx-sse==0.4.0
34
+ hyperframe==6.1.0
35
+ idna==3.10
36
+ importlib_metadata==8.5.0
37
+ jiter==0.8.2
38
+ jsonpatch==1.33
39
+ jsonpointer==3.0.0
40
+ langchain==0.3.15
41
+ langchain-community==0.3.15
42
+ langchain-core==0.3.31
43
+ langchain-openai==0.3.1
44
+ langchain-qdrant==0.2.0
45
+ langchain-text-splitters==0.3.5
46
+ langgraph==0.2.74
47
+ langgraph-checkpoint==2.0.16
48
+ langgraph-sdk==0.1.51
49
+ langsmith==0.3.8
50
+ Lazify==0.4.0
51
+ literalai==0.1.103
52
+ lxml==5.3.1
53
+ marshmallow==3.26.1
54
+ msgpack==1.1.0
55
+ multidict==6.1.0
56
+ mypy-extensions==1.0.0
57
+ numpy==1.26.4
58
+ openai==1.63.2
59
+ opentelemetry-api==1.29.0
60
+ opentelemetry-exporter-otlp==1.29.0
61
+ opentelemetry-exporter-otlp-proto-common==1.29.0
62
+ opentelemetry-exporter-otlp-proto-grpc==1.29.0
63
+ opentelemetry-exporter-otlp-proto-http==1.29.0
64
+ opentelemetry-instrumentation==0.50b0
65
+ opentelemetry-proto==1.29.0
66
+ opentelemetry-sdk==1.29.0
67
+ opentelemetry-semantic-conventions==0.50b0
68
+ orjson==3.10.15
69
+ pillow==11.1.0
70
+ portalocker==2.10.1
71
+ propcache==0.2.1
72
+ protobuf==5.29.3
73
+ pydantic==2.10.6
74
+ pydantic-settings==2.7.1
75
+ pydantic_core==2.27.2
76
+ PyJWT==2.10.1
77
+ python-docx==1.1.2
78
+ python-dotenv==1.0.1
79
+ python-engineio==4.11.2
80
+ python-multipart==0.0.18
81
+ python-socketio==5.12.1
82
+ PyYAML==6.0.2
83
+ qdrant-client==1.13.2
84
+ regex==2024.11.6
85
+ requests==2.32.3
86
+ requests-toolbelt==1.0.0
87
+ simple-websocket==1.1.0
88
+ sniffio==1.3.1
89
+ socksio==1.0.0
90
+ SQLAlchemy==2.0.38
91
+ starlette==0.41.3
92
+ syncer==2.0.3
93
+ tenacity==9.0.0
94
+ tiktoken==0.9.0
95
+ tomli==2.2.1
96
+ tqdm==4.67.1
97
+ typing-inspect==0.9.0
98
+ uptrace==1.29.0
99
+ urllib3==2.3.0
100
+ uvicorn==0.34.0
101
+ watchfiles==0.20.0
102
+ wrapt==1.17.2
103
+ wsproto==1.2.0
104
+ yarl==1.18.3
105
+ zipp==3.21.0
106
+ zstandard==0.23.0
requirements.txt:Zone.Identifier ADDED
File without changes
states.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from typing_extensions import List, TypedDict
2
+ from typing import TypedDict, Annotated
3
+ from langgraph.graph.message import add_messages
4
+
5
+
6
+ class AgentState(TypedDict):
7
+ messages: Annotated[list, add_messages]
states.py:Zone.Identifier ADDED
File without changes
tools.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import Tool, tool
2
+ import requests
3
+ import json
4
+ from docx import Document
5
+ import re
6
+
7
+
8
+ @tool
9
+ def search_pypi(package_name: str) -> str:
10
+ """Search PyPI for Python package information. Input should be the package name.
11
+ Args:
12
+ package_name: name of the package
13
+ """
14
+ print(f"Tool called for package: {package_name}")
15
+ base_url = "https://pypi.org/pypi"
16
+ try:
17
+ try:
18
+ response = requests.get(f"{base_url}/{package_name}/json")
19
+ response.raise_for_status()
20
+ info = response.json()
21
+ except requests.RequestException as e:
22
+ raise Exception(f"Error fetching PyPI info for {package_name}: {str(e)}")
23
+ result = json.dumps({
24
+ "name": info["info"]["name"],
25
+ "summary": info["info"]["summary"],
26
+ })
27
+ print(f"Tool result: {result}")
28
+ return result
29
+ except Exception as e:
30
+ return f"Could not find package information: {str(e)}"
31
+
32
+ # @tool
33
+ def write_to_docx(documentation_text: str) -> str:
34
+ """
35
+ Writes the AI-generated documentation to a .docx file and returns the file path.
36
+ """
37
+ doc = Document()
38
+ # doc.add_heading("Code Documentation", level=1)
39
+
40
+ lines = documentation_text.split("\n")
41
+ for line in lines:
42
+ if line.startswith("# "): # Section Heading
43
+ doc.add_heading(line[2:], level=1)
44
+ elif line.startswith("## "): # Subsection Heading
45
+ doc.add_heading(line[3:], level=2)
46
+ else: # Normal paragraph
47
+ doc.add_paragraph(line)
48
+
49
+ file_path = "generated_documentation.docx"
50
+ doc.save(file_path)
51
+ return file_path
tools.py:Zone.Identifier ADDED
File without changes