├── rag-ollama-basic ├── .coverage ├── data │ └── mydocument.pdf ├── requirements-dev.txt ├── .gitignore ├── requirements.txt ├── README └── rag_local.py ├── agent-ollama-basic ├── requirements-dev.txt ├── requirements.txt ├── .gitignore ├── README └── agent_local.py ├── README.md └── LICENSE /rag-ollama-basic/.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeyNina101/aimin/HEAD/rag-ollama-basic/.coverage -------------------------------------------------------------------------------- /rag-ollama-basic/data/mydocument.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeyNina101/aimin/HEAD/rag-ollama-basic/data/mydocument.pdf -------------------------------------------------------------------------------- /rag-ollama-basic/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # Development tools for formatting, linting, and type checking 2 | # To install these dependencies: 3 | # pip install -r requirements-dev.txt 4 | # for freeze versions: pip freeze > requirements-dev.txt 5 | 6 | black 7 | flake8 8 | flake8-bugbear 9 | flake8-docstrings 10 | flake8-comprehensions 11 | pep8-naming 12 | mypy 13 | pytest 14 | pytest-cov 15 | -------------------------------------------------------------------------------- /agent-ollama-basic/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # Development tools for formatting, linting, and type checking 2 | # To install these dependencies: 3 | # pip install -r requirements-dev.txt 4 | # for freeze versions: pip freeze > requirements-dev.txt 5 | 6 | black 7 | flake8 8 | flake8-bugbear 9 | flake8-docstrings 10 | flake8-comprehensions 11 | pep8-naming 12 | mypy 13 | pytest 14 | pytest-cov 15 | -------------------------------------------------------------------------------- /agent-ollama-basic/requirements.txt: -------------------------------------------------------------------------------- 1 | # For freezxe versions pip freeze > requirements.txt 2 | 3 | # Core LangChain framework 4 | langchain 5 | langchain-core 6 | langchain-community 7 | 8 | # Ollama integration for local LLMs 9 | langchain-ollama 10 | 11 | # Vector store for embeddings and similarity search 12 | chromadb 13 | 14 | # Load environment variables from a .env file 15 | python-dotenv 16 | 17 | -------------------------------------------------------------------------------- /rag-ollama-basic/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore virtual environments 2 | venv/ 3 | .env 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *.pyo 9 | *.pyd 10 | 11 | # VS Code settings 12 | .vscode/ 13 | 14 | # Local vector database (ChromaDB) 15 | chroma_db/ 16 | 17 | # Log and runtime files 18 | *.log 19 | *.pid 20 | 21 | # User and backup files 22 | *.swp 23 | *.swo 24 | *~ 25 | *.bak 26 | 27 | # Ignore PDF documents in the data folder (optional) 28 | data/*.pdf 29 | 30 | # Ollama cache and models (optional, if not shared) 31 | ~/.ollama/ 32 | -------------------------------------------------------------------------------- /agent-ollama-basic/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore virtual environments 2 | venv/ 3 | .env 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *.pyo 9 | *.pyd 10 | 11 | # VS Code settings 12 | .vscode/ 13 | 14 | # Local vector database (ChromaDB) 15 | chroma_db/ 16 | 17 | # Log and runtime files 18 | *.log 19 | *.pid 20 | 21 | # User and backup files 22 | *.swp 23 | *.swo 24 | *~ 25 | *.bak 26 | 27 | # Ignore PDF documents in the data folder (optional) 28 | data/*.pdf 29 | 30 | # Ollama cache and models (optional, if not shared) 31 | ~/.ollama/ 32 | -------------------------------------------------------------------------------- /rag-ollama-basic/requirements.txt: -------------------------------------------------------------------------------- 1 | # For freezxe versions pip freeze > requirements.txt 2 | 3 | # Core LangChain framework 4 | langchain 5 | langchain-core 6 | langchain-community 7 | 8 | # Ollama integration for local LLMs 9 | langchain-ollama 10 | 11 | # Vector store for embeddings and similarity search 12 | chromadb 13 | 14 | # Embedding generation (when not using model-native embeddings) 15 | # sentence-transformers 16 | 17 | # Basic PDF parsing (for well-structured PDFs) 18 | pypdf 19 | # Advanced PDF parsing (handles complex layouts and noisy documents) 20 | unstructured[pdf] 21 | 22 | # Load environment variables from a .env file 23 | python-dotenv 24 | 25 | # Token counting and context window management 26 | tiktoken 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI'm In 2 | 3 | This repository collects real use cases of applied AI, focused on backend systems, automation, agents, and integration workflows. Each case is implemented with functional code and clear structure. Technologies include Python, n8n, Make, LangChain, local models, and custom pipelines. 4 | 5 | ## Contents 6 | 7 | - Use cases organized by goal and architecture 8 | - Executable logic with focused, structured code 9 | - Integration patterns for AI-first systems in real business contexts 10 | 11 | ## Structure 12 | 13 | Each folder includes: 14 | - Problem and context 15 | - Architecture overview 16 | - Code 17 | - Notes and variations 18 | 19 | ## License 20 | 21 | MIT — see [LICENSE](./LICENSE) 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Nina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /agent-ollama-basic/README: -------------------------------------------------------------------------------- 1 | # agent_local.py 2 | 3 | 1. Run `ollama serve` in one terminal to launch the local model server. 4 | 5 | 2. Run `python agent_local.py` in another terminal to start the agent. 6 | 7 | 3. The script loads a local model via `ChatOllama`, registers Python tools using the `@tool` decorator, and builds a structured prompt for tool-using agents. 8 | 9 | 4. The agent receives a user query and begins step-by-step reasoning using the ReAct pattern. 10 | 11 | 5. The model generates a **Thought** indicating its reasoning. 12 | 13 | 6. The model selects an **Action** and the arguments required. 14 | 15 | 7. The `AgentExecutor` runs the corresponding Python function (tool). 16 | 17 | 8. The tool’s result is returned as an **Observation**. 18 | 19 | 9. The model receives the observation and continues reasoning until it produces a final answer. 20 | 21 | 10. If `verbose=True`, each step of the reasoning is printed to the console. 22 | 23 | 11. Each call to `run_agent()` is stateless and does not retain prior context. If you want the agent to remember previous interactions, you must manually pass a `chat_history` object to preserve memory across runs. Keep in mind that the model’s `context_window` only defines the maximum token limit per interaction — not persistent memory. 24 | 25 | 26 | ### Model Info 27 | 28 | **Model**: `granite3-dense:2b` 29 | 30 | The IBM Granite 2B and 8B models are designed to support tool-based use cases and retrieval-augmented generation (RAG), streamlining code generation, translation, and bug fixing. 31 | 32 | | Property | Value | 33 | |-----------------|------------------------| 34 | | Size | 1.6 GB | 35 | | Context Length | 4K tokens | 36 | | Input Type | Text | 37 | | Release Date | October 21st, 2024 | 38 | | License | Apache 2.0 | 39 | | Developers | IBM Research | 40 | 41 | See: [https://ollama.com/library/granite3-dense](https://ollama.com/library/granite3-dense) 42 | 43 | ## Acknowledgements 44 | 45 | This project adapts and extends the tutorial by Chaitanya Rahalkar 46 | Source: [freeCodeCamp article](https://www.freecodecamp.org/news/build-a-local-ai/) 47 | GitHub: [chaitanyarahalkar](https://github.com/chaitanyarahalkar) 48 | 49 | Adapted and maintained by Nina Fernanda Durán (https://github.com/HeyNina101/) -------------------------------------------------------------------------------- /rag-ollama-basic/README: -------------------------------------------------------------------------------- 1 | # rag_local.py 2 | 3 | 1. Start the Ollama server in a separate terminal using `ollama serve`. This launches the local Ollama API server at http://localhost:11434. 4 | 5 | 2. Run the script with `python rag_local.py`. 6 | 7 | 3. The script initializes the embedding model using `nomic-embed-text` via LangChain’s `OllamaEmbeddings`. This converts text chunks into numerical vectors. 8 | 9 | 4. It loads a PDF file using a loader like `PyPDFLoader`, extracting the full textual content of the document. 10 | 11 | 5. The text is split into smaller chunks using `RecursiveCharacterTextSplitter`. Each chunk is around 1000 characters, with optional overlap. 12 | 13 | 6. Each chunk is embedded using the model running locally in Ollama. 14 | 15 | 7. These embeddings are stored in `ChromaDB`, a local vector database. If the database already exists, new chunks can be appended. 16 | 17 | 8. A RAG chain is built using LangChain’s LCEL syntax. The pipeline includes a retriever for fetching relevant chunks, a prompt template, the `qwen:0.5b` model accessed via `ChatOllama`, and an output parser. 18 | 19 | 9. When a question is submitted, LangChain computes its embedding, retrieves the most relevant chunks from ChromaDB, constructs the final prompt, queries the LLM, and parses the output. 20 | 21 | 10. The final response is printed to the terminal. The answer is strictly based on the retrieved context, not general model knowledge. 22 | 23 | ### Model Info 24 | 25 | **Model:** `qwen:0.5b` 26 | Qwen 1.5 is a family of LLMs developed by Alibaba Cloud, ranging from 0.5B to 110B parameters. 27 | 28 | This model was selected for its low memory footprint and strong performance in local RAG pipelines. Despite its compact size (0.5B parameters), it supports a 32K context window, making it suitable for reasoning across multi-page documents. 29 | 30 | **Note:** `qwen:0.5b` is a general-purpose LLM and has **not** been fine-tuned for retrieval-augmented generation (RAG) or grounded QA. While effective in controlled settings, it may hallucinate if the context is weak or ambiguous. 31 | 32 | | Property | Value | 33 | |-----------------|------------------| 34 | | Size | 395 MB | 35 | | Context Length | 32K tokens | 36 | | Input Type | Text | 37 | | Developers | Alibaba Cloud | 38 | 39 | See: [https://ollama.com/library/qwen](https://ollama.com/library/qwen) 40 | 41 | ## Acknowledgements 42 | 43 | This project adapts and extends the tutorial by Chaitanya Rahalkar 44 | Source: [freeCodeCamp article](https://www.freecodecamp.org/news/build-a-local-ai/) 45 | GitHub: [chaitanyarahalkar](https://github.com/chaitanyarahalkar) 46 | 47 | Adapted and maintained by Nina Fernanda Durán 48 | GitHub: [HeyNina101](https://github.com/HeyNina101) 49 | -------------------------------------------------------------------------------- /agent-ollama-basic/agent_local.py: -------------------------------------------------------------------------------- 1 | # agent_local.py 2 | import os 3 | from dotenv import load_dotenv 4 | from langchain.agents import tool 5 | import datetime 6 | from langchain_ollama import ChatOllama 7 | from langchain.prompts import ChatPromptTemplate 8 | from langchain.prompts.chat import SystemMessagePromptTemplate, HumanMessagePromptTemplate 9 | from langchain.agents import create_tool_calling_agent 10 | from langchain.agents import AgentExecutor 11 | 12 | load_dotenv() # Optional 13 | MODEL = "granite3-dense:2b" 14 | 15 | @tool 16 | def get_current_datetime(format: str = "%Y-%m-%d %H:%M:%S") -> str: 17 | """ 18 | Returns the current date and time, formatted according to the provided Python strftime format string. 19 | Use this tool whenever the user asks for the current date, time, or both. 20 | Example format strings: '%Y-%m-%d' for date, '%H:%M:%S' for time. 21 | If no format is specified, defaults to '%Y-%m-%d %H:%M:%S'. 22 | """ 23 | try: 24 | return datetime.datetime.now().strftime(format) 25 | except Exception as e: 26 | return f"Error formatting date/time: {e}" 27 | 28 | # List of tools the agent can use 29 | tools = [get_current_datetime] 30 | print("Custom tool defined.") 31 | 32 | def get_agent_llm(model_name=MODEL, temperature=0): 33 | """Initializes the ChatOllama model for the agent.""" 34 | # Ensure Ollama server is running (ollama serve) 35 | llm = ChatOllama( 36 | model=model_name, 37 | temperature=temperature # Lower temperature for more predictable tool use 38 | # Consider increasing num_ctx if expecting long conversations or complex reasoning 39 | # num_ctx=8192 40 | ) 41 | print(f"Initialized ChatOllama agent LLM with model: {model_name}") 42 | return llm 43 | 44 | # def get_agent_prompt(prompt_hub_name="hwchase17/openai-tools-agent"): 45 | # """Pulls the agent prompt template from LangChain Hub.""" 46 | # # This prompt is designed for OpenAI but often works well with other tool-calling models. 47 | # # Alternatively, define a custom ChatPromptTemplate. 48 | # prompt = hub.pull(prompt_hub_name) 49 | # print(f"Pulled agent prompt from Hub: {prompt_hub_name}") 50 | # # print("Prompt Structure:") 51 | # # prompt.pretty_print() # Uncomment to see the prompt structure 52 | # return prompt 53 | 54 | 55 | def get_agent_prompt(): 56 | """Custom ReAct-style prompt with required agent_scratchpad variable.""" 57 | system_msg = SystemMessagePromptTemplate.from_template( 58 | "You are an AI agent that can use external tools to answer user questions.\n\n" 59 | "Available tool:\n" 60 | "- `get_current_datetime`: Returns the current date or time in a specified format. Use only when the user explicitly asks for the current date or time.\n\n" 61 | "Only use the tools listed above. If no tool is needed, respond immediately after your Thought with a Final Answer. Do not invent tools or actions.\n\n" 62 | "Follow this exact format:\n" 63 | "Thought: your reasoning\n" 64 | "Action: the action to take\n" 65 | "Action Input: the input to the action\n" 66 | "Observation: the result of the action\n" 67 | "... (repeat as needed)\n" 68 | "Final Answer: the final response to the user" 69 | ) 70 | 71 | human_msg = HumanMessagePromptTemplate.from_template("{input}\n\n{agent_scratchpad}") 72 | 73 | return ChatPromptTemplate.from_messages([system_msg, human_msg]) 74 | 75 | 76 | def build_agent(llm, tools, prompt): 77 | """Builds the tool-calling agent runnable.""" 78 | agent = create_tool_calling_agent(llm, tools, prompt) 79 | print("Agent runnable created.") 80 | return agent 81 | 82 | def create_agent_executor(agent, tools): 83 | """Creates the agent executor.""" 84 | agent_executor = AgentExecutor( 85 | agent=agent, 86 | tools=tools, 87 | verbose=True # Set to True to see agent thoughts and tool calls 88 | ) 89 | print("Agent executor created.") 90 | return agent_executor 91 | 92 | 93 | def run_agent(executor, user_input): 94 | """Runs the agent executor with the given input.""" 95 | print("\nInvoking agent...") 96 | print(f"Input: {user_input}") 97 | response = executor.invoke({"input": user_input}) 98 | print("\nAgent Response:") 99 | print(response['output']) 100 | 101 | # --- Main Execution --- 102 | if __name__ == "__main__": 103 | # 1. Define Tools (already done above) 104 | 105 | # 2. Get Agent LLM 106 | agent_llm = get_agent_llm(model_name=MODEL) # Use the chosen Qwen 3 model 107 | 108 | # 3. Get Agent Prompt 109 | agent_prompt = get_agent_prompt() 110 | 111 | # 4. Build Agent Runnable 112 | agent_runnable = build_agent(agent_llm, tools, agent_prompt) 113 | 114 | # 5. Create Agent Executor 115 | agent_executor = create_agent_executor(agent_runnable, tools) 116 | 117 | # 6. Run Agent wiht 3 task 118 | run_agent(agent_executor, "What is the current date?") 119 | run_agent(agent_executor, "What time is it right now? Use HH:MM format.") 120 | run_agent(agent_executor, "Tell me a joke.") # Should not use the tool -------------------------------------------------------------------------------- /rag-ollama-basic/rag_local.py: -------------------------------------------------------------------------------- 1 | # rag_local.py 2 | import os 3 | from dotenv import load_dotenv 4 | from langchain_community.document_loaders import PyPDFLoader # Or UnstructuredPDFLoader / PyPDFLoader 5 | from langchain_text_splitters import RecursiveCharacterTextSplitter # For splitting text into chunks 6 | from langchain_ollama import OllamaEmbeddings # Ollama embeddings for text representation 7 | from langchain_community.vectorstores import Chroma # database for storing embeddings 8 | from langchain_ollama import ChatOllama 9 | from langchain_core.prompts import ChatPromptTemplate 10 | from langchain_core.runnables import RunnablePassthrough 11 | from langchain_core.output_parsers import StrOutputParser 12 | 13 | load_dotenv() # Optional: Loads environment variables from.env file 14 | 15 | DATA_PATH = "data/" 16 | PDF_FILENAME = "mydocument.pdf" 17 | CHROMA_PATH = "chroma_db" # Directory to store ChromaDB data 18 | MODEL = "qwen:0.5b" 19 | 20 | def load_documents(): 21 | """Loads documents from the specified data path.""" 22 | pdf_path = os.path.join(DATA_PATH, PDF_FILENAME) 23 | loader = PyPDFLoader(pdf_path) 24 | # loader = UnstructuredPDFLoader(pdf_path) # Alternative 25 | documents = loader.load() 26 | print(f"Loaded {len(documents)} page(s) from {pdf_path}") 27 | return documents 28 | 29 | def split_documents(documents): 30 | """Splits documents into smaller chunks.""" 31 | text_splitter = RecursiveCharacterTextSplitter( 32 | chunk_size=1000, 33 | chunk_overlap=200, 34 | length_function=len, 35 | is_separator_regex=False, 36 | ) 37 | all_splits = text_splitter.split_documents(documents) 38 | print(f"Split into {len(all_splits)} chunks") 39 | return all_splits 40 | 41 | def get_embedding_function(model_name="nomic-embed-text"): 42 | """Initializes the Ollama embedding function.""" 43 | # Ensure Ollama server is running (ollama serve) 44 | embeddings = OllamaEmbeddings(model=model_name) 45 | print(f"Initialized Ollama embeddings with model: {model_name}") 46 | return embeddings 47 | 48 | def get_vector_store(embedding_function, persist_directory=CHROMA_PATH): 49 | """Initializes or loads the Chroma vector store.""" 50 | vectorstore = Chroma( 51 | persist_directory=persist_directory, 52 | embedding_function=embedding_function 53 | ) 54 | print(f"Vector store initialized/loaded from: {persist_directory}") 55 | return vectorstore 56 | 57 | def index_documents(chunks, embedding_function, persist_directory=CHROMA_PATH): 58 | """Indexes document chunks into the Chroma vector store.""" 59 | print(f"Indexing {len(chunks)} chunks...") 60 | # Use from_documents for initial creation. 61 | # This will overwrite existing data if the directory exists but isn't a valid Chroma DB. 62 | # For incremental updates, initialize Chroma first and use vectorstore.add_documents(). 63 | vectorstore = Chroma.from_documents( 64 | documents=chunks, 65 | embedding=embedding_function, 66 | persist_directory=persist_directory 67 | ) 68 | vectorstore.persist() # Ensure data is saved 69 | print(f"Indexing complete. Data saved to: {persist_directory}") 70 | return vectorstore 71 | 72 | def create_rag_chain(vector_store, llm_model_name=MODEL, context_window=8192): 73 | """Creates the RAG chain.""" 74 | # Initialize the LLM 75 | llm = ChatOllama( 76 | model=llm_model_name, 77 | temperature=0, # Lower temperature for more factual RAG answers 78 | num_ctx=context_window # IMPORTANT: Set context window size 79 | ) 80 | print(f"Initialized ChatOllama with model: {llm_model_name}, context window: {context_window}") 81 | 82 | # Create the retriever 83 | retriever = vector_store.as_retriever( 84 | search_type="similarity", # Or "mmr" 85 | search_kwargs={'k': 3} # Retrieve top 3 relevant chunks 86 | ) 87 | print("Retriever initialized.") 88 | 89 | # Define the prompt template 90 | template = """Answer the question based ONLY on the following context: {context} 91 | Question: {question} """ 92 | 93 | prompt = ChatPromptTemplate.from_template(template) 94 | print("Prompt template created.") 95 | 96 | # Define the RAG chain using LCEL 97 | rag_chain = ( 98 | {"context": retriever, "question": RunnablePassthrough()} 99 | | prompt 100 | | llm 101 | | StrOutputParser() 102 | ) 103 | print("RAG chain created.") 104 | return rag_chain 105 | 106 | def query_rag(chain, question): 107 | """Queries the RAG chain and prints the response.""" 108 | print("\nQuerying RAG chain...") 109 | print(f"Question: {question}") 110 | response = chain.invoke(question) 111 | print("\nResponse:") 112 | print(response) 113 | 114 | # --- Main Execution --- 115 | if __name__ == "__main__": 116 | # 1. Load Documents 117 | docs = load_documents() 118 | 119 | # 2. Split Documents 120 | chunks = split_documents(docs) 121 | 122 | # 3. Get Embedding Function 123 | embedding_function = get_embedding_function() # Using Ollama nomic-embed-text 124 | 125 | # 4. Index Documents (Only needs to be done once per document set) 126 | # Check if DB exists, if not, index. For simplicity, we might re-index here. 127 | # A more robust approach would check if indexing is needed. 128 | print("Attempting to index documents...") 129 | vector_store = index_documents(chunks, embedding_function) 130 | # To load existing DB instead: 131 | # vector_store = get_vector_store(embedding_function) 132 | 133 | # 5. Create RAG Chain 134 | rag_chain = create_rag_chain(vector_store, llm_model_name=MODEL) # if has RAM < 8GB Use the chosen Qwen 3 model 135 | 136 | # 6. Query 137 | query_question = "What is the main topic of the document?" # Replace with a specific question 138 | query_rag(rag_chain, query_question) 139 | 140 | query_question_2 = "Summarize the introduction section." # Another example 141 | query_rag(rag_chain, query_question_2) --------------------------------------------------------------------------------