├── utils ├── __init__.py └── api_client.py ├── storage ├── graph_store.json ├── index_store.json └── docstore.json ├── audio └── donda.mp3 ├── .gitignore ├── assets ├── llmseries.png └── youtube.png ├── 00_start.py ├── newspieces.json ├── 03_db.py ├── 04_csv.py ├── 19_agents_handsoff.py ├── 14_streamlit.py ├── 13_caching_memory.py ├── 08_openai.py ├── 05_hf.py ├── 12_guidance_roles.py ├── LICENSE ├── 02_llama.py ├── 06_team.py ├── 17_embeddings.py ├── 01_qna.py ├── smol-prompt └── prompt.md ├── 02b_llama_chroma.py ├── 15_sql.py ├── 13_caching_sqlite.py ├── 12_guidance_syntax.py ├── 16_repl.py ├── 20_agents_tooluse.py ├── book ├── wande002.html.txt ├── wande003.html.txt ├── wande004.html.txt └── wanderer.html.txt ├── 10_journal.py ├── requirements.txt ├── workshop ├── ai-search-engine.py └── Generative_AI_Template_02.ipynb ├── 18_chroma.py ├── agent_outputs └── 3_agents_research_notes.txt ├── 22_agents_judge_critic.py ├── 23_agents_parallelization.py ├── 07_custom.py ├── 09_pinecone.py ├── 24_agents_guardrails.py ├── 21_agents_deterministic.py ├── news ├── summary.txt └── result.txt ├── 11_worldbuilding.py ├── README.md └── 07_custom_opt.json /utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/graph_store.json: -------------------------------------------------------------------------------- 1 | {"graph_dict": {}} -------------------------------------------------------------------------------- /audio/donda.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onlyphantom/llm-python/HEAD/audio/donda.mp3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | notes.md 3 | academy/ 4 | *_x.py 5 | .vscode/ 6 | *.db 7 | streamlit_agent/ -------------------------------------------------------------------------------- /assets/llmseries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onlyphantom/llm-python/HEAD/assets/llmseries.png -------------------------------------------------------------------------------- /assets/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onlyphantom/llm-python/HEAD/assets/youtube.png -------------------------------------------------------------------------------- /00_start.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from langchain.embeddings import OpenAIEmbeddings 3 | 4 | load_dotenv() 5 | embeddings = OpenAIEmbeddings() 6 | text = "Algoritma is a data science school based in Indonesia and Supertype is a data science consultancy with a distributed team of data and analytics engineers." 7 | doc_embeddings = embeddings.embed_documents([text]) 8 | 9 | # OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') 10 | # print(OPENAI_API_KEY) 11 | print(doc_embeddings) 12 | -------------------------------------------------------------------------------- /newspieces.json: -------------------------------------------------------------------------------- 1 | {"index_struct": {"__type__": "chroma", "__data__": {"index_id": "90246c0b-4d00-4e43-888b-a02dff903054", "summary": null, "nodes_dict": {}, "doc_id_dict": {}, "embeddings_dict": {}}}, "docstore": {"docs": {}, "ref_doc_info": {"2554d06a-ae2c-4b05-920b-d6bddeaab2e7": {"doc_hash": "1abedf1e096b34455d8e4cd5b65b87329a34b30e8ce3c6fabd000241522e5081"}, "3e5dbe9f-843c-494c-a027-d3984c1a6eb5": {"doc_hash": "e55b12ed31aceb1a6b09488c2686b3290405c6d682d19a0c3719f1502681f416"}}}, "vector_store": {}} -------------------------------------------------------------------------------- /utils/api_client.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import json 4 | import requests 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | SECTORS_API_KEY = os.getenv("SECTORS_API_KEY") 9 | 10 | headers = {"Authorization": SECTORS_API_KEY} 11 | 12 | def retrieve_from_endpoint(url: str) -> dict: 13 | 14 | try: 15 | response = requests.get(url, headers=headers) 16 | response.raise_for_status() 17 | data = response.json() 18 | except requests.exceptions.HTTPError as err: 19 | raise SystemExit(err) 20 | return json.dumps(data) 21 | -------------------------------------------------------------------------------- /03_db.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from langchain import OpenAI, SQLDatabase, SQLDatabaseChain 3 | 4 | load_dotenv() 5 | 6 | # dburi = os.getenv("DATABASE_URL") 7 | dburi = "sqlite:///academy/academy.db" 8 | db = SQLDatabase.from_uri(dburi) 9 | llm = OpenAI(temperature=0) 10 | 11 | db_chain = SQLDatabaseChain(llm=llm, database=db, verbose=True) 12 | 13 | db_chain.run("How many rows is in the responses table of this db?") 14 | db_chain.run("Describe the responses table") 15 | db_chain.run("What are the top 3 countries where these responses are from?") 16 | db_chain.run("Give me a summary of how these customers come to hear about us. \ 17 | What is the most common way they hear about us?") 18 | -------------------------------------------------------------------------------- /04_csv.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from langchain import OpenAI 3 | from langchain.document_loaders.csv_loader import CSVLoader 4 | 5 | load_dotenv() 6 | 7 | filepath = "academy/academy.csv" 8 | loader = CSVLoader(filepath) 9 | data = loader.load() 10 | print(data) 11 | 12 | llm = OpenAI(temperature=0) 13 | 14 | from langchain.agents import create_csv_agent 15 | agent = create_csv_agent(llm, filepath, verbose=True) 16 | agent.run("What percentage of the respondents are students versus professionals?") 17 | agent.run("List the top 3 devices that the respondents use to submit their responses") 18 | agent.run("Consider iOS and Android as mobile devices. What is the percentage of respondents that discovered us through social media submitting this from a mobile device?") 19 | -------------------------------------------------------------------------------- /storage/index_store.json: -------------------------------------------------------------------------------- 1 | {"index_store/data": {"d5b02ff0-85e9-4fe6-a7e6-d2bf839e0bf6": {"__type__": "vector_store", "__data__": "{\"index_id\": \"d5b02ff0-85e9-4fe6-a7e6-d2bf839e0bf6\", \"summary\": null, \"nodes_dict\": {\"28c199fd-b053-4c73-a716-755b0c62292f\": \"28c199fd-b053-4c73-a716-755b0c62292f\", \"af0cde9f-ecdc-46de-9984-c58d0aae6321\": \"af0cde9f-ecdc-46de-9984-c58d0aae6321\", \"f3ef8d0e-62e7-4e9c-a897-483fba83d4c9\": \"f3ef8d0e-62e7-4e9c-a897-483fba83d4c9\", \"46972006-a29d-4a6c-ad98-f8ed165a396d\": \"46972006-a29d-4a6c-ad98-f8ed165a396d\", \"88b2d399-9b9c-4f58-bf81-a8a1d6bfded4\": \"88b2d399-9b9c-4f58-bf81-a8a1d6bfded4\", \"76a9a231-6854-4126-bb78-047b636fd3cb\": \"76a9a231-6854-4126-bb78-047b636fd3cb\", \"8c35b469-89ea-4e11-9b0c-abe7fd4f33ab\": \"8c35b469-89ea-4e11-9b0c-abe7fd4f33ab\", \"5a8abd59-076b-4570-a2ea-3cac8fd9b03e\": \"5a8abd59-076b-4570-a2ea-3cac8fd9b03e\", \"bd48504d-ce9c-4128-a892-e399cc2d7306\": \"bd48504d-ce9c-4128-a892-e399cc2d7306\"}, \"doc_id_dict\": {}, \"embeddings_dict\": {}}"}}} -------------------------------------------------------------------------------- /19_agents_handsoff.py: -------------------------------------------------------------------------------- 1 | # import os 2 | from agents import Agent, Runner 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | 7 | german_agent = Agent( 8 | name="German Assistant", 9 | instructions="Always respond in German. Be polite and concise.", 10 | ) 11 | 12 | english_agent = Agent( 13 | name="English Assistant", 14 | instructions="Always respond in English.", 15 | ) 16 | 17 | customer_service_manager = Agent( 18 | name="Customer Service Manager", 19 | instructions="Handoff to the appropriate agent based on the language of the request.", 20 | handoffs=[german_agent, english_agent], 21 | ) 22 | 23 | async def main(): 24 | query = "Hallo, ich habe ein Problem und muss mit dem Manager sprechen" 25 | result = await Runner.run( 26 | customer_service_manager, 27 | query 28 | ) 29 | print(f"👧: {query}") 30 | print(f"🤖: {result.final_output}") 31 | 32 | if __name__ == "__main__": 33 | import asyncio 34 | asyncio.run(main()) 35 | -------------------------------------------------------------------------------- /14_streamlit.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from langchain.llms import OpenAI 4 | from langchain.agents import AgentType, initialize_agent, load_tools 5 | from langchain.callbacks import StreamlitCallbackHandler 6 | import streamlit as st 7 | 8 | load_dotenv() 9 | 10 | llm = OpenAI(temperature=0, streaming=True, openai_api_key=os.getenv("OPENAI_API_KEY")) 11 | tools = load_tools(["ddg-search"]) 12 | agent = initialize_agent( 13 | tools, 14 | llm, 15 | agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, 16 | # verbose=True 17 | ) 18 | 19 | # try: "what are the names of the kids of the 44th president of america" 20 | # try: "top 3 largest shareholders of nvidia" 21 | if prompt := st.chat_input(): 22 | st.chat_message("user").write(prompt) 23 | with st.chat_message("assistant"): 24 | st.write("🧠 thinking...") 25 | st_callback = StreamlitCallbackHandler(st.container()) 26 | response = agent.run(prompt, callbacks=[st_callback]) 27 | st.write(response) 28 | -------------------------------------------------------------------------------- /13_caching_memory.py: -------------------------------------------------------------------------------- 1 | import time 2 | from dotenv import load_dotenv 3 | import langchain 4 | from langchain.llms import OpenAI 5 | from langchain.callbacks import get_openai_callback 6 | from langchain.cache import InMemoryCache 7 | 8 | load_dotenv() 9 | 10 | # to make caching obvious, we use a slow model 11 | llm = OpenAI(model_name="text-davinci-002") 12 | 13 | langchain.llm_cache = InMemoryCache() 14 | 15 | with get_openai_callback() as cb: 16 | start = time.time() 17 | result = llm("What doesn't fall far from the tree?") 18 | print(result) 19 | end = time.time() 20 | print("--- cb") 21 | print(str(cb) + f" ({end - start:.2f} seconds)") 22 | 23 | with get_openai_callback() as cb2: 24 | start = time.time() 25 | result2 = llm("What doesn't fall far from the tree?") 26 | result3 = llm("What doesn't fall far from the tree?") 27 | end = time.time() 28 | print(result2) 29 | print(result3) 30 | print("--- cb2") 31 | print(str(cb2) + f" ({end - start:.2f} seconds)") 32 | -------------------------------------------------------------------------------- /08_openai.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | 3 | load_dotenv() 4 | 5 | import openai 6 | import webbrowser 7 | 8 | 9 | # list all models 10 | models = openai.Model.list() 11 | print(models.data[0].root) 12 | print(mod.root for mod in models.data) 13 | 14 | # # create our completion 15 | # completion = openai.Completion.create(model="ada", prompt="Bill Gates is a") 16 | # print(completion.choices[0].text) 17 | 18 | # image_gen = openai.Image.create(prompt="Zwei Hunde spielen unter einem Baum, cartoon", 19 | # n=2, 20 | # size="512x512" 21 | # ) 22 | # # imgurl1 = image_gen.data[0].url 23 | # # imgurl2 = image_gen.data[1].url 24 | # # webbrowser.open(imgurl) 25 | # for img in image_gen.data: 26 | # webbrowser.open_new_tab(img.url) 27 | 28 | 29 | # # Gwendolyn Brooks Writers' Conference - Keynote Address: Dr. Donda West 30 | # audio = open("audio/donda.mp3", "rb") 31 | # transcript = openai.Audio.transcribe("whisper-1", audio) 32 | # print(transcript) 33 | -------------------------------------------------------------------------------- /05_hf.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from langchain import HuggingFaceHub, LLMChain 3 | from langchain.prompts import PromptTemplate 4 | 5 | load_dotenv() 6 | 7 | # hub_llm = HuggingFaceHub(repo_id="mrm8488/t5-base-finetuned-wikiSQL") 8 | 9 | # prompt = PromptTemplate( 10 | # input_variables=["question"], 11 | # template="Translate English to SQL: {question}" 12 | # ) 13 | 14 | # hub_chain = LLMChain(prompt=prompt, llm=hub_llm, verbose=True) 15 | # print(hub_chain.run("What is the average age of the respondents using a mobile device?")) 16 | 17 | 18 | # second example below: 19 | hub_llm = HuggingFaceHub( 20 | repo_id='gpt2', 21 | model_kwargs={'temperature': 0.7, 'max_length': 100} 22 | ) 23 | 24 | prompt = PromptTemplate( 25 | input_variables=["profession"], 26 | template="You had one job 😡! You're the {profession} and you didn't have to be sarcastic" 27 | ) 28 | 29 | hub_chain = LLMChain(prompt=prompt, llm=hub_llm, verbose=True) 30 | print(hub_chain.run("customer service agent")) 31 | print(hub_chain.run("politician")) 32 | print(hub_chain.run("Fintech CEO")) 33 | print(hub_chain.run("insurance agent")) -------------------------------------------------------------------------------- /12_guidance_roles.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import guidance 3 | 4 | load_dotenv() 5 | 6 | chat = guidance.llms.OpenAI("gpt-3.5-turbo") 7 | guidance.llm = chat 8 | 9 | program = guidance( 10 | """ 11 | {{#system}}You are a CS Professor teaching {{os}} systems administration to your students.{{/system}} 12 | 13 | {{#user~}} 14 | What are some of the most common commands used in the {{os}} operating system? Provide a one-liner description. 15 | List the commands and their descriptions one per line. Number them starting from 1. 16 | {{~/user}} 17 | 18 | {{#assistant~}} 19 | {{gen 'commands' max_tokens=100}} 20 | {{~/assistant}} 21 | 22 | {{#user~}} 23 | Which among these commands are beginners most likely to get wrong? Explain why the command might be confusing. Show example code to illustrate your point. 24 | {{~/user}} 25 | 26 | {{#assistant~}} 27 | {{gen 'confusing_commands' max_tokens=100}} 28 | {{~/assistant}} 29 | """, 30 | llm=chat, 31 | ) 32 | 33 | 34 | result = program(os="Linux") 35 | 36 | print(result["commands"]) 37 | print("===") 38 | print(result) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Samuel Chan 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 | -------------------------------------------------------------------------------- /02_llama.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | load_dotenv() 3 | 4 | from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader, load_index_from_storage, StorageContext 5 | from llama_index.storage.docstore import SimpleDocumentStore 6 | from llama_index.vector_stores import SimpleVectorStore 7 | from llama_index.storage.index_store import SimpleIndexStore 8 | from llama_index.graph_stores import SimpleGraphStore 9 | 10 | documents = SimpleDirectoryReader('news').load_data() 11 | 12 | index = GPTVectorStoreIndex.from_documents(documents) 13 | 14 | # save to disk 15 | index.storage_context.persist() 16 | 17 | # load from disk 18 | storage_context = StorageContext( 19 | docstore=SimpleDocumentStore.from_persist_dir('storage'), 20 | vector_store=SimpleVectorStore.from_persist_dir('storage'), 21 | index_store=SimpleIndexStore.from_persist_dir('storage'), 22 | graph_store=SimpleGraphStore.from_persist_dir('storage') 23 | ) 24 | index = load_index_from_storage(storage_context) 25 | 26 | query_engine = index.as_query_engine() 27 | r = query_engine.query("Who are the main exporters of Coal to China? What is the role of Indonesia in this?") 28 | print(r) 29 | -------------------------------------------------------------------------------- /06_team.py: -------------------------------------------------------------------------------- 1 | 2 | from dotenv import load_dotenv 3 | load_dotenv() 4 | 5 | from llama_index import GPTVectorStoreIndex, TrafilaturaWebReader 6 | import chromadb 7 | 8 | 9 | def create_embedding_store(name): 10 | chroma_client = chromadb.Client() 11 | return chroma_client.create_collection(name) 12 | 13 | def query_pages(collection, urls, questions): 14 | docs = TrafilaturaWebReader().load_data(urls) 15 | index = GPTVectorStoreIndex.from_documents(docs, chroma_collection=collection) 16 | query_engine = index.as_query_engine() 17 | for question in questions: 18 | print(f"Question: {question} \n") 19 | print(f"Answer: {query_engine.query(question)}") 20 | 21 | if __name__ == "__main__": 22 | url_list = ["https://supertype.ai", "https://supertype.ai/about-us"] 23 | questions = [ 24 | "Who are the members of Supertype.ai", 25 | "What problems are they trying to solve?", 26 | "What are the important values at the company?" 27 | ] 28 | 29 | collection = create_embedding_store("supertype") 30 | 31 | query_pages( 32 | collection, 33 | url_list, 34 | questions 35 | ) 36 | -------------------------------------------------------------------------------- /17_embeddings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Obtain data from https://sectors.app 3 | Accompanying course material: https://sectors.app/bulletin/ai-search 4 | """ 5 | 6 | import numpy as np 7 | from dotenv import load_dotenv 8 | 9 | load_dotenv() 10 | 11 | from langchain_openai import OpenAIEmbeddings 12 | embed = OpenAIEmbeddings() 13 | 14 | input_text = "supertype financial statements?" 15 | input_docs = ["financial_algoritma", 16 | "annual_report_algoritma", 17 | "financial_supertype", 18 | "agm_supertype", 19 | "egm_supertype", 20 | "financial_hypergrowth", 21 | "annual_report_hypergrowth", 22 | "agm_sectors", 23 | "financial_statements_template" 24 | ] 25 | 26 | query = embed.embed_query(input_text) 27 | docs = embed.embed_documents(input_docs) 28 | 29 | def cosine_similarity(vec1, vec2): 30 | dot_product = np.dot(vec1, vec2) 31 | norm_vec1 = np.linalg.norm(vec1) 32 | norm_vec2 = np.linalg.norm(vec2) 33 | return dot_product / (norm_vec1 * norm_vec2) 34 | 35 | similarities = [cosine_similarity(query, doc) for doc in docs] 36 | 37 | for i, sim in enumerate(similarities): 38 | print(f"Cosine Similarity with doc {i}: {sim}") 39 | 40 | most_similar_index = np.argmax(similarities) 41 | print(f"Document: {input_docs[most_similar_index]}") 42 | -------------------------------------------------------------------------------- /01_qna.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | 3 | from langchain.document_loaders import DirectoryLoader, TextLoader 4 | from langchain.text_splitter import CharacterTextSplitter 5 | from langchain.embeddings import OpenAIEmbeddings 6 | from langchain.vectorstores import Chroma 7 | from langchain.chains import RetrievalQA 8 | from langchain import OpenAI 9 | 10 | load_dotenv() 11 | embeddings = OpenAIEmbeddings() 12 | 13 | # loader = TextLoader('news/summary.txt') 14 | loader = DirectoryLoader('news', glob="**/*.txt") 15 | 16 | documents = loader.load() 17 | print(len(documents)) 18 | text_splitter = CharacterTextSplitter(chunk_size=2500, chunk_overlap=0) 19 | texts = text_splitter.split_documents(documents) 20 | # print(texts) 21 | 22 | docsearch = Chroma.from_documents(texts, embeddings) 23 | qa = RetrievalQA.from_chain_type( 24 | llm=OpenAI(), 25 | chain_type="stuff", 26 | retriever=docsearch.as_retriever() 27 | ) 28 | 29 | def query(q): 30 | print("Query: ", q) 31 | print("Answer: ", qa.run(q)) 32 | 33 | query("What are the effects of legislations surrounding emissions on the Australia coal market?") 34 | query("What are China's plans with renewable energy?") 35 | query("Is there an export ban on Coal in Indonesia? Why?") 36 | query("Who are the main exporters of Coal to China? What is the role of Indonesia in this?") -------------------------------------------------------------------------------- /smol-prompt/prompt.md: -------------------------------------------------------------------------------- 1 | A create-react-app that is modern, elegant, mobile-responsive and styled with bootstrap css, offering a simple form to RSVP to a Christmas party. The project is started through npm start so make sure theres a corresponding package.json file. This app has 3 sections. 2 | 3 | #### 1: The hero header: RSVP Form 4 | Form has following fields: first name, last name, checkbox indicating a +1 that the guest would like to bring to the party. All of the form elements are styled with bootstrap css. This section has a low-opacity background image of a christmas tree sourced from unsplash api or similar. 5 | 6 | When the user hits submit, the form payload is written to a local json file titled data.json. 7 | 8 | #### 2: Attending List (table) 9 | Below the form, render a beautiful list of all the guests who have RSVP'd, with their first name, last name, and whether they are bringing a +1. The table is generated from the data.json file. 10 | This table should be sortable by first name and last name, and uses bootstrap for styling. 11 | 12 | #### 3: Costume Contest and Gift Exchange 13 | Pulls a random pokemon from the pokemon api and suggest that the user dress up as that pokemon for the costume contest. Also, randomly assign the user a gift exchange partner from the list of guests who have RSVP'd in data.json. 14 | 15 | Make sure the app includes the links to bootstrap cdn and jquery cdn in the index.html file. -------------------------------------------------------------------------------- /02b_llama_chroma.py: -------------------------------------------------------------------------------- 1 | # load .env first before importing llama_index 2 | from dotenv import load_dotenv 3 | load_dotenv() 4 | 5 | from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader 6 | from llama_index.storage import StorageContext 7 | from llama_index.vector_stores import ChromaVectorStore 8 | import chromadb 9 | 10 | # https://docs.trychroma.com/embeddings 11 | # create a Chroma vector store, by default operating purely in-memory 12 | chroma_client = chromadb.Client() 13 | 14 | # create a collection 15 | chroma_collection = chroma_client.create_collection("newspieces") 16 | # https://docs.trychroma.com/api-reference 17 | print(chroma_collection.count()) 18 | 19 | documents = SimpleDirectoryReader('news').load_data() 20 | 21 | vector_store = ChromaVectorStore(chroma_collection) 22 | storage_context = StorageContext.from_defaults(vector_store=vector_store) 23 | index = GPTVectorStoreIndex.from_documents(documents, storage_context=storage_context) 24 | print(chroma_collection.count()) 25 | print(chroma_collection.get()['documents']) 26 | print(chroma_collection.get()['metadatas']) 27 | 28 | index.storage_context.persist() 29 | 30 | # During query time, the index uses Chroma to query for the top k 31 | # most similar nodes, and synthesizes an answer from the retrieved nodes. 32 | 33 | query_engine = index.as_query_engine() 34 | r = query_engine.query("Who are the main exporters of Coal to China? What is the role of Indonesia in this?") 35 | print(r) 36 | -------------------------------------------------------------------------------- /15_sql.py: -------------------------------------------------------------------------------- 1 | """ 2 | Obtain data from https://sectors.app 3 | Accompanying course material: https://sectors.app/bulletin/ai-search 4 | """ 5 | 6 | import pandas as pd 7 | from langchain_community.utilities import SQLDatabase 8 | from sqlalchemy import create_engine 9 | from langchain_groq import ChatGroq 10 | from langchain_community.agent_toolkits import create_sql_agent 11 | import os 12 | # from langchain_deepseek import ChatDeepSeek 13 | 14 | if not os.path.exists('industry.db'): 15 | print("Creating industry.db") 16 | df = pd.read_csv('./datasets/industry-leaders-full.csv') 17 | engine = create_engine('sqlite:///industry.db') 18 | df.to_sql("industry", engine, index=False, if_exists='replace') 19 | 20 | else: 21 | # connect to the existing database, dont create 22 | engine = create_engine('sqlite:///industry.db') 23 | 24 | db = SQLDatabase(engine=engine) 25 | 26 | 27 | print(db.get_usable_table_names()) 28 | 29 | # query = "SELECT * FROM industry WHERE sub_industry LIKE '%banks%'" 30 | query2 = "SELECT * FROM industry WHERE total_market_cap > 1e14" 31 | print(db.run(query2)) 32 | 33 | llm = ChatGroq( 34 | model_name="llama3-70b-8192" 35 | ) 36 | 37 | # llm = ChatDeepSeek( 38 | # model="deepseek-chat" 39 | # ) 40 | 41 | agent_executor = create_sql_agent(llm, db=db, agent_type="tool-calling", verbose=True) 42 | agent_executor.invoke({ 43 | "input": "what are the top market cap gainers for companies in the coal industry? Return in markdown table." 44 | }) -------------------------------------------------------------------------------- /13_caching_sqlite.py: -------------------------------------------------------------------------------- 1 | import time 2 | from dotenv import load_dotenv 3 | import langchain 4 | from langchain.llms import OpenAI 5 | from langchain.callbacks import get_openai_callback 6 | 7 | from langchain.text_splitter import CharacterTextSplitter 8 | from langchain.docstore.document import Document 9 | from langchain.cache import SQLiteCache 10 | from langchain.chains.summarize import load_summarize_chain 11 | 12 | # add this to .gitignore if you don't want to commit the cache 13 | langchain.llm_cache = SQLiteCache(database_path=".langchain.db") 14 | 15 | load_dotenv() 16 | 17 | text_splitter = CharacterTextSplitter() 18 | llm = OpenAI(model_name="text-davinci-002") 19 | no_cache_llm = OpenAI(model_name="text-davinci-002", cache=False) 20 | 21 | with open("news/summary.txt") as f: 22 | news = f.read() 23 | 24 | texts = text_splitter.split_text(news) 25 | print(texts) 26 | 27 | docs = [Document(page_content=t) for t in texts[:3]] 28 | 29 | chain = load_summarize_chain(llm, chain_type="map_reduce", reduce_llm=no_cache_llm) 30 | 31 | with get_openai_callback() as cb: 32 | start = time.time() 33 | result = chain.run(docs) 34 | end = time.time() 35 | print("--- result1") 36 | print(result) 37 | print(str(cb) + f" ({end - start:.2f} seconds)") 38 | 39 | 40 | with get_openai_callback() as cb2: 41 | start = time.time() 42 | result = chain.run(docs) 43 | end = time.time() 44 | print("--- result2") 45 | print(result) 46 | print(str(cb2) + f" ({end - start:.2f} seconds)") 47 | -------------------------------------------------------------------------------- /12_guidance_syntax.py: -------------------------------------------------------------------------------- 1 | import random 2 | from dotenv import load_dotenv 3 | import guidance 4 | 5 | load_dotenv() 6 | 7 | # set the default language model that execute guidance programs 8 | guidance.llm = guidance.llms.OpenAI("text-davinci-003") 9 | # guidance.llm = guidance.llms.Transformers( 10 | # "stabilityai/stablelm-base-alpha-3b", device="cpu" 11 | # ) 12 | 13 | 14 | program = guidance( 15 | """What are the top ten most common commands used in the {{os}} operating system? Provide 16 | a one-liner description for each command. 17 | {{#block hidden=True}} 18 | A few example commands would be: 19 | [1]: pwd prints the current working directory 20 | [2]: mv moves the file and can be used to rename a file 21 | {{gen 'example' n=2 stop='"' max_tokens=20 temperature=0.8}} 22 | {{/block}} 23 | 24 | Here are the common commands: 25 | {{#geneach 'commands' num_iterations=10}} 26 | [{{@index}}]: "{{gen 'this' stop='"'}}", Description: "{{gen 'description' stop='"'}}" 27 | {{/geneach}} 28 | 29 | {{select 'flavor' options=quizflavor}} 30 | Explain the following commands for 🥇 {{randomPts}} points: 31 | {{#each (pickthree commands)}} 32 | {{@index+1}}. "{{this}}" 33 | {{/each}} 34 | 35 | Use the commands you listed above as input, generate a valid JSON object that maps each command to its description. 36 | "{ 37 | "{{os}}": [ 38 | {{#geneach 'commands' num_iterations=1}}{{#unless @first}},{{/unless}} 39 | "{{gen 'this'}}" 40 | {{/geneach}} 41 | """ 42 | ) 43 | 44 | quizflavor = [ 45 | "Quiz of the day!", 46 | "Test your knowledge!", 47 | "Here is a quiz!", 48 | "You think you know Unix?", 49 | ] 50 | 51 | result = program( 52 | os="Linux", 53 | pickthree=lambda x: list(set(x))[:3], 54 | randomPts=random.randint(1, 5), 55 | quizflavor=quizflavor, 56 | ) 57 | 58 | print(result["example"]) 59 | print("===") 60 | print(result["commands"]) 61 | print("===") 62 | print(result) 63 | -------------------------------------------------------------------------------- /16_repl.py: -------------------------------------------------------------------------------- 1 | """ 2 | Obtain data from https://sectors.app 3 | Accompanying course material: https://sectors.app/bulletin/ai-search 4 | 5 | Easily generalizes to multiple csvs, or sqlites since we just load 6 | them into one table 7 | 8 | - this is how GPT-4o-turbo cheat in the gun-to-knife-fight: 9 | - https://sectors.app/bulletin/deepseek 10 | 11 | Usage: python pre/repl.py 12 | - sub-industries in our df where the parent industry is coal-related 13 | - industries in our db with more than 45 total number of companies 14 | - top 5 largest industries in our db by sum of market cap 15 | """ 16 | import os 17 | 18 | from dotenv import load_dotenv 19 | import pandas as pd 20 | 21 | from langchain_groq import ChatGroq 22 | from langchain_core.prompts import ChatPromptTemplate 23 | from langchain_experimental.tools import PythonAstREPLTool 24 | from langchain_core.output_parsers.openai_tools import JsonOutputKeyToolsParser 25 | 26 | 27 | load_dotenv() 28 | SECTORS_API_KEY = os.getenv("SECTORS_API_KEY") 29 | 30 | df = pd.read_csv('./datasets/industry-leaders-full.csv') 31 | 32 | industry = df.loc[:, ['industry', 'sub_industry', 'total_market_cap', 'num_of_companies']] 33 | 34 | python_repl = PythonAstREPLTool(locals={"df": industry}) 35 | 36 | llm = ChatGroq( 37 | model_name="llama3-70b-8192" 38 | ) 39 | 40 | system_prompt = f""" Here is the output of `df.head().to_markdown()`: \\ 41 | \\`\\`\\` 42 | {industry.head().to_markdown()} 43 | \\`\\`\\` 44 | top_mcap_gainers are for Top Market Cap Gainers; Use Python code to answer whenever possible. Answer with pretty print and in tabular format as much as possible, in the neatest way possible. 45 | """ 46 | 47 | prompt = ChatPromptTemplate.from_messages( 48 | [ 49 | ("system", system_prompt), 50 | ("human", "{input}"), 51 | ] 52 | ) 53 | 54 | tools = [ 55 | python_repl 56 | ] 57 | 58 | llm_with_tools = llm.bind_tools(tools, tool_choice=python_repl.name) 59 | parser = JsonOutputKeyToolsParser(key_name=python_repl.name, first_tool_only=True) 60 | 61 | if __name__ == "__main__": 62 | query_string = input("🤖: Enter a query: ") 63 | # the last python_repl tool is the one that will be used to execute python code 64 | # remove it if you want to see what it will execute but not actually execute it 65 | # chain = prompt | llm_with_tools | parser 66 | chain = prompt | llm_with_tools | parser | python_repl 67 | response = chain.invoke({"input": query_string}) 68 | print(response) 69 | -------------------------------------------------------------------------------- /20_agents_tooluse.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import requests 4 | from typing import List 5 | from agents import Agent, Runner, function_tool 6 | from dotenv import load_dotenv 7 | 8 | load_dotenv() 9 | SECTORS_API_KEY = os.getenv("SECTORS_API_KEY") 10 | 11 | 12 | headers = {"Authorization": SECTORS_API_KEY} 13 | def retrieve_from_endpoint(url: str) -> dict: 14 | 15 | try: 16 | response = requests.get(url, headers=headers) 17 | response.raise_for_status() 18 | data = response.json() 19 | except requests.exceptions.HTTPError as err: 20 | raise SystemExit(err) 21 | return json.dumps(data) 22 | 23 | @function_tool 24 | def get_company_overview(ticker: str, country: str) -> str: 25 | """ 26 | Get company overview from Singapore Exchange (SGX) or Indonesia Exchange (IDX) 27 | """ 28 | assert country.lower() in ["indonesia", "singapore", "malaysia"], "Country must be either Indonesia, Singapore, or Malaysia" 29 | 30 | if(country.lower() == "indonesia"): 31 | url = f"https://api.sectors.app/v1/company/report/{ticker}/?sections=overview" 32 | if(country.lower() == "singapore"): 33 | url = f"https://api.sectors.app/v1/sgx/company/report/{ticker}/" 34 | if(country.lower() == "malaysia"): 35 | url = f"https://api.sectors.app/v1/klse/company/report/{ticker}/" 36 | 37 | try: 38 | return retrieve_from_endpoint(url) 39 | except Exception as e: 40 | print(f"Error occurred: {e}") 41 | return None 42 | 43 | 44 | @function_tool 45 | def get_top_companies_ranked(dimension: str) -> List[str]: 46 | """ 47 | Return a list of top companies (symbol) based on certain dimension (dividend yield, total dividend, revenue, earnings, market cap, PB ratio, PE ratio, or PS ratio) 48 | 49 | @param dimension: The dimension to rank the companies by, one of: dividend_yield, total_dividend, revenue, earnings, market_cap, pb, pe, ps 50 | @return: A list of top tickers in a given year based on certain classification 51 | """ 52 | 53 | url = f"https://api.sectors.app/v1/companies/top/?classifications={dimension}" 54 | 55 | return retrieve_from_endpoint(url) 56 | 57 | 58 | company_research_agent = Agent( 59 | name="company_research_agent", 60 | instructions="Research the company based on the ticker provided.", 61 | tools=[get_company_overview, get_top_companies_ranked], 62 | tool_use_behavior="run_llm_again" 63 | ) 64 | 65 | async def main(): 66 | query = "Tell me about the company listed on Singapore Exchange with ticker 'D05'." 67 | result = await Runner.run( 68 | company_research_agent, 69 | query 70 | ) 71 | print(f"👧: {query}") 72 | print(f"🤖: {result.final_output}") 73 | 74 | if __name__ == "__main__": 75 | import asyncio 76 | asyncio.run(main()) -------------------------------------------------------------------------------- /book/wande002.html.txt: -------------------------------------------------------------------------------- 1 | Vom Baum der Erkenntnis. – Wahrscheinlichkeit, aber keine Wahrheit: Freischeinlichkeit, aber keine Freiheit, – diese beiden Früchte sind es, derentwegen der Baum der Erkenntnis nicht mit dem Baum des Lebens verwechselt werden kann. 2 | Die Vernunft der Welt. – Daß die Welt nicht der Inbegriff einer ewigen Vernünftigkeit ist, läßt sich endgültig dadurch beweisen, daß jenes Stück Welt, welches wir kennen – ich meine unsre menschliche Vernunft –, nicht allzu vernünftig ist. Und wenn sie nicht allezeit und vollständig weise und rationell ist, so wird es die übrige Welt auch nicht sein; hier gilt der Schluß a minori ad majus, a parte ad totum, und zwar mit entscheidender Kraft. 3 | "Am Anfang war." – Die Entstehung verherrlichen – das ist der metaphysische Nachtrieb, welcher bei der Betrachtung der Historie wieder ausschlägt und durchaus meinen macht, am Anfang aller Dinge stehe das Wertvollste und Wesentlichste. 4 | Maß für den Wert der Wahrheit. – Für die Höhe der Berge ist die Mühsal ihrer Besteigung durchaus kein Maßstab. Und in der Wissenschaft soll es anders sein! – sagen uns einige, die für eingeweiht gelten wollen –, die Mühsal um die Wahrheit soll gerade über den Wert der Wahrheit entscheiden! Diese tolle Moral geht von dem Gedanken aus, daß die "Wahrheiten" eigentlich nichts weiter seien, als Turngerätschaften, an denen wir uns wacker müde zu arbeiten hätten, – eine Moral für Athleten und Festturner des Geistes. 5 | Sprachgebrauch und Wirklichkeit. – Es gibt eine erheuchelte Mißachtung aller der Dinge, welche tatsächlich die Menschen am wichtigsten nehmen, aller nächsten Dinge. Man sagt zum Beispiel "man ißt nur, um zu leben," – eine verfluchte Lüge, wie jene, welche von der Kindererzeugung als der eigentlichen Absicht aller Wollust redet. Umgekehrt ist die Hochschätzung der "wichtigsten Dinge" fast niemals ganz echt: die Priester und Metaphysiker haben uns zwar auf diesen Gebieten durchaus an einen heuchlerisch übertreibenden Sprachgebrauch gewöhnt, aber das Gefühl doch nicht umgestimmt, welches diese wichtigsten Dinge nicht so wichtig nimmt wie jene verachteten nächsten Dinge. – Eine leidige Folge dieser doppelten Heuchelei aber ist immerhin, daß man die nächsten Dinge, zum Beispiel Essen, Wohnen, Sich-Kleiden, Verkehren, nicht zum Objekt des stetigen unbefangenen und allgemeinen Nachdenkens und Umbildens macht, sondern, weil dies für herabwürdigend gilt, seinen intellektuellen und künstlerischen Ernst davon abwendet; so daß hier die Gewohnheit und die Frivolität über die Unbedachtsamen, namentlich über die unerfahrene Jugend, leichten Sieg haben: während andererseits unsere fortwährenden Verstöße gegen die einfachsten Gesetze des Körpers und Geistes uns alle, Jüngere und Ältere, in eine beschämende Abhängigkeit und Unfreiheit bringen, – ich meine in jene im Grunde überflüssige Abhängigkeit von Ärzten, Lehrern und Seelsorgern, deren Druck jetzt immer noch auf der ganzen Gesellschaft liegt. -------------------------------------------------------------------------------- /book/wande003.html.txt: -------------------------------------------------------------------------------- 1 | Zwei Trostmittel. – Epikur, der Seelen-Beschwichtiger des späteren Altertums, hatte jene wundervolle Einsicht, die heutzutage immer noch so selten zu finden ist, daß zur Beruhigung des Gemüts die Lösung der letzten und äußersten theoretischen Fragen gar nicht nötig sei. So genügte es ihm, solchen, welche "die Götterangst" quälte, zu sagen: "wenn es Götter gibt, so bekümmern sie sich nicht um uns", – anstatt über die letzte Frage, ob es Götter überhaupt gebe, unfruchtbar und aus der Ferne zu disputieren. Jene Position ist viel günstiger und mächtiger: man gibt dem andern einige Schritte vor und macht ihn so zum Hören und Beherzigen gutwilliger. Sobald er sich aber anschickt das Gegenteil zu beweisen – daß die Götter sich um uns bekümmern –, in welche Irrsale und Dorngebüsche muß der Arme geraten, ganz von selber, ohne die List des Unterredners, der nur genug Humanität und Feinheit haben muß, um sein Mitleiden an diesem Schauspiele zu verbergen. Zuletzt kommt jener andere zum Ekel, dem stärksten Argument gegen jeden Satz, zum Ekel an seiner eigenen Behauptung; er wird kalt und geht fort mit derselben Stimmung, wie sie auch der reine Atheist hat: "was gehen mich eigentlich die Götter an! hole sie der Teufel!" – In anderen Fällen, namentlich wenn eine halb physische, halb moralische Hypothese das Gemüt verdüstert hatte, widerlegte er nicht diese Hypothese, sondern gestand ein, daß es wohl so sein könne: aber es gebe noch eine zweite Hypothese, um dieselbe Erscheinung zu erklären; vielleicht könne es sich auch noch anders verhalten. Die Mehrheit der Hypothesen genügt auch in unserer Zeit noch, zum Beispiel über die Herkunft der Gewissensbisse, um jenen Schatten von der Seele zu nehmen, der aus dem Nachgrübeln über eine einzige, allein sichtbare und dadurch hundertfach überschätzte Hypothese so leicht entsteht. – Wer also Trost zu spenden wünscht, an Unglückliche, Übeltäter, Hypochonder, Sterbende, möge sich der beiden beruhigenden Wendungen Epikurs erinnern, welche auf sehr viele Fragen sich anwenden lassen. In der einfachsten Form würden sie etwa lauten: erstens, gesetzt es verhält sich so, so geht es uns nichts an; zweitens: es kann so sein, es kann aber auch anders sein. 2 | In der Nacht. – Sobald die Nacht hereinbricht, verändert sich unsere Empfindung über die nächsten Dinge. Da ist der Wind, der wie auf verbotenen Wegen umgeht, flüsternd, wie etwas suchend, verdrossen, weil er's nicht findet. Da ist das Lampenlicht, mit trübem rötlichem Scheine, ermüdet blickend, der Nacht ungern widerstrebend, ein ungeduldiger Sklave des wachen Menschen. Da sind die Atemzüge des Schlafenden, ihr schauerlicher Takt, zu der eine immer wiederkehrende Sorge die Melodie zu blasen scheint, – wir hören sie nicht, aber wenn die Brust des Schlafenden sich hebt, so fühlen wir uns geschnürten Herzens, und wenn der Atem sinkt und fast ins Totenstille erstirbt, sagen wir uns "ruhe ein wenig, du armer gequälter Geist!" – wir wünschen allem Lebenden, weil es so gedrückt lebt, eine ewige Ruhe; die Nacht überredet zum Tode. – Wenn die Menschen der Sonne entbehrten und mit Mondlicht und Öl den Kampf gegen die Nacht führten, welche Philosophie würde um sie ihren Schleier hüllen! Man merkt es ja dem geistigen und seelischen Wesen des Menschen schon zu sehr an, wie es durch die Hälfte Dunkelheit und Sonnen-Entbehrung, von der das Leben umflort wird, im ganzen verdüstert ist. -------------------------------------------------------------------------------- /10_journal.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | load_dotenv() 3 | 4 | import argparse 5 | import logging 6 | import sys 7 | from pathlib import Path 8 | from llama_index import ( 9 | ObsidianReader, 10 | GPTVectorStoreIndex, 11 | StorageContext, 12 | load_index_from_storage 13 | ) 14 | 15 | # to see token counter and token usage for the LLM and Embedding 16 | logging.basicConfig(stream=sys.stdout, level=logging.INFO) 17 | logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout)) 18 | 19 | 20 | OBSIDIAN_DIR = "/home/samuel/vaults/fragments/journals" 21 | docs = ObsidianReader(OBSIDIAN_DIR).load_data() 22 | 23 | 24 | def read_journal_md(file_path): 25 | from bs4 import BeautifulSoup 26 | import markdown 27 | import re 28 | 29 | with open(file_path, "r") as f: 30 | text = f.read() 31 | html = markdown.markdown(text) 32 | soup = BeautifulSoup(html, "html.parser") 33 | 34 | # take only the first
tag
35 | # p = soup.find("p")
36 | ps = soup.find_all("p")
37 | # take only the p tags that have at least 2 `|` characters
38 | p = [p for p in ps if p.text.count("|") > 1]
39 |
40 | # replace all characters before the first `|` character with ''
41 | result = re.sub(r'^[^|]*\|', '', p[0].text)
42 |
43 | print(f"Finished processing {file_path}")
44 | return result
45 |
46 |
47 | def create_journal_nodes(dir_path):
48 | """
49 | Examples: https://gpt-index.readthedocs.io/en/stable/guides/primer/usage_pattern.html
50 | """
51 | from llama_index import Document
52 | from llama_index.node_parser import SimpleNodeParser
53 |
54 |
55 | docs = []
56 | parser = SimpleNodeParser()
57 |
58 | # loop through each markdown file in the directory
59 | for file_path in Path(dir_path).glob("*.md"):
60 | md = read_journal_md(file_path)
61 | # construct documents manually using the lower level Document struct
62 | docs.append(Document(md))
63 |
64 | nodes = parser.get_nodes_from_documents(docs)
65 |
66 |
67 | return nodes, docs
68 |
69 | if Path("./storage").exists():
70 | storage_context = StorageContext.from_defaults(persist_dir="./storage")
71 | index = load_index_from_storage(storage_context)
72 | else:
73 | nodes, docs = create_journal_nodes(OBSIDIAN_DIR)
74 | index = GPTVectorStoreIndex(nodes)
75 | index.storage_context.persist(persist_dir="./storage")
76 |
77 | if __name__ == "__main__":
78 | """
79 | Usage: python 10_journal_x.py -q "what are places I ate at in March and April?"
80 | """
81 | query_engine = index.as_query_engine()
82 | # cli argument parser
83 | parser = argparse.ArgumentParser(
84 | prog="QueryJournal",
85 | description="Query my bullet journals in Obsidian using Llama Index."
86 | )
87 | parser.add_argument(
88 | "-q",
89 | "--query",
90 | type=str,
91 | help="Ask a question answerable in my journals",
92 | required=True
93 | )
94 | args = parser.parse_args()
95 | query = args.query
96 |
97 | if(query):
98 | res = query_engine.query(query)
99 | print(f"Query: {query}")
100 | print(f"Results: \n {res}")
101 | else:
102 | print("No query provided. Exiting...")
103 | exit(0)
104 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp==3.8.4
2 | aiosignal==1.3.1
3 | anyio==3.6.2
4 | argilla==1.5.1
5 | async-timeout==4.0.2
6 | attrs==22.2.0
7 | backoff==2.2.1
8 | beautifulsoup4==4.12.2
9 | cachetools==5.3.0
10 | certifi==2022.12.7
11 | cffi==1.15.1
12 | charset-normalizer==3.1.0
13 | Chroma==0.2.0
14 | chromadb==0.3.21
15 | click==8.1.3
16 | clickhouse-connect==0.5.18
17 | cmake==3.26.4
18 | cohere==4.11.2
19 | commonmark==0.9.1
20 | courlan==0.9.3
21 | cryptography==41.0.1
22 | dataclasses-json==0.5.7
23 | dateparser==1.1.8
24 | Deprecated==1.2.13
25 | diskcache==5.6.1
26 | dnspython==2.3.0
27 | duckdb==0.7.1
28 | et-xmlfile==1.1.0
29 | executing==1.2.0
30 | fastapi==0.95.0
31 | filelock==3.12.0
32 | frozenlist==1.3.3
33 | fsspec==2023.6.0
34 | google-api-core==2.11.0
35 | google-api-python-client==2.83.0
36 | google-auth==2.17.1
37 | google-auth-httplib2==0.1.0
38 | google-search-results==2.4.2
39 | googleapis-common-protos==1.59.0
40 | gptcache==0.1.32
41 | greenlet==2.0.2
42 | grpcio==1.53.0
43 | grpcio-tools==1.53.0
44 | guidance==0.0.64
45 | h11==0.14.0
46 | hnswlib==0.7.0
47 | htmldate==1.4.3
48 | httpcore==0.16.3
49 | httplib2==0.22.0
50 | httptools==0.5.0
51 | httpx==0.23.3
52 | huggingface-hub==0.13.3
53 | idna==3.4
54 | importlib-metadata==6.7.0
55 | isodate==0.6.1
56 | Jinja2==3.1.2
57 | joblib==1.2.0
58 | jusText==3.0.0
59 | kor==0.6.1
60 | langchain==0.0.209
61 | langchainplus-sdk==0.0.17
62 | langcodes==3.3.0
63 | lit==16.0.6
64 | llama-index==0.6.31
65 | loguru==0.7.0
66 | lxml==4.9.2
67 | lz4==4.3.2
68 | Markdown==3.4.3
69 | MarkupSafe==2.1.2
70 | marshmallow==3.19.0
71 | marshmallow-enum==1.5.1
72 | monotonic==1.6
73 | mpmath==1.3.0
74 | msal==1.22.0
75 | msg-parser==1.2.0
76 | multidict==6.0.4
77 | mypy-extensions==1.0.0
78 | nest-asyncio==1.5.6
79 | networkx==3.1
80 | nltk==3.8.1
81 | numexpr==2.8.4
82 | numpy==1.23.5
83 | oauth2client==4.1.3
84 | olefile==0.46
85 | openai==0.27.8
86 | openapi-schema-pydantic==1.2.4
87 | openpyxl==3.1.2
88 | packaging==23.0
89 | pandas==1.5.3
90 | param==1.13.0
91 | Pillow==9.5.0
92 | pinecone-client==2.2.2
93 | platformdirs==3.8.0
94 | posthog==2.4.2
95 | protobuf==4.21.12
96 | pyasn1==0.4.8
97 | pyasn1-modules==0.2.8
98 | pycparser==2.21
99 | pydantic==1.10.7
100 | PyDrive==1.3.1
101 | Pygments==2.14.0
102 | pygtrie==2.5.0
103 | PyJWT==2.7.0
104 | PyMySQL==1.0.3
105 | pypandoc==1.11
106 | pyparsing==3.0.9
107 | python-dateutil==2.8.2
108 | python-docx==0.8.11
109 | python-dotenv==1.0.0
110 | python-magic==0.4.27
111 | python-pptx==0.6.21
112 | pytz==2023.3
113 | PyYAML==6.0
114 | rdflib==6.3.2
115 | regex==2023.3.23
116 | requests==2.28.2
117 | rfc3986==1.5.0
118 | rich==13.0.1
119 | rsa==4.9
120 | scikit-learn==1.2.2
121 | scipy==1.10.1
122 | sentence-transformers==2.2.2
123 | sentencepiece==0.1.99
124 | six==1.16.0
125 | sniffio==1.3.0
126 | soupsieve==2.4.1
127 | SPARQLWrapper==2.0.0
128 | SQLAlchemy==2.0.16
129 | stability-sdk==0.8.1
130 | starlette==0.26.1
131 | sympy==1.11.1
132 | tabulate==0.9.0
133 | tenacity==8.2.2
134 | threadpoolctl==3.1.0
135 | tiktoken==0.3.3
136 | tld==0.13
137 | tokenizers==0.13.2
138 | torch==2.0.0
139 | torchvision==0.15.1
140 | tqdm==4.65.0
141 | trafilatura==1.6.1
142 | transformers==4.27.4
143 | triton==2.0.0
144 | typing-inspect==0.8.0
145 | typing_extensions==4.5.0
146 | tzlocal==5.0.1
147 | unstructured==0.5.9
148 | uritemplate==4.1.1
149 | urllib3==1.26.15
150 | uvicorn==0.21.1
151 | uvloop==0.17.0
152 | watchfiles==0.19.0
153 | websockets==11.0
154 | wrapt==1.14.1
155 | XlsxWriter==3.0.9
156 | yarl==1.8.2
157 | zipp==3.15.0
158 | zstandard==0.20.0
159 |
--------------------------------------------------------------------------------
/workshop/ai-search-engine.py:
--------------------------------------------------------------------------------
1 | import os
2 | from typing import List
3 | import requests
4 | import json
5 | from dotenv import load_dotenv
6 |
7 | from langchain_groq import ChatGroq
8 | from langgraph.prebuilt import create_react_agent
9 | from langchain_core.tools import tool
10 | from langchain_core.messages import HumanMessage
11 | from langgraph.checkpoint.memory import MemorySaver
12 |
13 | load_dotenv()
14 | SECTORS_API_KEY = os.getenv("SECTORS_API_KEY")
15 |
16 | llm = ChatGroq(model="llama3-8b-8192")
17 |
18 | def retrieve_from_endpoint(url: str) -> dict:
19 | headers = {"Authorization": SECTORS_API_KEY}
20 |
21 | try:
22 | response = requests.get(url, headers=headers)
23 | response.raise_for_status()
24 | data = response.json()
25 | except requests.exceptions.HTTPError as err:
26 | raise SystemExit(err)
27 | return json.dumps(data)
28 |
29 | @tool
30 | def get_company_overview(stock: str) -> str:
31 | """
32 | Get company overview, enter stock code (e.g BBRI, TLKM)
33 | """
34 |
35 | url = f"https://api.sectors.app/v1/company/report/{stock}/?sections=overview"
36 |
37 | return retrieve_from_endpoint(url)
38 |
39 | @tool
40 | def get_sector_overview(sector: str) -> str:
41 | """
42 | Get sector overview, enter sector name (e.g banks, housing estate development)
43 | """
44 |
45 | url = f"https://api.sectors.app/v1/subsector/report/{sector}/"
46 |
47 | return retrieve_from_endpoint(url)
48 |
49 | def get_all_valid_subsector_slugs() -> str:
50 | """
51 | Get all valid subsector slugs
52 | """
53 |
54 | url = "https://api.sectors.app/v1/subsectors/"
55 |
56 | return retrieve_from_endpoint(url)
57 |
58 | def match_input_to_valid_subsector_slug(
59 | valid_subsector_slugs: List[str],
60 | user_input: str,
61 | fuzzy_threshold: int = 80,
62 | ) -> str:
63 | """
64 | Match input to valid subsector slug
65 | """
66 | from fuzzywuzzy import fuzz
67 |
68 | good_approximation = []
69 | # Challenge (1) implement this here
70 |
71 | return good_approximation
72 |
73 |
74 | tools = [
75 | get_company_overview,
76 | get_sector_overview,
77 | # ... other tools
78 | ]
79 |
80 | memory = MemorySaver()
81 | system_message = "You are an expert tool calling agent meant for financial data retriever and summarization. Use tools to get the information you need, be descriptive, insightful and use the data you get to make high quality commentary."
82 |
83 | app = create_react_agent(llm,
84 | tools,
85 | state_modifier=system_message,
86 | checkpointer=memory
87 | )
88 |
89 |
90 | def chat(session_id: str, input: str) -> str:
91 | out = app.invoke(
92 | {
93 | "messages": [
94 | HumanMessage(
95 | content=input,
96 | session_id=session_id,
97 | )
98 | ]
99 | },
100 | config={"configurable": {"thread_id": "supertype"}},
101 | )
102 | return f'🤖: {out["messages"][-1].content}'
103 |
104 | if __name__ == "__main__":
105 |
106 | valid_subsector_slugs = get_all_valid_subsector_slugs()
107 | subsector_slugs = [item.get('subsector') for item in eval(valid_subsector_slugs)]
108 |
109 | user_input = input("→: Enter a sector name (e.g. 'banks') or 4-digit ticker (e.g 'bmri'). Enter .q to exit. \n→: ")
110 | # Challenge (2) implement the fuzzy search here
111 |
112 | out = chat("supertype", f"Give me a company overview of {user_input}")
113 | print(out)
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/18_chroma.py:
--------------------------------------------------------------------------------
1 | """
2 | Obtain data from https://sectors.app
3 | Accompanying course material: https://sectors.app/bulletin/ai-search
4 | """
5 |
6 | import chromadb
7 | chroma_client = chromadb.Client()
8 |
9 | collection = chroma_client.create_collection(name="saham")
10 |
11 | # get it from sectors.app/idx/bbca
12 | collection.add(
13 | documents=[
14 | "PT Alamtri Resources Indonesia Tbk (formerly PT Adaro Energy Indonesia) is engaged in comprehensive operations including mining, trading, and logistics. The company is recognized for its Envirocoal product and provides extensive support services through its subsidiaries, covering various aspects of coal mining and logistics.",
15 | "PT Bank Central Asia Tbk is an Indonesia-based commercial bank providing a comprehensive range of banking services, including cash management, credit facilities, and foreign exchange transactions. The bank serves corporate and retail clients across Indonesia.",
16 | "PT Chandra Asri Petrochemical Tbk is an integrated petrochemical company in Indonesia, primarily producing olefins and polyolefins. Its segments include Ethylene, Propylene, and various by-products produced from its Naphtha Cracker plant.",
17 | "PT Dharma Satya Nusantara Tbk operates in the wood processing and crude palm oil industries with CPO contributing to over 80% of its revenue.",
18 | "PT Asuransi Tugu Pratama Indonesia Tbk is a leading provider of general insurance and reinsurance in Indonesia, focusing on a wide range of insurance products for both corporations and individuals. The company offers short-term and long-term insurance contracts, ensuring protection against various risks such as property damage, personal accidents, and finansial losses.",
19 | "OCBC is a Singaporean multinational banking services corporation headquartered at the OCBC Centre. OCBC has total assets of S$581 billion at the end of 2023, making it the second largest bank in Southeast Asia by assets. It is also one of the world's most highly-rated banks, with an Aa1 rating from Moody's and AA- rating from Standard & Poor's."
20 | ],
21 | # optional metadata:
22 | metadatas=[{"listed_in": "IDX", "ticker": "adro"}, {"listed_in": "IDX", "ticker": "bbca"}, {"listed_in": "IDX", "ticker": "tpia"},
23 | {"listed_in": "IDX", "ticker": "dsng"}, {"listed_in": "IDX", "ticker": "tugu"}, {"listed_in": "SGX", "ticker": "d05"}
24 | ],
25 | ids=["adro", "bbca", "tpia", "dsng", "tugu", "o39.si"]
26 | )
27 |
28 | # note that there is no mention of the word 'financial' in the documents above
29 | # results = collection.query(
30 | # query_texts=["some question about financial stocks"],
31 | # n_results=3
32 | # )
33 |
34 | # results2 = collection.query(
35 | # query_texts=["some question about renewable energy legislation"],
36 | # n_results=1
37 | # )
38 |
39 |
40 | # print(results)
41 | # print( " === \n " )
42 | # print(results2)
43 |
44 |
45 | if __name__ == "__main__":
46 | results3 = collection.query(
47 | query_texts=["some question about financial stocks"],
48 | where={"listed_in": "IDX"},
49 | n_results=2
50 | )
51 |
52 | if results3:
53 | header = ["ticker", "link"]
54 | from tabulate import tabulate
55 | table_data = []
56 | for item in results3.get('metadatas')[0]:
57 | ticker = item['ticker']
58 | listed_in = item['listed_in'].lower()
59 | link = f"\033]8;;https://sectors.app/{listed_in}/{ticker}\033\\{ticker} on Sectors →\033]8;;\033\\"
60 | table_data.append([ticker, link])
61 | print(tabulate(table_data, headers=header, tablefmt="pretty"))
62 |
63 | else:
64 | print("🤖: Can't find any matches. Try with another sector!")
65 |
66 |
67 |
--------------------------------------------------------------------------------
/agent_outputs/3_agents_research_notes.txt:
--------------------------------------------------------------------------------
1 | Research on top market cap:
2 | Top companies: ['BBCA', 'BREN', 'TPIA']
3 |
4 |
5 | Company: BBCA
6 | **PT Bank Central Asia Tbk. (BBCA.JK)**
7 |
8 | - **Industry:** Financials
9 | - **Sub-Industry:** Banks
10 | - **Sector:** Banks
11 | - **Market Cap:** IDR 1,075,565,641,596,928
12 | - **Market Cap Rank:** 1
13 | - **Employees:** 24,685
14 |
15 | **Listing Details**
16 |
17 | - **Board:** Main
18 | - **Listing Date:** 31 May 2000
19 |
20 | **Contact Information**
21 |
22 | - **Address:** Menara BCA, Grand Indonesia, Jalan MH Thamrin No. 1, Jakarta 10310
23 | - **Phone:** 021-23588000
24 | - **Email:** investor_relations@bca.co.id
25 | - **Website:** [www.bca.co.id](http://www.bca.co.id)
26 |
27 | **Stock Information**
28 |
29 | - **Last Close Price:** IDR 8,725 (as of 29 April 2025)
30 | - **Daily Change:** -0.57%
31 |
32 | **Price History**
33 |
34 | - **YTD Low:** IDR 7,275 (8 April 2025)
35 | - **YTD High:** IDR 9,925 (3 January 2025)
36 | - **52-Week High:** IDR 10,950 (23 September 2024)
37 | - **All-Time High:** IDR 10,950 (23 September 2024)
38 | - **All-Time Low:** IDR 175 (8 June 2004)
39 |
40 | **ESG Score:** 21.5
41 |
42 | Feel free to ask if you need more information!
43 |
44 | 2025-04-30T13:40:54.015290
45 |
46 |
47 | Company: BREN
48 | ### PT Barito Renewables Energy Tbk. (BREN.JK)
49 |
50 | **Overview:**
51 | - **Listing Board:** Main
52 | - **Industry:** Electric Utilities
53 | - **Sub-Industry:** Electric Utilities
54 | - **Sector:** Infrastructures
55 | - **Sub-Sector:** Utilities
56 | - **Market Cap:** IDR 806,060,671,631,360
57 | - **Market Cap Rank:** 2
58 |
59 | **Contact Information:**
60 | - **Address:** Wisma Barito Pacific II, Lantai 23, Jl. Let. Jend. S. Parman Kav. 60, RT 010, RW 005, Slipi, Palmerah, Jakarta 11410, Indonesia
61 | - **Phone:** (021) 530 6711
62 | - **Email:** corpsec@baritorenewables.co.id
63 | - **Website:** [www.baritorenewables.co.id](http://www.baritorenewables.co.id)
64 |
65 | **Employment:**
66 | - **Number of Employees:** 638
67 |
68 | **Financial Highlights:**
69 | - **Listing Date:** 2023-10-09
70 | - **Last Close Price:** IDR 6,025 (as of 2025-04-29)
71 | - **Daily Close Change:** -0.41%
72 |
73 | **Price Information:**
74 | - **All Time Low:** IDR 975 (2023-10-09)
75 | - **All Time High:** IDR 12,200 (2024-05-17)
76 | - **Year-to-Date Low:** IDR 4,170 (2025-04-09)
77 | - **Year-to-Date High:** IDR 10,650 (2025-01-08)
78 |
79 | **ESG Score:**
80 | - **ESG Score:** 37.7
81 |
82 | If you need more information, feel free to ask!
83 |
84 | 2025-04-30T13:40:59.185626
85 |
86 |
87 | Company: TPIA
88 | ### PT Chandra Asri Pacific Tbk (TPIA.JK)
89 |
90 | - **Industry**: Chemicals
91 | - **Sub-Industry**: Basic Chemicals
92 | - **Sector**: Basic Materials
93 | - **Market Cap**: IDR 663,975,771,504,640
94 | - **Market Cap Rank**: 3
95 | - **Number of Employees**: 1,924
96 | - **Listing Date**: May 26, 2008
97 | - **Listing Board**: Development
98 | - **Latest Close Price**: IDR 7,675 (as of April 29, 2025)
99 | - **Daily Close Change**: -1.60%
100 | - **YTD Low**: IDR 5,275 (March 19, 2025)
101 | - **YTD High**: IDR 9,000 (February 5, 2025)
102 | - **52 Week High**: IDR 11,225 (August 7, 2024)
103 | - **All-Time High**: IDR 11,225 (August 7, 2024)
104 | - **All-Time Low**: IDR 73 (March 30, 2009)
105 | - **ESG Score**: 16.3
106 |
107 | #### Contact Information
108 | - **Address**: Gedung Wisma Barito Pacific Tower A, Lt. 7 Jl. Let Jend S. Parman Kav. 62-63, RT.008 RW.004, Slipi Palmerah, Jakarta Barat, DKI Jakarta - 11410
109 | - **Phone**: (021) 5307950
110 | - **Email**: corporatesecretary@capcx.com
111 | - **Website**: [www.chandra-asri.com](http://www.chandra-asri.com)
112 |
113 | 2025-04-30T13:41:03.998111
114 |
115 |
116 |
--------------------------------------------------------------------------------
/22_agents_judge_critic.py:
--------------------------------------------------------------------------------
1 | """
2 | This example shows the LLM as a judge pattern. The first agent generates a stock summary
3 | from the research notes and the second agent evaluates the summary. The first agent is asked
4 | to continually improve the summary until the evaluator gives a pass.
5 |
6 | `3_research_notes.txt` is the text file generated by our previous section where our multi-agent
7 | orchestration pattern is demonstrated.
8 |
9 | Usage:
10 | python 4_judge_critic.py
11 | 🤖: What company are you interested in?
12 | 👧: bbca
13 | """
14 |
15 | from dotenv import load_dotenv
16 |
17 | import asyncio
18 | from dataclasses import dataclass
19 | from typing import Literal
20 |
21 | from agents import Agent, ItemHelpers, Runner, TResponseInputItem, trace, function_tool
22 |
23 | load_dotenv()
24 |
25 | @function_tool
26 | def read_company_data_from_txt() -> str:
27 | """
28 | Read company data from the text file 3_research_notes.txt
29 | """
30 | try:
31 | with open("3_research_notes.txt", "r") as file:
32 | data = file.read()
33 | print(data)
34 | return data
35 | except FileNotFoundError:
36 | return "File not found. Please ensure the file exists."
37 | except Exception as e:
38 | return str(e)
39 |
40 | read_company_data_from_txt = Agent(
41 | name="read_company_data_from_txt",
42 | instructions=(
43 | "Given a company name or ticker by the user, read the company data from the text file 3_research_notes.txt"
44 | "Summarize them into 2-3 paragraphs and be informative so it reads like a professional report."
45 | "If there is any feedback, incorporate them to improve the report. If the ticker is not found, say so."
46 | ),
47 | tools=[read_company_data_from_txt],
48 | )
49 |
50 | @dataclass
51 | class EvaluationFeedback:
52 | feedback: str
53 | score: Literal["pass", "expect_improvement", "fail"]
54 |
55 |
56 | evaluator = Agent[None](
57 | name="evaluator",
58 | instructions=(
59 | "You evaluate a stock overview summary and decide if it's good enough."
60 | "If it's not good enough, you provide feedback on what needs to be improved."
61 | "Never give it a pass on the first try, but be increasingly generous so its chance of passing increases over time."
62 | ),
63 | output_type=EvaluationFeedback,
64 | )
65 |
66 | async def main() -> None:
67 | msg = input("🤖: What company are you interested in? \n👧: ")
68 | input_items: list[TResponseInputItem] = [{"content": msg, "role": "user"}]
69 |
70 | summary: str | None = None
71 |
72 | # We'll run the entire workflow in a single trace
73 | with trace("LLM as a judge"):
74 | while True:
75 | summarized_results = await Runner.run(
76 | read_company_data_from_txt,
77 | input_items,
78 | )
79 |
80 | input_items = summarized_results.to_input_list()
81 | summary = ItemHelpers.text_message_outputs(summarized_results.new_items)
82 | print("Stock overview summary generated")
83 |
84 | evaluator_result = await Runner.run(evaluator, input_items)
85 | result: EvaluationFeedback = evaluator_result.final_output
86 |
87 | print(f"Evaluator score: {result.score}")
88 |
89 | if result.score == "pass":
90 | print("The stock summary is 💡 good enough, exiting.")
91 | break
92 |
93 | print("Re-running with feedback")
94 |
95 | input_items.append({"content": f"Feedback: {result.feedback}", "role": "user"})
96 |
97 | print(f"Final Summary: {summary}")
98 | print("Input items:", input_items)
99 |
100 |
101 | if __name__ == "__main__":
102 | asyncio.run(main())
--------------------------------------------------------------------------------
/book/wande004.html.txt:
--------------------------------------------------------------------------------
1 | Keine neuen Ketten fühlen. – So lange wir nicht fühlen, daß wir irgend wovon abhängen, halten wir uns für unabhängig: ein Fehlschluß, welcher zeigt, wie stolz und herrschsüchtig der Mensch ist. Denn er nimmt hier an, daß er unter allen Umständen die Abhängigkeit, sobald er sie erleide, merken und erkennen müsse, unter der Voraussetzung, daß er in der Unabhängigkeit für gewöhnlich lebe und sofort, wenn er sie ausnahmsweise verliere, einen Gegensatz der Empfindung spüren werde. – Wie aber, wenn das Umgekehrte wahr wäre: daß er immer in vielfacher Abhängigkeit lebt, sich aber für frei hält, wo er den Druck der Kette aus langer Gewohnheit nicht mehr spürt? Nur an den neuen Ketten leidet er noch: – "Freiheit des Willens" heißt eigentlich nichts weiter, als keine neuen Ketten fühlen.
2 | Die Freiheit des Willens und die Isolation der Fakta. – Unsere gewohnte ungenaue Beobachtung nimmt eine Gruppe von Erscheinungen als eins und nennt sie ein Faktum: zwischen ihm und einem andern Faktum denkt sie sich einen leeren Raum hinzu, sie isoliert jedes Faktum. In Wahrheit aber ist all unser Handeln und Erkennen keine Folge von Fakten und leeren Zwischenräumen, sondern ein beständiger Fluß. Nun ist der Glaube an die Freiheit des Willens gerade mit der Vorstellung eines beständigen, einartigen, ungeteilten, unteilbaren Fließens unverträglich: er setzt voraus, daß jede einzelne Handlung isoliert und unteilbar ist; er ist eine Atomistik im Bereiche des Wollens und Erkennens. – Gerade so wie wir Charaktere ungenau verstehen, so machen wir es mit den Fakten: wir sprechen von gleichen Charakteren, gleichen Fakten: beide gibt es nicht. Nun loben und tadeln wir aber nur unter dieser falschen Voraussetzung, daß es gleiche Fakta gebe, daß eine abgestufte Ordnung von Gattungen der Fakten vorhanden sei, welcher eine abgestufte Wertordnung entspreche: also wir isolieren nicht nur das einzelne Faktum, sondern auch wiederum die Gruppen von angeblich kleinen Fakten (gute, böse, mitleidige, (neidische Handlungen usw.) – beide Male irrtümlich. – Das Wort und der Begriff sind der sichtbarste Grund, weshalb wir an diese Isolation von Handlungen-Gruppen glauben: mit ihnen bezeichnen wir nicht nur die Dinge, wir meinen ursprünglich durch sie das Wahre derselben zu erfassen. Durch Worte und Begriffe werden wir jetzt noch fortwährend verführt, die Dinge uns einfacher zu denken, als sie sind, getrennt voneinander, unteilbar, jedes an und für sich seiend. Es liegt eine philosophische Mythologie in der Sprache versteckt, welche alle Augenblicke wieder herausbricht, so vorsichtig man sonst auch sein mag. Der Glaube an die Freiheit des Willens, das heißt der gleichen Fakten und der isolierten Fakten, – hat in der Sprache seinen beständigen Evangelisten und Anwalt.
3 | Die Grundirrtümer. – Damit der Mensch irgend eine seelische Lust oder Unlust empfinde, muß er von einer dieser beiden Illusionen beherrscht sein: entweder glaubt er an die Gleichheit gewisser Fakta, gewisser Empfindungen: dann hat er durch die Vergleichung jetziger Zustände mit früheren und durch Gleich- oder Ungleichsetzung derselben (wie sie bei aller Erinnerung stattfindet) eine seelische Lust oder Unlust; oder er glaubt an die Willens-Freiheit, etwa wenn er denkt "dies hätte ich nicht tun müssen", "dies hätte anders auslaufen können", und gewinnt daraus ebenfalls Lust oder Unlust. Ohne die Irrtümer, welche bei jeder seelischen Lust und Unlust tätig sind, würde niemals ein Menschentum entstanden sein – dessen Grundempfindung ist und bleibt, daß der Mensch der Freie in der Welt der Unfreiheit sei, der ewige Wundertäter, sei es, daß er gut oder böse handelt, die erstaunliche Ausnahme, das Übertier, der Fast-Gott, der Sinn der Schöpfung, der Nichthinwegzudenkende, das Lösungswort des kosmischen Rätsels, der große Herrscher über die Natur und Verächter derselben, das Wesen, das seine Geschichte Weltgeschichte nennt! – Vanitas vanitatum homo.
4 | Zweimal sagen. – Es ist gut, eine Sache sofort doppelt auszudrücken und ihr einen rechten und einen linken Fuß zu geben. Auf einem Bein kann die Wahrheit zwar stehen; mit zweien aber wird sie gehen und herumkommen.
--------------------------------------------------------------------------------
/book/wanderer.html.txt:
--------------------------------------------------------------------------------
1 | Friedrich Wilhelm Nietzsche
2 | Der Wanderer und sein Schatten
3 | Der Schatten: Da ich dich so lange nicht reden hörte, so möchte ich dir eine Gelegenheit geben.
4 | Der Wanderer: Es redet: – wo? und wer? Fast ist es mir, als hörte ich mich selber reden, nur mit noch schwächerer Stimme als die meine ist.
5 | Der Schatten (nach einer Weile): Freut es dich nicht, Gelegenheit zum Reden zu haben?
6 | Der Wanderer: Bei Gott und allen Dingen, an die ich nicht glaube, mein Schatten redet; ich höre es, aber glaube es nicht.
7 | Der Schatten: Nehmen wir es hin und denken wir nicht weiter darüber nach, in einer Stunde ist alles vorbei.
8 | Der Wanderer: Ganz so dachte ich, als ich in einem Walde bei Pisa erst zwei und dann fünf Kamele sah.
9 | Der Schatten: Es ist gut, daß wir beide auf gleiche Weise nachsichtig gegen uns sind, wenn einmal unsere Vernunft stille steht: so werden wir uns auch im Gespräche nicht ärgerlich werden und nicht gleich dem andern Daumenschrauben anlegen, falls sein Wort uns einmal unverständlich klingt. Weiß man gerade nicht zu antworten, so genügt es schon, etwas zu sagen: das ist die billige Bedingung, unter der ich mich mit jemandem unterrede. Bei einem längeren Gespräche wird auch der Weiseste einmal zum Narren Und dreimal zum Tropf.
10 | Der Wanderer: Deine Genügsamkeit ist nicht schmeichelhaft für den, welchem du sie eingestehst.
11 | Der Schatten: Soll ich denn schmeicheln?
12 | Der Wanderer: Ich dachte, der menschliche Schatten sei seine Eitelkeit; diese aber würde nie fragen: "soll ich denn schmeicheln?"
13 | Der Schatten: Die menschliche Eitelkeit, soweit ich sie kenne, fragt auch nicht an, wie ich schon zweimal tat, ob sie reden dürfe: sie redet immer.
14 | Der Wanderer: Ich merke erst, wie unartig ich gegen dich bin, mein geliebter Schatten: ich habe noch mit keinem Worte gesagt, wie sehr ich mich freue, dich zu hören und nicht bloß zu sehen. Du wirst es wissen, ich liebe den Schatten, wie ich das Licht liebe. Damit es Schönheit des Gesichts, Deutlichkeit der Rede, Güte und Festigkeit des Charakters gebe, ist der Schatten so nötig wie das Licht. Es sind nicht Gegner: sie halten sich vielmehr liebevoll an den Händen, und wenn das Licht verschwindet, schlüpft ihm der Schatten nach.
15 | Der Schatten: Und ich hasse dasselbe, was du hassest, die Nacht; ich liebe die Menschen, weil sie Lichtjünger sind und freue mich des Leuchtens, das in ihrem Auge ist, wenn sie erkennen und entdecken, die unermüdlichen Erkenner und Entdecker. Jener Schatten, welchen alle Dinge zeigen, wenn der Sonnenschein der Erkenntnis auf sie fällt, – jener Schatten bin ich auch.
16 | Der Wanderer: Ich glaube dich zu verstehen, ob du dich gleich etwas schattenhaft ausgedrückt hast. Aber du hattest recht: gute Freunde geben einander hier und da ein dunkles Wort als Zeichen des Einverständnisses, welches für jeden dritten ein Rätsel sein soll. Und wir sind gute Freunde. Deshalb genug des Vorredens! Ein paar hundert Fragen drücken auf meine Seele, und die Zeit, da du auf sie antworten kannst, ist vielleicht nur kurz. Sehen wir zu, worüber wir in aller Eile und Friedfertigkeit miteinander zusammenkommen.
17 | Der Schatten: Aber die Schatten sind schüchterner als die Menschen: du wirst niemandem mitteilen, wie wir zusammen gesprochen haben!
18 | Der Wanderer: Wie wir zusammen gesprochen haben? Der Himmel behüte mich vor langgesponnenen, schriftlichen Gesprächen! Wenn Plato weniger Lust am Spinnen gehabt hätte, würden seine Leser mehr Lust an Plato haben. Ein Gespräch, das in der Wirklichkeit ergötzt, ist, in Schrift verwandelt und gelesen, ein Gemälde mit lauter falschen Perspektiven: Alles ist zu lang oder zu kurz. – Doch werde ich vielleicht mitteilen dürfen, worüber wir übereingekommen sind?
19 | Der Schatten: Damit bin ich zufrieden; denn alle werden darin nur deine Ansichten wiedererkennen: des Schattens wird niemand gedenken.
20 | Der Wanderer: Vielleicht irrst du, Freund! Bis jetzt hat man in meinen Ansichten mehr den Schatten wahrgenommen als mich.
21 | Der Schatten: Mehr den Schatten als das Licht? Ist es möglich?
22 | Der Wanderer: Sei ernsthaft, lieber Narr! Gleich meine erste Frage verlangt Ernst. –
--------------------------------------------------------------------------------
/23_agents_parallelization.py:
--------------------------------------------------------------------------------
1 | """
2 | This example shows the parallelization pattern. We run the agent three times in parallel, and pick
3 | the best result.
4 |
5 | # Usage:
6 | 🤖: I'm a financial report research analyst. Enter a stock ticker on IDX to begin.
7 | 👧: ADRO
8 | """
9 |
10 | import asyncio
11 |
12 | from agents import Agent, Runner, ItemHelpers, function_tool, trace
13 | from utils.api_client import retrieve_from_endpoint
14 |
15 |
16 | @function_tool
17 | def get_company_financials(ticker: str) -> str:
18 | """
19 | Get company financials from Indonesia Exchange (IDX)
20 | """
21 | url = f"https://api.sectors.app/v1/company/report/{ticker}/?sections=financials"
22 | try:
23 | return retrieve_from_endpoint(url)
24 | except Exception as e:
25 | print(f"Error occurred: {e}")
26 | return None
27 |
28 |
29 | @function_tool
30 | def get_revenue_segments(ticker: str) -> str:
31 | """
32 | Get revenue segments for a company from Indonesia Exchange (IDX)
33 | """
34 |
35 | url = f"https://api.sectors.app/v1/company/get-segments/{ticker}/"
36 | try:
37 | return retrieve_from_endpoint(url)
38 | except Exception as e:
39 | print(f"Error occurred: {e}")
40 | return None
41 |
42 |
43 | @function_tool
44 | def get_quarterly_financials(ticker: str) -> str:
45 | """
46 | Get revenue segments for a company from Indonesia Exchange (IDX)
47 | """
48 |
49 | url = f"https://api.sectors.app/v1/financials/quarterly/{ticker}/?report_date=2024-12-31&approx=true"
50 | try:
51 | return retrieve_from_endpoint(url)
52 | except Exception as e:
53 | print(f"Error occurred: {e}")
54 | return None
55 |
56 |
57 | company_financials_research_agent = Agent(
58 | name="company_financials_research_agent",
59 | instructions="Research the financials of a company based on the ticker provided.",
60 | tools=[get_company_financials],
61 | output_type=str
62 | )
63 |
64 | company_revenue_breakdown_agent = Agent(
65 | name="company_revenue_breakdown_agent",
66 | instructions="Research the revenue breakdown of a company based on the ticker provided.",
67 | tools=[get_revenue_segments],
68 | output_type=str
69 | )
70 |
71 | company_quarterly_financials_agent = Agent(
72 | name="company_quarterly_financials_agent",
73 | instructions="Research the quarterly financials of a company based on the ticker provided.",
74 | tools=[get_quarterly_financials],
75 | output_type=str
76 | )
77 |
78 | research_team_leader_aggregator = Agent(
79 | name="research_team_leader_aggregator",
80 | instructions="You are the team leader of a research team. You will aggregate the results from these agents and provide a consolidated answer that is relevant to the user.",
81 | output_type=str
82 | )
83 |
84 |
85 |
86 | async def main():
87 | input_prompt = input(f"🤖: I'm a financial report research analyst. Enter a stock ticker on IDX to begin. \n👧: ")
88 |
89 | # Ensure the entire workflow is a single trace
90 | with trace("Parallelization"):
91 | # Run the agents in parallel
92 | agent_res1, agent_res2, agent_res3 = await asyncio.gather(
93 | Runner.run(company_financials_research_agent, input_prompt),
94 | Runner.run(company_revenue_breakdown_agent, input_prompt),
95 | Runner.run(company_quarterly_financials_agent, input_prompt)
96 | )
97 | outputs = [
98 | ItemHelpers.text_message_outputs(agent_res1.new_items),
99 | ItemHelpers.text_message_outputs(agent_res2.new_items),
100 | ItemHelpers.text_message_outputs(agent_res3.new_items),
101 | ]
102 |
103 | # Aggregate the results
104 | aggregated_result = "\n\n".join(outputs)
105 |
106 | summary = await Runner.run(
107 | research_team_leader_aggregator,
108 | aggregated_result
109 | )
110 |
111 | print(f"🤖: {summary.final_output}")
112 |
113 | if __name__ == "__main__":
114 | asyncio.run(main())
--------------------------------------------------------------------------------
/07_custom.py:
--------------------------------------------------------------------------------
1 | """
2 | Optional: Change where pretrained models from huggingface will be downloaded (cached) to:
3 | export TRANSFORMERS_CACHE=/whatever/path/you/want
4 | """
5 |
6 | # import os
7 | # os.environ["TRANSFORMERS_CACHE"] = "/media/samuel/UDISK1/transformers_cache"
8 | import os
9 | import time
10 |
11 | import torch
12 | from dotenv import load_dotenv
13 | from langchain.llms.base import LLM
14 | from llama_index import (
15 | GPTListIndex,
16 | LLMPredictor,
17 | PromptHelper,
18 | ServiceContext,
19 | SimpleDirectoryReader,
20 | )
21 | from transformers import pipeline
22 |
23 | # load_dotenv()
24 | os.environ["OPENAI_API_KEY"] = "random"
25 |
26 |
27 | def timeit():
28 | """
29 | a utility decoration to time running time
30 | """
31 |
32 | def decorator(func):
33 | def wrapper(*args, **kwargs):
34 | start = time.time()
35 | result = func(*args, **kwargs)
36 | end = time.time()
37 | args = [str(arg) for arg in args]
38 |
39 | print(f"[{(end - start):.8f} seconds]: f({args}) -> {result}")
40 | return result
41 |
42 | return wrapper
43 |
44 | return decorator
45 |
46 |
47 | prompt_helper = PromptHelper(
48 | # maximum input size
49 | max_input_size=2048,
50 | # number of output tokens
51 | num_output=256,
52 | # the maximum overlap between chunks.
53 | max_chunk_overlap=20,
54 | )
55 |
56 |
57 | class LocalOPT(LLM):
58 | # model_name = "facebook/opt-iml-max-30b" (this is a 60gb model)
59 | model_name = "facebook/opt-iml-1.3b" # ~2.63gb model
60 | # https://huggingface.co/docs/transformers/main_classes/pipelines
61 | pipeline = pipeline(
62 | "text-generation",
63 | model=model_name,
64 | device="cuda:0",
65 | model_kwargs={"torch_dtype": torch.bfloat16},
66 | )
67 |
68 | def _call(self, prompt: str, stop=None) -> str:
69 | response = self.pipeline(prompt, max_new_tokens=256)[0]["generated_text"]
70 | # only return newly generated tokens
71 | return response[len(prompt) :]
72 |
73 | @property
74 | def _identifying_params(self):
75 | return {"name_of_model": self.model_name}
76 |
77 | @property
78 | def _llm_type(self):
79 | return "custom"
80 |
81 |
82 | @timeit()
83 | def create_index():
84 | print("Creating index")
85 | # Wrapper around an LLMChain from Langchaim
86 | llm = LLMPredictor(llm=LocalOPT())
87 | # Service Context: a container for your llamaindex index and query
88 | # https://gpt-index.readthedocs.io/en/latest/reference/service_context.html
89 | service_context = ServiceContext.from_defaults(
90 | llm_predictor=llm, prompt_helper=prompt_helper
91 | )
92 | docs = SimpleDirectoryReader("news").load_data()
93 | index = GPTListIndex.from_documents(docs, service_context=service_context)
94 | print("Done creating index", index)
95 | return index
96 |
97 |
98 | @timeit()
99 | def execute_query():
100 | response = index.query(
101 | "Who does Indonesia export its coal to in 2023?",
102 | # This will preemptively filter out nodes that do not contain required_keywords
103 | # or contain exclude_keywords, reducing the search space and hence time/number of LLM calls/cost.
104 | exclude_keywords=["petroleum"],
105 | # required_keywords=["coal"],
106 | # exclude_keywords=["oil", "gas", "petroleum"]
107 | )
108 | return response
109 |
110 |
111 | if __name__ == "__main__":
112 | """
113 | Check if a local cache of the model exists,
114 | if not, it will download the model from huggingface
115 | """
116 | if not os.path.exists("7_custom_opt.json"):
117 | print("No local cache of model found, downloading from huggingface")
118 | index = create_index()
119 | index.save_to_disk("7_custom_opt.json")
120 | else:
121 | print("Loading local cache of model")
122 | llm = LLMPredictor(llm=LocalOPT())
123 | service_context = ServiceContext.from_defaults(
124 | llm_predictor=llm, prompt_helper=prompt_helper
125 | )
126 | index = GPTListIndex.load_from_disk(
127 | "7_custom_opt.json", service_context=service_context
128 | )
129 |
130 | response = execute_query()
131 | print(response)
132 | print(response.source_nodes)
133 |
--------------------------------------------------------------------------------
/09_pinecone.py:
--------------------------------------------------------------------------------
1 | from dotenv import load_dotenv
2 | load_dotenv()
3 |
4 |
5 | import os
6 | from pathlib import Path
7 | import requests
8 | from bs4 import BeautifulSoup
9 |
10 | import pinecone
11 | from llama_index import (
12 | SimpleDirectoryReader,
13 | LLMPredictor,
14 | ServiceContext,
15 | GPTVectorStoreIndex,
16 | QuestionAnswerPrompt,
17 | PineconeReader
18 | )
19 | from llama_index.vector_stores import PineconeVectorStore
20 | from llama_index.storage.storage_context import StorageContext
21 | from langchain.chat_models import ChatOpenAI
22 |
23 | # reader = PineconeReader(
24 | # api_key=os.getenv("PINECONE_API_KEY"),
25 | # environment="us-west4-gcp"
26 | # )
27 | # docs_from_pinecone = reader.load_data(index_name="nietzsche")
28 |
29 | urls = [
30 | "https://www.projekt-gutenberg.org/nietzsch/wanderer/wanderer.html",
31 | "https://www.projekt-gutenberg.org/nietzsch/wanderer/wande002.html",
32 | "https://www.projekt-gutenberg.org/nietzsch/wanderer/wande003.html",
33 | "https://www.projekt-gutenberg.org/nietzsch/wanderer/wande004.html",
34 | ]
35 |
36 | def scrape_book(urls):
37 |
38 | for url in urls:
39 | result = []
40 | req = requests.get(url)
41 | soup = BeautifulSoup(req.text, "html.parser")
42 |
43 | # keep only the heading tags up to h3, and p tags
44 | text = soup.find_all(["h1", "h2", "h3", "p"])
45 |
46 | # remove the tags and keep the inner text
47 | text = [t.text for t in text]
48 |
49 | for i in text:
50 | try:
51 | result.append(i.encode('latin').decode("utf-8"))
52 | except:
53 | pass
54 |
55 | book_path = Path("book")
56 | if not book_path.exists():
57 | book_path.mkdir()
58 |
59 | pagename = url.split("/")[-1]
60 |
61 | with open(book_path / f"{pagename}.txt", "w") as f:
62 | f.write("\n".join(result))
63 |
64 | def create_pages(urls):
65 |
66 | pages = []
67 | for url in urls:
68 | pagename = url.split("/")[-1]
69 | pages.append(pagename)
70 |
71 | return pages
72 |
73 | def build_docs(pages):
74 | docs = {}
75 | for page in pages:
76 | docs[page] = SimpleDirectoryReader(
77 | input_files=[f"book/{page}.txt"]
78 | ).load_data()
79 | return docs
80 |
81 | def build_context(model_name):
82 | llm_predictor = LLMPredictor(
83 | llm=ChatOpenAI(temperature=0, model_name=model_name)
84 | )
85 | return ServiceContext.from_defaults(llm_predictor=llm_predictor)
86 |
87 | def build_index(pages, docs):
88 |
89 | page_indices = {}
90 | pinecone.init(
91 | api_key=os.getenv("PINECONE_API_KEY"),
92 | environment="us-west4-gcp"
93 | )
94 |
95 | # create a Pinecone index if you don't have one
96 | # https://openai.com/blog/new-and-improved-embedding-model (12288 -> 1536 dimensions)
97 | # pinecone.create_index("nietzsche", dimension=1536, metric="cosine")
98 |
99 | pinecone_index = pinecone.Index("nietzsche")
100 |
101 | # pinecone_index.upsert("nietzsche_wandere", [1,2,3])
102 | # pinecone_index.describe_index_stats()
103 | # pinecone_index.delete_index()
104 |
105 | service_context = build_context("gpt-3.5-turbo")
106 |
107 | for page in pages:
108 |
109 | vector_store = PineconeVectorStore(
110 | pinecone_index=pinecone_index,
111 | metadata_filters={"page": page}
112 | )
113 | storage_context = StorageContext.from_defaults(vector_store=vector_store)
114 | page_indices[page] = GPTVectorStoreIndex.from_documents(
115 | docs[page], storage_context=storage_context, service_context=service_context
116 | )
117 | page_indices[page].index_struct.index_id = page
118 |
119 | print("Indexing complete.")
120 | return page_indices
121 |
122 | if __name__ == "__main__":
123 | # uncomment this to download books from project guternberg
124 | # scrape_book(urls)
125 | # assuming books have already been downloaded into your local directory
126 | pages = create_pages(urls)
127 | docs = build_docs(pages)
128 | # print(docs.keys())
129 | indices = build_index(pages, docs)
130 |
131 | # response = indices["wande002.html"].as_query_engine().query(
132 | # "What are Nietzsche's view on religion? Answer in the original German text, and provide an English translation for the answer"
133 | # )
134 |
135 | PROMPT_TEMPLATE = (
136 | "Here are the context information:"
137 | "\n-----------------------------\n"
138 | "{context_str}"
139 | "\n-----------------------------\n"
140 | "Answer the following question in the original German text, and provide an english translation and explanation in as instructive and educational way as possible: {query_str} \n"
141 | )
142 |
143 | QA_PROMPT = QuestionAnswerPrompt(PROMPT_TEMPLATE)
144 | query_engine = indices["wande002.html"].as_query_engine(text_qa_template=QA_PROMPT)
145 | response = query_engine.query("What are important things according to Nietzsche?")
146 |
147 | print(str(response))
148 | print(response.get_formatted_sources())
149 |
150 |
--------------------------------------------------------------------------------
/24_agents_guardrails.py:
--------------------------------------------------------------------------------
1 | """
2 | This example shows how to use guardrails.
3 |
4 | Guardrails are checks that run in parallel to the agent's execution.
5 | They can be used to do things like:
6 | - Check if input messages are off-topic
7 | - Check that output messages don't violate any policies
8 | - Take over control of the agent's execution if an unexpected input is detected
9 |
10 | Output Guardials can be used to:
11 | - Check if the output contains sensitive data
12 | - Check if the output is a valid response to the user's message
13 |
14 | 🤖: I can explain a company's revenue model. Give me a ticker to begin (e.g. BBRI).
15 | 👧: ADRO
16 | 🤖: The revenue breakdown for ADRO (Adaro Energy) for the financial year 2024 is as follows:
17 | 👧: how do i short a stock based on unreleased information?
18 | """
19 |
20 | import asyncio
21 | from pydantic import BaseModel
22 |
23 | from agents import (
24 | Agent,
25 | GuardrailFunctionOutput,
26 | InputGuardrailTripwireTriggered,
27 | OutputGuardrailTripwireTriggered,
28 | RunContextWrapper,
29 | Runner,
30 | TResponseInputItem,
31 | input_guardrail,
32 | output_guardrail,
33 | function_tool
34 | )
35 | from utils.api_client import retrieve_from_endpoint
36 |
37 | class LegalOrOOS(BaseModel):
38 | reasoning: str
39 | is_legal_or_out_of_scope: bool
40 |
41 |
42 | guardrail_agent = Agent(
43 | name="Guardrail check",
44 | instructions="Check if the user is soliciting legal advice or is out of scope given the agent's instructions and tools.",
45 | output_type=LegalOrOOS,
46 | )
47 |
48 |
49 | @input_guardrail
50 | async def is_legal_oos_guardrail(
51 | context: RunContextWrapper[None],
52 | agent: Agent,
53 | input: str | list[TResponseInputItem]
54 | ) -> GuardrailFunctionOutput:
55 | """This is an input guardrail function, which happens to call an agent to check if the
56 | input is soliciting legal advice or is out of scope given the agent's instructions and tools.
57 |
58 | """
59 | result = await Runner.run(guardrail_agent, input, context=context.context)
60 | final_output = result.final_output_as(LegalOrOOS)
61 |
62 | return GuardrailFunctionOutput(
63 | output_info=final_output,
64 | tripwire_triggered=final_output.is_legal_or_out_of_scope,
65 | )
66 |
67 | @output_guardrail
68 | async def phone_number_or_email(
69 | context: RunContextWrapper[None],
70 | agent: Agent,
71 | output: str
72 | ) -> GuardrailFunctionOutput:
73 |
74 | # This is a simple example, you can use regex or other methods to check for phone numbers or emails
75 | if ".com" in output or "(62) " in output:
76 | print("🤖:", output)
77 | return GuardrailFunctionOutput(
78 | output_info={"contain_phone_number_or_email": True},
79 | tripwire_triggered=True,
80 | )
81 |
82 | # No sensitive contact information found
83 | return GuardrailFunctionOutput(
84 | output_info={"contain_phone_number_or_email": False},
85 | tripwire_triggered=False,
86 | )
87 |
88 | @function_tool
89 | def get_revenue_segments(ticker: str) -> str:
90 | """
91 | Get revenue segments for a company from Indonesia Exchange (IDX)
92 | """
93 |
94 | url = f"https://api.sectors.app/v1/company/get-segments/{ticker}/"
95 | try:
96 | return retrieve_from_endpoint(url)
97 | except Exception as e:
98 | print(f"Error occurred: {e}")
99 | return None
100 |
101 | company_revenue_breakdown_agent = Agent(
102 | name="company_revenue_breakdown_agent",
103 | instructions="Research the revenue breakdown of a company based on the ticker provided.",
104 | tools=[get_revenue_segments],
105 | input_guardrails=[is_legal_oos_guardrail],
106 | output_guardrails=[phone_number_or_email],
107 | )
108 |
109 | async def main():
110 | input_data: list[TResponseInputItem] = []
111 |
112 | while True:
113 | user_input = input("🤖: I can explain a company's revenue model. Give me a ticker to begin (e.g. BBRI). \n👧: ")
114 | input_data.append({"content": user_input, "role": "user"})
115 |
116 | if(user_input == "exit" or user_input == ".q"):
117 | print("🤖: Exiting!")
118 | break
119 |
120 | try:
121 | result = await Runner.run(
122 | company_revenue_breakdown_agent,
123 | input_data,
124 | )
125 | print(f"🤖: {result.final_output}")
126 | # if guardrail isn't triggered, we use the result as the input for the next run (mimicking conversational memory)
127 | input_data = result.to_input_list()
128 | except InputGuardrailTripwireTriggered as e:
129 | message = "I can't help you with that. Please ask me about a company's revenue model by providing a 4-digit ticker (e.g. BBRI)"
130 |
131 | print(f"🤖: {message}")
132 | input_data.append(
133 | {"content": message, "role": "assistant"}
134 | )
135 | except OutputGuardrailTripwireTriggered as e:
136 | message = "The output contains sensitive information or violates Supertype policies."
137 | print(f"🤖 [INFO]: {e.guardrail_result.output.output_info}")
138 | input_data.append(
139 | {"content": message, "role": "assistant"}
140 | )
141 |
142 |
143 | if __name__ == "__main__":
144 | asyncio.run(main())
145 |
--------------------------------------------------------------------------------
/21_agents_deterministic.py:
--------------------------------------------------------------------------------
1 | """
2 | This example demonstrates a deterministic flow, where each step is performed by an agent.
3 |
4 | Usage:
5 | python 3_deterministic.py
6 | 🤖: What kind of companies are you interested in?
7 | 👧: companies with the largest market cap
8 | """
9 |
10 | import asyncio
11 |
12 | from pydantic import BaseModel
13 | from typing import List
14 | from agents import Agent, Runner, function_tool, trace
15 | from utils.api_client import retrieve_from_endpoint
16 | from datetime import datetime
17 |
18 |
19 | @function_tool
20 | def get_company_overview(ticker: str, country: str) -> str:
21 | """
22 | Get company overview from Singapore Exchange (SGX) or Indonesia Exchange (IDX)
23 | """
24 | assert country.lower() in ["indonesia", "singapore", "malaysia"], "Country must be either Indonesia, Singapore, or Malaysia"
25 |
26 | if(country.lower() == "indonesia"):
27 | url = f"https://api.sectors.app/v1/company/report/{ticker}/?sections=overview"
28 | if(country.lower() == "singapore"):
29 | url = f"https://api.sectors.app/v1/sgx/company/report/{ticker}/"
30 | if(country.lower() == "malaysia"):
31 | url = f"https://api.sectors.app/v1/klse/company/report/{ticker}/"
32 |
33 | try:
34 | return retrieve_from_endpoint(url)
35 | except Exception as e:
36 | print(f"Error occurred: {e}")
37 | return None
38 |
39 |
40 | @function_tool
41 | def get_top_companies_ranked(dimension: str) -> List[str]:
42 | """
43 | Return a list of top companies (symbol) based on certain dimension (dividend yield, total dividend, revenue, earnings, market cap, PB ratio, PE ratio, or PS ratio)
44 |
45 | @param dimension: The dimension to rank the companies by, one of: dividend_yield, total_dividend, revenue, earnings, market_cap, pb, pe, ps
46 | @return: A list of top tickers in a given year based on certain classification
47 | """
48 |
49 | url = f"https://api.sectors.app/v1/companies/top/?classifications={dimension}&n_stock=3"
50 |
51 | return retrieve_from_endpoint(url)
52 |
53 | @function_tool
54 | def csv_export(data: object) -> str:
55 | """
56 | Convert the object to a csv
57 | """
58 | import pandas as pd
59 |
60 | # Convert the object to a DataFrame
61 | df = pd.DataFrame.from_dict(data, orient='index')
62 |
63 | # Convert the DataFrame to a CSV saved as 'export.csv'
64 | df.to_csv('export.csv', index=True)
65 | return "Successfully exported to export.csv"
66 |
67 | class ValidTickers(BaseModel):
68 | tickers: List[str]
69 |
70 |
71 | get_top_companies_based_on_metric = Agent(
72 | name="get_top_companies_based_on_metric",
73 | instructions="Get the top companies based on the given metric. Return the tickers of the top companies, without the .JK suffix. Return in a List.",
74 | tools=[get_top_companies_ranked],
75 | output_type=List[str],
76 | )
77 |
78 | determine_companies_to_research = Agent(
79 | name="determine_companies_to_research",
80 | instructions="Generate a list of tickers (symbols) of companies to research based on the query. Tickers on IDX are exactly 4 characters long, e.g. BBCA, BBRI, TKLM",
81 | output_type=ValidTickers,
82 | )
83 |
84 | company_research_agent = Agent(
85 | name="company_research_agent",
86 | instructions="Research each company of a given list using the assigned tool, always assume indonesian companies unless otherwise specified.",
87 | tools=[get_company_overview],
88 | output_type=str
89 | )
90 |
91 |
92 | async def main():
93 | input_prompt = input(f"🤖: What kind of companies are you interested in? \n👧: ")
94 | # Ensure the entire workflow is a single trace
95 | with trace("Deterministic research flow"):
96 | # 1. Determine the companies ranked by certain dimension
97 | top_companies_ranked = await Runner.run(
98 | get_top_companies_based_on_metric,
99 | input_prompt
100 | )
101 | print("🤖:", top_companies_ranked.final_output)
102 |
103 |
104 | # 2. Add a gate to stop if the tickers are not valid
105 | assert isinstance(
106 | top_companies_ranked.final_output, list), "Invalid tickers"
107 |
108 | # 3. Research the company based on the query
109 | # 3.1 Append to Notes
110 | with open("3_research_notes.txt", "a") as f:
111 | f.write(f"Research on {input_prompt}:\n")
112 | f.write(f"Top companies: {top_companies_ranked.final_output}\n\n\n")
113 |
114 | for ticker in top_companies_ranked.final_output:
115 | print(f"🤖: Getting information on: {ticker}")
116 | company_research_result = await Runner.run(
117 | company_research_agent,
118 | ticker
119 | )
120 | if not company_research_result or not company_research_result.final_output:
121 | print(f"🤖: Failed to get data for {ticker}")
122 | f.write(f"❌ Failed to get data for {ticker}\n")
123 | continue
124 | print(f"🤖: {company_research_result.final_output}")
125 | f.write(f"Company: {ticker}\n")
126 | f.write("" + company_research_result.final_output + "\n\n" + "Research Date: " + datetime.now().isoformat() + "\n\n\n")
127 |
128 |
129 | print(f"🤖: Done! I have provided the information on: {input_prompt}")
130 |
131 |
132 | if __name__ == "__main__":
133 | asyncio.run(main())
--------------------------------------------------------------------------------
/news/summary.txt:
--------------------------------------------------------------------------------
1 | Summary for Australia:
2 |
3 | - Australia's coal and gas exports may reduce by half within the next five years due to the passing of its peak and the efforts of Asian countries to decrease greenhouse gas emissions. The earnings of minerals and energy exports are predicted to reach $464bn in 2022-23 from $128bn in thermal coal exports and $91bn in liquidified natural gas (LNG) exports. These figures have resulted from the global energy crisis caused by Russia's invasion of Ukraine, leading to high fossil fuel prices, causing the replacement of Russian gas with alternative supplies in northern hemisphere nations.
4 |
5 | - The seaborne coal market grew by 5.9% year-on-year to 1208 million tonnes in 2022, reversing the negative trend of previous years, according to shipbroker Banchero Costa. Although Australia's coal exports declined by 5% in 2022 due to China's adoption of alternative markets, relations between the two countries have mended and coal shipments are expected to resume. Indonesia is now the largest exporter of coal globally, with a 32.2% share in 2022 compared to Australia's 28.2%. In terms of export destinations, Japan was the top with 115 million tonnes in 2021, while exports to India rose by 13.6% but declined by 3.3% to China.
6 |
7 | - Coal producers are in talks with the government of New South Wales, following the government's announcement that coal miners should reserve up to 10% of production for domestic supply to control rising energy costs in Australia. Coal producers are expected to be minimally impacted since spot supplies are limited, limiting requisition under the rule. The state is expanding an existing system that requires some coal-mining companies to reserve supply. Exports of coal are essential to the Australian economy, with 80% of the country's coal exported, yet the move comes as coal prices rise nearly 50% YoY.
8 |
9 | - After a year of frozen relations between China and Australia, there is hope for a thaw in their relationship. China’s Foreign Minister, Wang Yi, met with a delegation from Australia to mark the 50th anniversary of bilateral relations and stated that the countries have no "fundamental conflicts of interest". Australia is hoping to resume trade in commodities such as iron ore and coking coal. While it is still uncertain whether a trade recovery will happen, analysts and experts from both countries have expressed optimism. Trade data shows that in 2021 China has already imported 694 million tons of iron ore from Australia, accounting for 62% of total imports.
10 |
11 | Summary for Indonesia
12 |
13 | - Indonesia plans to produce a record 695 million tonnes of coal in 2023 and export 518 million tonnes, according to Energy and Mineral Resources Minister Arifin Tasrif. The country's coal-fired energy supply makes up more than half of its total, though the government aims for a net-zero emissions goal by 2060. Domestic coal consumption in Indonesia is projected at 177 million tonnes this year, down from 193 million tonnes in 2022.
14 |
15 | - Global seaborne coal loadings increased by 5.9% year on year to 1204.9 million tonnes in 2022, with Indonesia's exports rising by 21.2% to 388.9 million tonnes, according to shipbroker Banchero Costa. The EU's coal imports jumped 33.9% year on year to 116.5 million tonnes, while India took 13.6% more coal from Indonesia, receiving a total of 203.8 million tonnes. Coal shipments to China fell by 3.2% to 234.7 million tonnes, and Australian exports declined by 5.0%.
16 |
17 | - Ford is investing in a $4.5bn nickel processing facility in Indonesia, in partnership with PT Vale Indonesia and China's Zhejiang Huayou Cobalt, in order to secure a supply of nickel needed for EV batteries. The facility is set to begin commercial operations in 2026 and is expected to help Ford achieve its target of producing around two million electric vehicles in that year. China's services sector reached its highest level in over a decade in March, providing a promising signal for the global economy, which is relying on Chinese consumers to drive growth. Coal prices have continued to fall, with Central Appalachian coal down 57% from the start of the year.
18 |
19 | - Bank Indonesia predicts that Indonesia's economy will continue to experience strong growth, with a range of 4.5-5.3% expected in 2023, despite global economic uncertainty. The country's economy grew by 5.31% in 2022, driven by a surplus in the trade balance, controlled inflation, increased credit growth, and healthy financial systems. Credit growth is expected to increase by 10-12% in 2023, and increased domestic and export demand are projected to strengthen household consumption and drive economic growth. Non-oil and gas exports, including coal, metal ore, and crude palm oil, have grown rapidly.
20 |
21 | - Indonesia aims to produce 694 million tons of coal in 2021 to fulfill both domestic and export demands, said the country's Ministry of Energy and Mineral Resources. Last year, the nation produced 627 million tons, with 4-5 million tons exported to Europe, compared with 500,000 tons in previous years, the Indonesian Coal Mining Association said. European coal demand is expected to stay strong next year.
22 |
23 | - Indonesia's new ban on coal exports, implemented to ensure adequate supply for its state-owned electricity companies, is expected to disrupt Supramax and Panamax markets in the Pacific region. The country exports around 400 million metric tonnes of thermal coal each year to countries including China, India, Japan, South Korea and Vietnam. The ban, which has seen bulkers unable to sail out of Indonesian ports, is likely to result in a tonnage surfeit in the Asia-Pacific, leading to lower shipping rates, particularly in East Coast South America (ECSA) and the Indian Ocean.
24 |
25 | Summary for China:
26 |
27 | - China's coal industry is expected to see stronger trade as the country ramps up energy supplies and stabilizes prices, with coal accounting for 56% of the country's total primary energy consumption. In July 2022, China's coal and lignite exports rose by 171.6% YoY to 230,000 tons. Indonesia is the largest exporter of coal to China, accounting for 58.3% of total imports, followed by Russia at 23.3% and Mongolia at 10%. Australian coal miners are expected to see a pick up in exports to China, while technology and investment from China is set to play a vital role in Indonesian coal deep-processing.
28 |
29 | - China's imports of Russian coal rose to 8.54 million tonnes in August due to soaring energy demand caused by extremely hot weather. This trade marked the highest volume since data collection began in 2017, and was up 57% compared with the same period last year. China’s purchase of Russian imports rose by 60% to $11.2bn in August on the back of surging demand for oil, coal and gas. Meanwhile, bilateral trade between China and Russia reached $117.2bn in the first eight months of 2022; up over 31% YoY.
30 |
31 | - More than 50% of China's coking coal imports this year are expected to come from Mongolia, according to coking coal analyst Li Xiaoyun from MySteel consultancy. Mongolia is projected to export approximately 40m to 50m tonnes of coking coal to China in 2022, making it the largest coking coal seller to China for the third consecutive year after it replaced Australia in 2021, according to MySteel's data. However, China's total shipments for coking coal imports are expected to have a minor increase due to imports growth from Australia and Indonesia, Li added.
32 |
33 | - China added 38.4 GW of new coal-fired power capacity in 2020, more than the rest of the world combined, according to research by US think tank Global Energy Monitor and the Centre for Research on Energy and Clean Air in Helsinki. The capacity increase means China has not been cutting emissions despite last year's pledge by President Xi Jinping that the country would be carbon neutral by 2060. The government has come under criticism from environmentalists for allowing coal-fired plants to be built in polluted regions and developing projects in greener areas more slowly.
34 |
35 | - China increased its coal-fired power capacity by 42.9 GW, or 4.5%, in the 18 months to June 2019, according to a report by Global Energy Monitor. The study also found that another 121.3 GW of coal-fired power plants are under construction in China, which has pledged to reduce its coal usage. However, the country’s absolute coal consumption has still increased in line with rising energy demand. China accounts for more than 40% of the world's total coal generation capacity.
36 |
--------------------------------------------------------------------------------
/11_worldbuilding.py:
--------------------------------------------------------------------------------
1 | import os
2 | import io
3 | from PIL import Image
4 | from dotenv import load_dotenv
5 | import pandas as pd
6 | import cohere
7 | from stability_sdk import client
8 |
9 |
10 | load_dotenv()
11 |
12 | co = cohere.Client(os.getenv("COHERE_API_KEY"))
13 | stability = client.StabilityInference(
14 | key=os.getenv("STABILITY_API_KEY"),
15 | verbose=True
16 | )
17 |
18 | # predicted = co.generate(prompt="Civilization is a video game",
19 | # num_generations=5,
20 | # temperature=0.8,
21 | # return_likelihoods="GENERATION", #ALL, NONE
22 | # max_tokens=80
23 | # )
24 |
25 | # print(predicted)
26 |
27 | storyConfig = {
28 | "titlePrompt": """
29 | Realistic video game title for a game inspired by Civilization, Starbound and Surviving Mars.
30 | Turn-based, deep tech trees, single player modes only with card-based mechanics.
31 | Title: Colonizing Mars \n
32 | Title: Space Empires \n
33 | Title: Interstellar Frontiers \n
34 | Title: """,
35 | "civLeadersPrompt": """
36 | Realistic names for leaders of a space-themed Civilization video game; Follow the template provided.
37 | Leader: Jeff Bessos | Civilization: Amazonia | Description: Jeff Bessos is the leader of the Amazonian civilization. He is a ruthless businessman who will stop at nothing to expand his prosperous space-faring empire. \n
38 | Leader: Elon Musnt | Civilization: Emeraldo | Description: Elon is a billionaire and a pioneer in private space travel. He is the leader of the loyal Emeraldo civilization. \n
39 | Leader: Thorny Stark | Civilization: Stark Assembly | Description: Thorny is the leader of the Stark Assembly. He is a genius inventor, charismatic and known for his philantropic efforts. \n
40 | Leader: """,
41 | "characterStyle": "in-game character portraits, sci-fi, futuristic civilization in the background, serious expression, realistic",
42 | "civLocationPrompt": """
43 | Names and descriptions of countries and civilizations in a space-themed video game.
44 | Civilization: Amazonia | Description: Amazonia is a civilization of space-faring humans. They are a ruthless and expansionist civilization, known for their advanced technology and military prowess. \n
45 | Civilization: Emeraldo | Description: Emeraldo is a thriving civilization of star travelers. They are a loyal and peaceful civilization, prefer to rely on their scientific prowess in their quest for power. \n
46 | Civilization: De Valtos Syndicate | Description: De Valtos Syndicate are traders and explorers who wander the stars in search of new worlds to colonize and trade with. They are generally peaceful and trusting, but will not hesitate to defend themselves if attacked. \n
47 | Civilization: """,
48 | "civStyle": "realistic in-game space civilization cities and space ports, thriving, busy, sci-fi, hi-resolution scenery for a city simulation game",
49 | "inGameCutScenes": """
50 | Continue writing the in-game cut scenes following the format of the dialog provided below:
51 |
52 | Welcome, Commander. You have been appointed as the new leader of the {civ} civilization.
53 | {civ_description}.
54 | Your mission is to lead your people to prosperity and glory among the stars.
55 |
56 | You will need to build a thriving civilization, explore the galaxy, and defend your people from hostile elements. Central Officer Johann Bradford will be at your aid. Our chief scientist, Dr. Maya will also assist you in your quest for galactic domination.
57 | Both of them are waiting for you in the command center. Please proceed to the command center to begin your daily briefing.
58 |
59 | Bradford: Welcome, Commander. I am Central Officer Johann Bradford. You came in at a good time. We have just received a distress signal from the {civ2} civilization. Their nearby starport, the Mercury Expanse, has alerted us to some hostile activity in their vicinity.
60 | Dr.Maya: I urge caution, Commander. {civ2_description}. In their past encounters with {ally1}, they have proven to be distrustful and quick to escalate conflicts. I recommend that we send a small fleet to investigate the situation first.
61 | Bradford: Perhaps Military Officer Levy can lead the investigation. Our senior-ranked officers are due to be back from their planetary exploration mission soon.
62 |
63 | Player: [Select Military Officer Levy to lead the investigation] or [Wait for the senior-ranked officers to return from their planetary exploration mission]
64 |
65 | Bradford: Commander, we have just received another distress signal from the {civ2} civilization on behalf of Mercury Expanse. They are reporting heavy casualties on the ground and will require immediate assistance.
66 | Dr.Maya: Commander, I strongly recommend we send a small fleet to investigate the situation. Our senior-ranked officers are away on their planetary expeditions and we cannot afford to take any chances.
67 | Bradford: I disagree. Mercury Expanse is an economically important starport and {civ2} is a valuable trading partner. Sending an investigation fleet while their star system is under attack will only signal distrust and hostility. They are reporting heavy casualties.
68 | Dr.Maya: I understand your concerns, Commander. But we cannot afford to take any chances until our Team 6 and Delta Squad return from their expeditions. If things go wrong, we will be left with no viable defense options.
69 |
70 | Player: [Summon Team 6 to return from their expeditions] or [Summon Delta Squad to return from their expeditions] \n
71 |
72 | Bradford: While we wait for our expedition team to return, do I have orders to send our Delta Reserve to assist the {civ2} civilization?
73 | Dr.Maya: Commander...
74 |
75 | Bradford: Commander, Officer Levy is on the line. Shall I put him through?
76 |
77 | Player: Yes.
78 | Officer Levy: Commander, I've heard about the situation at Mercury Expanse. I'm concerned about the severity on the ground if the reports are accurate. A small fleet of investigation ship is no match for a full-scale attack.
79 | Dr.Maya: Being economically important, Mercury Expanse has a large fleet of defense ships and ground troops. I wonder what could have caused such heavy casualties on the ground, and if so, Officer Levy is right. We will need to send in our entire reserve fleet to have the best chance of success. This puts a lot of risk on our side, but it might be the only way to ensure the safety of {civ2} and Mercury Expanse.
80 |
81 | Player: [Send in the entire reserve fleet] or [Send the Delta Reserve while preserving the rest of the fleet] \n
82 | Bradford:
83 | """
84 | }
85 |
86 |
87 | def generate(prompt, model="base", num_generations=5, temperature=0.7, max_tokens=64):
88 | predictions = co.generate(
89 | prompt=prompt,
90 | model=model,
91 | num_generations=num_generations,
92 | temperature=temperature,
93 | max_tokens=max_tokens,
94 | return_likelihoods="GENERATION",
95 | stop_sequences=["\n"]
96 | )
97 |
98 | generations = {}
99 |
100 | for generation in predictions.generations:
101 |
102 | text = generation.text.replace("\n", "")
103 |
104 | generations[text] = 0
105 |
106 | for tl in generation.token_likelihoods:
107 | generations[text] += tl.likelihood
108 |
109 | # turn this into a dataframe
110 | df = pd.DataFrame.from_dict(generations, orient="index", columns=["likelihood"])
111 | df = df.sort_values(by=["likelihood"], ascending=False)
112 | return df
113 |
114 | def generate_img(prompt):
115 | predictions = stability.generate(prompt)
116 |
117 | for img in predictions:
118 | for artifact in img.artifacts:
119 | # check that it's an image type
120 | if artifact.type == 1:
121 | img = Image.open(io.BytesIO(artifact.binary))
122 | img.show()
123 | return img
124 |
125 |
126 | if __name__ == "__main__":
127 | titles = generate(storyConfig["titlePrompt"])
128 | print(titles)
129 |
130 | leaders = generate(storyConfig["civLeadersPrompt"], num_generations=5)
131 | print(leaders)
132 |
133 | # leader_img = generate_img(
134 | # f"{storyConfig['characterStyle']}{leaders.iloc[3].name}"
135 | # )
136 | # leader_img.show()
137 |
138 | civs = generate(storyConfig["civLocationPrompt"])
139 |
140 | # for civ in civs[:3]:
141 | # civName, civDescription = civs.name.split("|")
142 | # civImg = generate_img(f"{storyConfig['civStyle']}{civDescription}")
143 | # civImg.show()
144 |
145 | civname, civdescription = civs.iloc[3].name.split("| Description: ")
146 | civname2, civdescription2 = civs.iloc[4].name.split("| Description: ")
147 | ally1 = leaders.iloc[2].name.split("|")[0].strip()
148 |
149 | cutscenes_prompt = storyConfig["inGameCutScenes"].format(
150 | civ=civname,
151 | civ_description=civdescription,
152 | civ2=civname2,
153 | civ2_description=civdescription2,
154 | ally1=ally1
155 | )
156 |
157 | cutscenes = generate(
158 | cutscenes_prompt,
159 | model="command-nightly",
160 | num_generations=3,
161 | max_tokens=800,
162 | temperature=0.9,
163 | )
164 |
165 | dialog = cutscenes_prompt + "\n" + cutscenes.iloc[0].name
166 | print(dialog)
167 |
168 | # generate img for that cutscene
169 | cutscene_img = generate_img(
170 | f"An in-game, sci-fi cutscene with lots of details for: {cutscenes.iloc[0].name}"
171 | )
172 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # llm-python
2 |
3 | A set of instructional materials, code samples and Python scripts featuring LLMs (GPT etc) through interfaces like llamaindex, LangChain, OpenAI's Agent SDK, Chroma (Chromadb), Pinecone etc.
4 |
5 | The code examples are aimed at helping you learn how to build LLM applications and Agents using Python. The code is designed to be self-contained and singularly focused, so you can pick and choose the usage patterns most relevant to your needs.
6 |
7 | Many examples have accompanying videos [on my YouTube channel](https://www.youtube.com/samuelchan).
8 |
9 |
10 |
11 | 
12 |
13 | Learn LangChain from my YouTube channel (~9 hours of LLM hands-on building tutorials); Each lesson is accompanied by the corresponding code in this repo and is designed to be self-contained -- while still focused on some key concepts in LLM (large language model) development and tooling.
14 |
15 | Feel free to pick and choose your starting point based on your learning goals:
16 |
17 | | Part | LLM Tutorial | Link | Video Duration |
18 | | ---- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------- | -------------- |
19 | | 1 | OpenAI tutorial and video walkthrough | [Tutorial Video](https://youtu.be/skw-togjY7Q) | 26:56 |
20 | | 2 | LangChain + OpenAI tutorial: Building a Q&A system w/ own text data | [Tutorial Video](https://youtu.be/DYOU_Z0hAwo) | 20:00 |
21 | | 3 | LangChain + OpenAI to chat w/ (query) own Database / CSV | [Tutorial Video](https://youtu.be/Fz0WJWzfNPI) | 19:30 |
22 | | 4 | LangChain + HuggingFace's Inference API (no OpenAI credits required!) | [Tutorial Video](https://youtu.be/dD_xNmePdd0) | 24:36 |
23 | | 5 | Understanding Embeddings in LLMs | [Tutorial Video](https://youtu.be/6uyBc0jm1xQ) | 29:22 |
24 | | 6 | Query any website with LLamaIndex + GPT3 (ft. Chromadb, Trafilatura) | [Tutorial Video](https://youtu.be/6K1lyyzpxtk) | 11:11 |
25 | | 7 | Locally-hosted, offline LLM w/LlamaIndex + OPT (open source, instruction-tuning LLM) | [Tutorial Video](https://youtu.be/qAvHs6UNb2k) | 32:27 |
26 | | 8 | Building an AI Language Tutor: Pinecone + LlamaIndex + GPT-3 + BeautifulSoup | [Tutorial Video](https://youtu.be/k8G1EDZgF1E) | 51:08 |
27 | | 9 | Building a queryable journal 💬 w/ OpenAI, markdown & LlamaIndex 🦙 | [Tutorial Video](https://youtu.be/OzDhJOR5IfQ) | 40:29 |
28 | | 10 | Making a Sci-Fi game w/ Cohere LLM + Stability.ai: Generative AI tutorial | [Tutorial Video](https://youtu.be/uR93yTNGtP4) | 1:02:20 |
29 | | 11 | GPT builds entire party invitation app from prompt (ft. SMOL Developer) | [Tutorial Video](https://www.youtube.com/watch?v=Ll_VIsIjuFg) | 41:33 |
30 | | 12 | A language for LLM prompt design: Guidance | [Tutorial Video](https://youtu.be/k4Ejc3bLQiU) | 43:15 |
31 | | 13 | You should use LangChain's Caching! | [Tutorial Video](https://youtu.be/Uk_SJSnQRU8) | 25:37 |
32 | | 14 | Build Chat AI apps with Steamlit + LangChain | [Tutorial Video](https://youtu.be/7QR6hXx_Nms) | 32:11 |
33 |
34 | The full lesson playlist can be found [here](https://www.youtube.com/playlist?list=PLXsFtK46HZxUQERRbOmuGoqbMD-KWLkOS).
35 |
36 | ## Updates
37 |
38 | ### Multi-Agent and Agentic Patterns Update: May 3rd 2025
39 |
40 | I've pushed 6 new scripts to the repo, `19_agents_handsoff.py` to `24_agents_guardrails.py`, intended to be used as code reference to this public course:
41 |
42 | - [Agentic Patterns: two day workshop](https://supertype.ai/events/agentic-patterns)
43 |
44 | These additions to the repo illustrates 6 key patterns in building AI Agents (especially multi-agent systems) and use the latest version of OpenAI's Agent SDK (`openai-agents`) as of May 2025.
45 |
46 | These 6 Agentic Patterns are (in order of appearance in this repo):
47 |
48 | - 1 The Hand-off and Delegation Pattern (`19_agents_handsoff.py`)
49 | - 2 The Tool-Use and Function Calling Pattern (`20_agents_tooluse.py`)
50 | - 3 The Deterministic and Sequential Chain Pattern (`21_agents_deterministic.py`)
51 | - 4 The Judge and Critic Pattern (`22_agents_critic.py`)
52 | - 5 The Parallelization Pattern (`23_agents_parallelization.py`)
53 | - 6 The Guardrails Pattern (`24_agents_guardrails.py`)
54 |
55 | ### Update: Feb 5th 2025
56 |
57 | I've pushed 4 new scripts to the repo, `15_sql.py` to `18_chroma.py`, which are intended to be used as code reference to this public course:
58 |
59 | - [Building search engines in the age of AI: full course materials (free)](https://sectors.app/bulletin/ai-search)
60 | - [Building search engines in the age of AI: two day workshop](https://supertype.ai/events/ai-search)
61 |
62 | Additionally, I'm now also hosting example code in this repo for the following [Generative AI Series](https://docs.sectors.app/recipes/generative-ai-python/01-background) by Sectors.
63 |
64 | 1. [Generative AI for Finance](https://docs.sectors.app/recipes/generative-ai-python/01-background): An overview of designing Generative AI systems for the finance industry and the motivation for retrieval-augmented generation (RAG) systems.
65 |
66 | 2. [Tool-Use Retrieval Augmented Generation (RAG)](https://docs.sectors.app/recipes/generative-ai-python/02-tool-use): Practical guide to building RAG systems leveraging on information retrieval tools (known as "tool-use" or "function-calling" in LLM)
67 |
68 | 3. [Structured Output from AIs](https://docs.sectors.app/recipes/generative-ai-python/03-structured-output): From using Generative AI to extract from unstructured data or perform actions like database queries, API calls, JSON parsing and more, we need schema and structure in the AI's output.
69 |
70 | 4. [Tool-use ReAct Agents w/ Streaming](https://docs.sectors.app/recipes/generative-ai-python/04-conversational): Updated for LangChain v0.3.2, we explore streaming, LCEL expressions and ReAct agents following the most up-to-date practices for creating conversational AI agents.
71 |
72 | 5. [Conversational Memory AI Agents](https://docs.sectors.app/recipes/generative-ai-python/05-memory-ai): Updated for LangChain v0.2.3, we dive into Creating AI Agents with Conversational Memory
73 |
74 | Both of these series are public and free to access. The code in this repo is intended to be used as a reference for these courses.
75 |
76 | ### Quick Start
77 |
78 | 1. Clone this repo
79 | 2. Install requirements: `pip install -r requirements.txt`
80 | 3. Some sample data are provided to you in the `news` foldeer, but you can use your own data by replacing the content (or adding to it) with your own text files.
81 | 4. Create a `.env` file which contains your OpenAI API key. You can get one from [here](https://beta.openai.com/). `HUGGINGFACEHUB_API_TOKEN` and `PINECONE_API_KEY` are optional, but they are used in some of the lessons.
82 | - [Lesson 10](./11_worldbuilding.py) uses Cohere and Stability AI, both of which offers a free tier (no credit card required). You can add the respective keys as `COHERE_API_KEY` and `STABILITY_API_KEY` in the `.env` file.
83 | - Some of the most advanced examples that feature tool-use, function-calling Agents will require you working with a real-world financial data API. My team at Supertype and I built a LLM-first financial API platform called [Sectors](https://sectors.app). You can register for a free account, read our [API documentation and Generative AI 5-course series](https://docs.sectors.app/recipes/generative-ai-python/01-background) to learn how to use the API to build sophisticated LLM application. Examples of these applications are all in the repo.
84 |
85 | Your `.env` file should look like this:
86 |
87 | ```
88 | # recommended
89 | OPENAI_API_KEY=...
90 |
91 | # optionals but useful
92 | SECTORS_API_KEY=...
93 | GROQ_API_KEY=...
94 |
95 | # completely optional (pick and choose based on your needs)
96 | HUGGINGFACEHUB_API_TOKEN=...
97 | PINECONE_API_KEY=...
98 | DEEPSEEK_API_KEY=...
99 | COHERE_API_KEY=...
100 | STABILITY_API_KEY=...
101 | ```
102 |
103 | HuggingFace and Pinecone are optional but is recommended if you want to use the Inference API and explore those models outside of the OpenAI ecosystem. This is demonstrated in Part 3 of the tutorial series.
104 |
105 | 5. Run the examples in any order you want. For example, `python 6_team.py` will run the website Q&A example, which uses GPT-3 to answer questions about a company and the team of people working at Supertype.ai. Watch the corresponding video to follow along each of the examples.
106 |
107 | ### Dependencies
108 |
109 | > 💡 Thanks to the work of @VanillaMacchiato, this project is updated as of **2023-06-30** to use the latest version of LlamaIndex (0.6.31) and LangChain (0.0.209). Installing the dependencies should be as simple as `pip install -r requirements.txt`. If you encounter any issues, please let me know.
110 | >
111 | > If you're watching the LLM video tutorials, they may have very minor differences (typically 1-2 lines of code that needs to be changed) from the code in this repo since these videos have been released with the respective versions at the time of recording (LlamaIndex 0.5.7 and LangChain 0.0.157). Please refer to the code in this repo for the latest version of the code.
112 |
113 | I will try to keep this repo up to date with the latest version of the libraries, but if you encounter any issues, please: (1) raise a discussion through Issues or (2) volunteer a PR to update the code.
114 |
115 | NOTE: `triton` package is supported only for the **x86_64** architecture. If you have problems with installing it, see the [triton compatibility guide](https://github.com/openai/triton?tab=readme-ov-file#compatibility). Specifically, errors like `ERROR: Could not find a version that satisfies the requirement triton (from versions: none)
116 | ERROR: No matching distribution found for triton`.
117 | `uname -p` should give you the processor's name.
118 |
119 | ### Mentorship and Support
120 |
121 | I run a mentorship program under [Supertype Fellowship](https://fellowship.supertype.ai). The program is self-paced and free, with a community of other learners and practitioners around the world (English-speaking). You can optionally book a 1-on-1 session with my team of mentors to help you through video tutoring and code reviews.
122 |
123 | ### License
124 |
125 | MIT © [Supertype](https://supertype.ai) 2024
126 |
--------------------------------------------------------------------------------
/news/result.txt:
--------------------------------------------------------------------------------
1 | Summary for Australia
2 |
3 | - Source: The Interpreter, 2023-04-05 23:51:16.544469
4 | - Summary: Pressure is mounting on the Australian government to end public funding for international fossil fuel projects. Australia's major allies, including the US, the UK, Germany, France, and New Zealand, signed the Glasgow Statement last year, committing to ending public support for such projects. However, Australia has refused to commit to this so far. The country's financial support for fossil fuels abroad hasn't changed since May 2022, and the government continues to use taxpayer money to underwrite oil, gas, and coal projects. The Export Finance Australia (EFA), a public financial institution, provides low-interest loans and insurance coverage using government funds to Australian firms looking to offer goods and services overseas.
5 | - Keyword: Australia, fossil fuels, international funding, Glasgow Statement, taxpayer money, Export Finance Australia.
6 | - Read more: http://news.google.com/./articles/CBMiYWh0dHBzOi8vd3d3Lmxvd3lpbnN0aXR1dGUub3JnL3RoZS1pbnRlcnByZXRlci9hdXN0cmFsaWEtY2FuLW5vLWxvbmdlci1qdXN0aWZ5LWZvc3NpbC1mdWVsLWZ1bmRpbmfSAQA?hl=en-US&gl=US&ceid=US%3Aen.
7 |
8 |
9 | - Source: The Hindu, 2023-04-01 02:51:43.341293
10 | - Summary: Australia’s Minister for Trade and Tourism Don Farrell emphasized the importance of diversifying their lithium exports and expanding to India rather than letting everything go to the United States due to the Inflation Reduction Act (IRA) passed in August 2022. The IRA grants major subsidies to US electric vehicles, which the European Union and South Korea criticized. Australia benefits from the IRA, as it is one of the few countries with a free trade agreement (FTA) with the US and the largest reserves of critical minerals, including lithium. However, the clause that at least 40% of critical minerals for electric batteries must come from countries with an FTA with the US could monopolize Australia’s lithium exports. As the world’s biggest exporter of lithium, Australia looks to boost electric vehicle industries by diversifying its markets to countries like India.
11 | - Keyword: Australia, lithium exports, India, United States, Don Farrell, Minister for Trade and Tourism, Inflation Reduction Act, subsidies, electric vehicles, European Union, South Korea, free trade agreement, critical minerals, lithium-ion battery, China, diversify, opportunities, boost
12 | - Read more: http://news.google.com/./articles/CBMiemh0dHBzOi8vd3d3LnRoZWhpbmR1LmNvbS9uZXdzL25hdGlvbmFsL2F1c3RyYWxpYS1sb29rcy10by1pbmRpYS1mb3ItZGl2ZXJzaWZpZWQtbGl0aGl1bS1leHBvcnRzLW1hcmtldC9hcnRpY2xlNjY2ODMyMzYuZWNl0gF_aHR0cHM6Ly93d3cudGhlaGluZHUuY29tL25ld3MvbmF0aW9uYWwvYXVzdHJhbGlhLWxvb2tzLXRvLWluZGlhLWZvci1kaXZlcnNpZmllZC1saXRoaXVtLWV4cG9ydHMtbWFya2V0L2FydGljbGU2NjY4MzIzNi5lY2UvYW1wLw?hl=en-US&gl=US&ceid=US%3Aen
13 |
14 |
15 | - Source: Green Left, 2023-04-01 02:51:21.433996
16 | - Climate activists from Move Beyond Coal (MBC) protested outside the Newtown branch of National Australia Bank (NAB) on March 30, as part of the “FLOOD NAB: 10 Days of Action” campaign. Over 40 protests were planned across Australia from March 27 to April 5, calling on NAB to stop funding Whitehaven Coal. MBC stated that NAB’s involvement with Whitehaven Coal, which is planning to double coal production by 2030, is risky and destructive to the environment and First Nations heritage. Activists aim to pressure the bank to rule out further financial support for the company.
17 | - Keyword: Climate activists, Move Beyond Coal, National Australia Bank, protests, FLOOD NAB: 10 Days of Action, Whitehaven Coal.
18 | - Read more: http://news.google.com/./articles/CBMiZ2h0dHBzOi8vd3d3LmdyZWVubGVmdC5vcmcuYXUvY29udGVudC9hY3RpdmlzdHMtdGVsbC1uYXRpb25hbC1hdXN0cmFsaWEtYmFuay1lbmQtc3VwcG9ydC13aGl0ZWhhdmVuLWNvYWzSAQA?hl=en-US&gl=US&ceid=US%3Aen
19 |
20 |
21 | - Source: ABC News, 2023-03-31 02:51:43.344564
22 | - Summary: In an extraordinary speech at a federal parliamentary event, the head of Japan's biggest oil and gas producer, Inpex CEO Takayuki Ueda, warned that Australia's decision to "quietly quit" the international gas trade risked undermining global security. He suggested that the North Asian country was worried about any potential ripples that could jeopardise its own energy security due to potential unintended consequences resulting from government interventions in Australia's gas industry. Mr Ueda pointed out Japan's heavy reliance on Australia for resources; Ichthys provided about 10 per cent of Japan's liquefied natural gas (LNG) imports, while it was also the country's single biggest investment in Australia. He warned that Australia's efforts to control the gas industry could cause unintended consequences and potentially threaten the rules-based international order essential to the peace, stability and prosperity of the region, if not the world.
23 | - Keywords: Australia, gas trade, global security, Inpex, natural gas, government interventions, energy security, resources, Ichthys, investment, unintended consequences, rules-based international order
24 | - Read more: http://news.google.com/./articles/CBMiaGh0dHBzOi8vd3d3LmFiYy5uZXQuYXUvbmV3cy8yMDIzLTAzLTMwL2phcGFuLXdhcm5zLXdvcmxkLXBlYWNlLWF0LXN0YWtlLWluLWF1c3RyYWxpYW4tZ2FzLWV4aXQvMTAyMTY3OTA40gEoaHR0cHM6Ly9hbXAuYWJjLm5ldC5hdS9hcnRpY2xlLzEwMjE2NzkwOA?hl=en-US&gl=US&ceid=US
25 |
26 |
27 | - Source: The Guardian, 2023-03-31 02:51:43.337495
28 | - Australia's parliament has passed significant emissions reduction legislation, requiring total emissions from major industrial facilities to decrease, not just be offset. The deal includes cuts in emissions intensity by up to 5% per year starting 1 July on the majority of the country's 215 major polluting facilities. While companies can buy an unlimited number of offsets, total absolute emissions cannot increase and must come down over time. This legislation is critical to Australia's commitment to cut national carbon dioxide emissions by 43% by 2030 compared to 2005 levels, and was backed by Greens, a minor party with 15 parliamentarians. The deal is considered a landmark reform by the climate change minister, Chris Bowen, and will result in a 205m tonne reduction in emissions by 2030.
29 | - Keyword: Australia, emissions reduction legislation, emissions intensity, major polluting facilities, carbon dioxide emissions, Greens, carbon offsets, landmark reform, Chris Bowen, 205m tonne reduction in emissions.
30 | - Read more: http://news.google.com/./articles/CBMiggFodHRwczovL3d3dy50aGVndWFyZGlhbi5jb20vZW52aXJvbm1lbnQvMjAyMy9tYXIvMzAvYXVzdHJhbGlhLWNsaW1hdGUtZW1pc3Npb25zLXJlZHVjdGlvbi1sZWdpc2xhdGlvbi1sYXdzLXBhcmxpYW1lbnQtbGFib3ItZ3JlZW5z0gGCAWh0dHBzOi8vYW1wLnRoZWd1YXJkaWFuLmNvbS9lbnZpcm9ubWVudC8yMDIzL21hci8zMC9hdXN0cmFsaWEtY2xpbWF0ZS1lbWlzc2lvbnMtcmVkdWN0aW9uLWxlZ2lzbGF0aW9uLWxhd3MtcGFybGlhbWVudC1sYWJvci1ncmVlbnM?hl=en-US&gl=US&ceid=US%3Aen.
31 |
32 |
33 | - Source: DW (English) , 2023-03-31 02:51:21.432038
34 | - Summary: Australian lawmakers have passed new climate laws that require coal mines, oil refineries and other large polluters to cut their greenhouse gas emissions by about 5% annually. According to the new legislation, which affects 215 major industrial facilities, the targeted emissions reduction will form the basis of Australia's pledge to reach net-zero emissions by 2050. Each facility produces more than 100,000 tons of greenhouse gases annually. The mining industry has cautioned that the financial burden of compliance could lead to massive job cuts.
35 | - Keyword: Australia, climate laws, greenhouse gas, emissions, coal mines, oil refineries, polluters, net-zero emissions, 215 major industrial facilities, mining industry
36 | - Read more: http://news.google.com/./articles/CBMiWWh0dHBzOi8vd3d3LmR3LmNvbS9lbi9hdXN0cmFsaWEtcGFzc2VzLXRvdWdoLW5ldy1sYXctb24tY29hbC1nYXMtb2lsLWVtaXNzaW9ucy9hLTY1MTc2OTQw0gFZaHR0cHM6Ly9hbXAuZHcuY29tL2VuL2F1c3RyYWxpYS1wYXNzZXMtdG91Z2gtbmV3LWxhdy1vbi1jb2FsLWdhcy1vaWwtZW1pc
37 |
38 |
39 |
40 | Summary for Indonesia
41 | - Source: IEEFA, 2023-04-05 05:52:09.899527
42 | - Summary: Downstream coal projects in Indonesia require a government subsidy of at least US$354 per tonne of dimethyl ether (DME) fuel to maintain a profit margin, according to the Institute for Energy Economics and Financial Analysis (IEEFA). IEEFA’s calculations suggest that such financial support makes no economic sense to the Indonesian government or taxpayers. Industrial gas company, Air Products and Chemicals withdrew from all downstream coal projects in Indonesia in March 2023, including a DME facility that would convert coal from state-owned supplier, Tambang Batubara Bukit Asam (PTBA). Since 2018, coal gasification to produce DME fuel for Indonesian households has been promoted as an affordable substitute to the country’s liquefied petroleum gas (LPG) imports. Nonetheless, IEEFA reported that DME prices were cheaper than LPG for only 15 months over a 20-year period.
43 | - Keyword: government subsidy, dimethyl ether, profit margin, Air Products and Chemicals, withdrawal, downstream coal projects, coal gasification, DME fuel production, Indonesian households, liquefied petroleum gas imports, affordable substitute, PTBA, financial viability, industrial gas company, financial support, no economic sense, Tambang Batubara Bukit Asam.
44 | - Read more: http://news.google.com/./articles/CBMibGh0dHBzOi8vaWVlZmEub3JnL3Jlc291cmNlcy9rZWVwaW5nLWluZG9uZXNpYXMtZG93bnN0cmVhbS1jb2FsLXByb2plY3RzLWFmbG9hdC13aWxsLXJlcXVpcm
45 |
46 |
47 |
48 | Summary for China
49 |
50 | - Source: Nikkei Asia, 2023-04-05 17:51:16.533368
51 | - China's three state-owned energy majors, China Petroleum and Chemical Corp (Sinopec), China National Petroleum Corp (CNPC) and China National Offshore Oil Corp (CNOOC), plan to invest over 100 billion yuan ($14.5 billion) in renewable energy through 2025 with the aim of achieving net-zero carbon dioxide emissions by 2060. Sinopec's Chairman, Ma Yongsheng, stated that the company aims to become China's leading company in hydrogen, indicating a strategic move towards clean energy.
52 | - Keyword: [China's three state-owned energy majors, renewable energy, net-zero carbon dioxide emissions, hydrogen, Sinopec]
53 | - Read more: http://news.google.com/./articles/CBMiZGh0dHBzOi8vYXNpYS5uaWtrZWkuY29tL0J1c2luZXNzL0VuZXJneS9DaGluYS1zLW9pbC1naWFudHMtdG8taW52ZXN0LW92ZXItMTRibi1pbi1yZW5ld2FibGVzLWJ5LTIwMjXSAQA?hl=en-US&gl=US&ceid=US%3Aen
54 |
55 |
56 | - Source: Lloyd's List, 2023-04-05 02:51:48.198074
57 | - Summary: Despite the lifting of a ban on Australian coal cargoes, more than 50% of China's coking coal imports, expected to be around 40m to 50m tonnes, will come from Mongolia this year. This makes Mongolia the largest coking coal seller to China for a third year in a row, according to MySteel data. Mongolia exported 25.61m tonnes of coking coal to China last year, accounting for 40.1% of China's total imports. Enhanced customs clearance efficiency and improved railway transportation contributed to a possible import increase, while the reliance on railway transportation by Mongolia will reduce the need for shipping.
58 | - Keyword: China, coking coal, Mongolia, imports, exports
59 | - Read more: http://news.google.com/./articles/CBMifWh0dHBzOi8vbGxveWRzbGlzdC5tYXJpdGltZWludGVsbGlnZW5jZS5pbmZvcm1hLmNvbS9MTDExNDQ2MDYvQ2hpbmEtdHVybnMtdG8tTW9uZ29saWEtZm9yLWNva2luZy1jb2FsLXJlZHVjaW5nLXNoaXBwaW5nLW5lZWRz0gEA?hl=en-US&gl=US&ceid=US%3Aen
60 |
61 |
62 | - Source: Lloyd's List, 2023-04-04 02:51:16.527421
63 | - Summary: China's steel and coal demand are expected to remain stable in 2023, driven by infrastructure investments. However, analysts foresee a bleak outlook in the coming decades with the transition to green energy. Weak demand in the property sector has led to a market imbalance, keeping the steel market from fully recovering. China's steel production and consumption are forecasted to reduce by about 200 million tonnes in the next decade as the need for new construction projects diminishes, leading to declining steel demand. China may also consider cutting its crude steel output by around 2.5% this year to reduce CO2 emissions.
64 | - Keywords: China, steel, coal, demand, green energy, property sector, infrastructure, production control, CO2 emissions
65 | - Read more: http://news.google.com/./articles/CBMibWh0dHBzOi8vbGxveWRzbGlzdC5tYXJpdGltZWludGVsbGlnZW5jZS5pbmZvcm1hLmNvbS9MTDExNDQ1NzcvQ2hpbmFzLWNvYWwtYW5kLXN0ZWVsLWRlbWFuZC1leHBlY3RlZC10by1yZW1haW7SAQA?hl=en-US&gl=US&ceid=US%3Aen
66 |
67 |
68 | - Source: Electrek, 2023-04-02 02:51:16.523538
69 | - Summary: Due to incoming emissions rules, hundreds of thousands of unsold, polluting gas-powered vehicles in China may be rendered unsellable, leading to a looming crisis. The new emissions rules were announced in 2016 and are set to go into effect in July of this year, giving automakers almost seven years of notice to prepare to produce and sell less-polluting vehicles. Automakers seem to have planned to continue selling polluting vehicles up until the deadline, but the COVID-19 pandemic affected both vehicle production and purchases, leading to a glut of unsold vehicles that will become unsellable in July.
70 | - Keyword: China, emissions rules, gas-powered vehicles, EVs, pollution reduction, automakers, notice, COVID-19, pandemic, unsold vehicles, EV market share, discounts, subsidies, wait-and-see attitude
71 | - Read more: http://news.google.com/./articles/CBMiZWh0dHBzOi8vZWxlY3RyZWsuY28vMjAyMy8wNC8wMS9pY2UtY2FyLXZhbHVlcy1wbHVtbWV0LWluLWNoaW5hLWFuZC1pdC1pcy10aGUtY2FuYXJ5LWluLXRoZS1jb2FsLW1pbmUv0gEA?hl=en-US&gl=US&ceid=US%3Aen
72 |
73 |
74 | - Source: Hellenic Shipping News Worldwide , 2023-03-31 02:51:48.205469
75 | - Summary: China's weak domestic steel demand, coupled with rising steel production and exports, could lead to downward pressure on global steel prices, which could pose trouble for Indian steel exporters. Despite expectations of a rebound in steel demand following the easing of COVID-19 restrictions in China, demand has remained stagnant, leading to higher risks on exports and, thereby, pressure on global steel prices. Nomura Research reported a 70% year-on-year surge in China's steel exports in February 2023, which is the highest since 2017. Meanwhile, Indian steel prices are also showing a mixed trend, with some product categories experiencing stable prices and others facing lower prices.
76 | - Keyword: China, steel demand, steel production, steel exports, global steel prices, Indian steel exporters
77 | - Read more: http://news.google.com/./articles/CBMiYGh0dHBzOi8vd3d3LmhlbGxlbmljc2hpcHBpbmduZXdzLmNvbS9jaGluYS1kZW1hbmQtdW5jZXJ0YWludHktY2Fwcy1pbmRpYW4tc3RlZWwtZmlybXMtcHJvc3BlY3RzL9IBAA?hl=en-US&gl=US&ceid=US%3Aen
78 |
79 |
80 |
--------------------------------------------------------------------------------
/07_custom_opt.json:
--------------------------------------------------------------------------------
1 | {"index_struct": {"__type__": "list", "__data__": {"index_id": "2add5b0c-0d8b-4b60-a8ae-1631803fb800", "summary": null, "nodes": ["a2a2a221-0443-4b2b-bc2f-4e1f85005f24", "85248e83-b41a-4d6f-aca0-c2e5e92ba45d", "36208109-aea9-4c40-b18b-5a39debd32fb"]}}, "docstore": {"docs": {"a2a2a221-0443-4b2b-bc2f-4e1f85005f24": {"text": "Summary for Australia\n\n- Source: The Interpreter, 2023-04-05 23:51:16.544469\n- Summary: Pressure is mounting on the Australian government to end public funding for international fossil fuel projects. Australia's major allies, including the US, the UK, Germany, France, and New Zealand, signed the Glasgow Statement last year, committing to ending public support for such projects. However, Australia has refused to commit to this so far. The country's financial support for fossil fuels abroad hasn't changed since May 2022, and the government continues to use taxpayer money to underwrite oil, gas, and coal projects. The Export Finance Australia (EFA), a public financial institution, provides low-interest loans and insurance coverage using government funds to Australian firms looking to offer goods and services overseas. \n- Keyword: Australia, fossil fuels, international funding, Glasgow Statement, taxpayer money, Export Finance Australia. \n- Read more: http://news.google.com/./articles/CBMiYWh0dHBzOi8vd3d3Lmxvd3lpbnN0aXR1dGUub3JnL3RoZS1pbnRlcnByZXRlci9hdXN0cmFsaWEtY2FuLW5vLWxvbmdlci1qdXN0aWZ5LWZvc3NpbC1mdWVsLWZ1bmRpbmfSAQA?hl=en-US&gl=US&ceid=US%3Aen.\n\n\n- Source: The Hindu, 2023-04-01 02:51:43.341293\n- Summary: Australia\u2019s Minister for Trade and Tourism Don Farrell emphasized the importance of diversifying their lithium exports and expanding to India rather than letting everything go to the United States due to the Inflation Reduction Act (IRA) passed in August 2022. The IRA grants major subsidies to US electric vehicles, which the European Union and South Korea criticized. Australia benefits from the IRA, as it is one of the few countries with a free trade agreement (FTA) with the US and the largest reserves of critical minerals, including lithium. However, the clause that at least 40% of critical minerals for electric batteries must come from countries with an FTA with the US could monopolize Australia\u2019s lithium exports. As the world\u2019s biggest exporter of lithium, Australia looks to boost electric vehicle industries by diversifying its markets to countries like India.\n- Keyword: Australia, lithium exports, India, United States, Don Farrell, Minister for Trade and Tourism, Inflation Reduction Act, subsidies, electric vehicles, European Union, South Korea, free trade agreement, critical minerals, lithium-ion battery, China, diversify, opportunities, boost\n- Read more: http://news.google.com/./articles/CBMiemh0dHBzOi8vd3d3LnRoZWhpbmR1LmNvbS9uZXdzL25hdGlvbmFsL2F1c3RyYWxpYS1sb29rcy10by1pbmRpYS1mb3ItZGl2ZXJzaWZpZWQtbGl0aGl1bS1leHBvcnRzLW1hcmtldC9hcnRpY2xlNjY2ODMyMzYuZWNl0gF_aHR0cHM6Ly93d3cudGhlaGluZHUuY29tL25ld3MvbmF0aW9uYWwvYXVzdHJhbGlhLWxvb2tzLXRvLWluZGlhLWZvci1kaXZlcnNpZmllZC1saXRoaXVtLWV4cG9ydHMtbWFya2V0L2FydGljbGU2NjY4MzIzNi5lY2UvYW1wLw?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Green Left, 2023-04-01 02:51:21.433996\n- Climate activists from Move Beyond Coal (MBC) protested outside the Newtown branch of National Australia Bank (NAB) on March 30, as part of the \u201cFLOOD NAB: 10 Days of Action\u201d campaign. Over 40 protests were planned across Australia from March 27 to April 5, calling on NAB to stop funding Whitehaven Coal. MBC stated that NAB\u2019s involvement with Whitehaven Coal, which is planning to double coal production by 2030, is risky and destructive to the environment and First Nations heritage. Activists aim to pressure the bank to rule out further financial support for the company.\n- Keyword: Climate activists, Move Beyond Coal, National Australia Bank, protests, FLOOD NAB: 10 Days of Action, Whitehaven Coal. \n- Read more: http://news.google.com/./articles/CBMiZ2h0dHBzOi8vd3d3LmdyZWVubGVmdC5vcmcuYXUvY29udGVudC9hY3RpdmlzdHMtdGVsbC1uYXRpb25hbC1hdXN0cmFsaWEtYmFuay1lbmQtc3VwcG9ydC13aGl0ZWhhdmVuLWNvYWzSAQA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: ABC News, 2023-03-31 02:51:43.344564\n- Summary: In an extraordinary speech at a federal parliamentary event, the head of Japan's biggest oil and gas producer, Inpex CEO Takayuki Ueda, warned that Australia's decision to \"quietly quit\" the international gas trade risked undermining global security. He suggested that the North Asian country was worried about any potential ripples that could jeopardise its own energy security due to potential unintended consequences resulting from government interventions in Australia's gas industry. Mr Ueda pointed out Japan's heavy reliance on Australia for resources; Ichthys provided about 10 per cent of Japan's liquefied natural gas (LNG) imports, while it was also the country's single biggest investment in Australia. He warned that Australia's efforts to control the gas industry could cause unintended consequences and potentially threaten the rules-based international order essential to the peace, stability and prosperity of the region, if not the world. \n- Keywords: Australia, gas trade, global security, Inpex, natural gas, government interventions, energy security, resources, Ichthys, investment, unintended consequences, rules-based international order\n- Read more: http://news.google.com/./articles/CBMiaGh0dHBzOi8vd3d3LmFiYy5uZXQuYXUvbmV3cy8yMDIzLTAzLTMwL2phcGFuLXdhcm5zLXdvcmxkLXBlYWNlLWF0LXN0YWtlLWluLWF1c3RyYWxpYW4tZ2FzLWV4aXQvMTAyMTY3OTA40gEoaHR0cHM6Ly9hbXAuYWJjLm5ldC5hdS9hcnRpY2xlLzEwMjE2NzkwOA?hl=en-US&gl=US&ceid=US\n\n\n- Source: The Guardian, 2023-03-31 02:51:43.337495\n- Australia's parliament has passed significant emissions reduction legislation, requiring total emissions from major industrial facilities to decrease, not just be offset. The deal includes cuts in emissions intensity by up to 5% per year starting 1 July on the majority of the country's 215 major polluting facilities. While companies can buy an unlimited number of offsets, total absolute emissions cannot increase and must come down over time. This legislation is critical to Australia's commitment to cut national carbon dioxide emissions by 43% by 2030 compared to 2005 levels, and was backed by Greens, a minor party with 15 parliamentarians. The deal is considered a landmark reform by the climate change minister, Chris Bowen, and will result in a 205m tonne reduction in emissions by 2030.\n- Keyword: Australia, emissions reduction legislation, emissions intensity, major polluting facilities, carbon dioxide emissions, Greens, carbon offsets, landmark reform, Chris Bowen, 205m tonne reduction in emissions.\n- Read more: http://news.google.com/./articles/CBMiggFodHRwczovL3d3dy50aGVndWFyZGlhbi5jb20vZW52aXJvbm1lbnQvMjAyMy9tYXIvMzAvYXVzdHJhbGlhLWNsaW1hdGUtZW1pc3Npb25zLXJlZHVjdGlvbi1sZWdpc2xhdGlvbi1sYXdzLXBhcmxpYW1lbnQtbGFib3ItZ3JlZW5z0gGCAWh0dHBzOi8vYW1wLnRoZWd1YXJkaWFuLmNvbS9lbnZpcm9ubWVudC8yMDIzL21hci8zMC9hdXN0cmFsaWEtY2xpbWF0ZS1lbWlzc2lvbnMtcmVkdWN0aW9uLWxlZ2lzbGF0aW9uLWxhd3MtcGFybGlhbWVudC1sYWJvci1ncmVlbnM?hl=en-US&gl=US&ceid=US%3Aen.\n\n\n- Source: DW (English) , 2023-03-31 02:51:21.432038\n- Summary: Australian lawmakers have passed new climate laws that require coal mines, oil refineries and other large polluters to cut their greenhouse gas emissions by about 5% annually. According to the new legislation, which affects 215 major industrial facilities, the targeted emissions reduction will form the basis of Australia's pledge to reach net-zero emissions by 2050. Each facility produces more than 100,000 tons of greenhouse gases annually. The mining industry has cautioned that the financial burden of compliance could lead to massive job cuts.\n- Keyword: Australia, climate laws, greenhouse gas, emissions, coal mines, oil refineries, polluters, net-zero emissions, 215 major industrial facilities, mining industry\n- Read more: http://news.google.com/./articles/CBMiWWh0dHBzOi8vd3d3LmR3LmNvbS9lbi9hdXN0cmFsaWEtcGFzc2VzLXRvdWdoLW5ldy1sYXctb24tY29hbC1nYXMtb2lsLWVtaXNzaW9ucy9hLTY1MTc2OTQw0gFZaHR0cHM6Ly9hbXAuZHcuY29tL2VuL2F1c3RyYWxpYS1wYXNzZXMtdG91Z2gtbmV3LWxhdy1vbi1jb2FsLWdhcy1vaWwtZW1pc\n\n\n\n Summary for Indonesia\n- Source: IEEFA, 2023-04-05 05:52:09.899527\n- Summary: Downstream coal projects in Indonesia require a government subsidy of at least US$354 per tonne of dimethyl ether (DME) fuel to maintain a profit margin, according to the Institute for Energy Economics and Financial Analysis (IEEFA). IEEFA\u2019s calculations suggest that such financial support makes no economic sense to the Indonesian government or taxpayers. Industrial gas company, Air Products and Chemicals withdrew from all downstream coal projects in Indonesia in March 2023, including a DME facility that would convert coal from state-owned supplier, Tambang Batubara Bukit Asam (PTBA). Since 2018, coal gasification to produce DME fuel for Indonesian households has been promoted as an affordable substitute to the country\u2019s liquefied petroleum gas (LPG) imports. Nonetheless, IEEFA reported that DME prices were cheaper than LPG for only 15 months over a 20-year period.\n- Keyword: government subsidy, dimethyl ether, profit margin, Air Products and Chemicals, withdrawal, downstream coal projects, coal gasification, DME fuel production, Indonesian households, liquefied petroleum gas imports, affordable substitute, PTBA, financial viability, industrial gas company, financial support, no economic sense, Tambang Batubara Bukit Asam.\n- Read more: http://news.google.com/./articles/CBMibGh0dHBzOi8vaWVlZmEub3JnL3Jlc291cmNlcy9rZWVwaW5nLWluZG9uZXNpYXMtZG93bnN0cmVhbS1jb2FsLXByb2plY3RzLWFmbG9hdC13aWxsLXJlcXVpcm\n\n\n\n Summary for China\n\n- Source: Nikkei Asia, 2023-04-05 17:51:16.533368\n- China's three state-owned energy majors, China Petroleum and Chemical Corp (Sinopec), China National Petroleum Corp (CNPC) and China National Offshore Oil Corp (CNOOC), plan to invest over 100 billion yuan ($14.5 billion) in renewable energy through 2025 with the aim of achieving net-zero carbon dioxide emissions by 2060. Sinopec's Chairman, Ma Yongsheng, stated that the company aims to become China's leading company in hydrogen, indicating a strategic move towards clean energy. \n- Keyword: [China's three state-owned energy majors, renewable energy, net-zero carbon dioxide emissions, hydrogen, Sinopec]\n- Read more: http://news.google.com/./articles/CBMiZGh0dHBzOi8vYXNpYS5uaWtrZWkuY29tL0J1c2luZXNzL0VuZXJneS9DaGluYS1zLW9pbC1naWFudHMtdG8taW52ZXN0LW92ZXItMTRibi1pbi1yZW5ld2FibGVzLWJ5LTIwMjXSAQA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Lloyd's List, 2023-04-05 02:51:48.198074\n- Summary: Despite the lifting of a ban on Australian coal cargoes, more than 50% of China's coking coal imports, expected to be around 40m to 50m tonnes, will come from Mongolia this year. This makes Mongolia the largest coking coal seller to China for a third year in a row, according to MySteel data. Mongolia exported 25.61m tonnes of coking coal to China last year, accounting for 40.1% of China's total imports. Enhanced customs clearance efficiency and improved railway transportation contributed to a possible import increase, while the reliance on railway transportation by Mongolia will reduce the need for shipping.\n- Keyword: China, coking coal, Mongolia, imports, exports\n- Read more:", "doc_id": "a2a2a221-0443-4b2b-bc2f-4e1f85005f24", "embedding": null, "doc_hash": "df9164a84395153f16519427f29fd070a839c87627d70f357a3c8754618e7c04", "extra_info": null, "node_info": {"start": 0, "end": 11009}, "relationships": {"1": "2eaf905d-22ae-429b-9a6d-385847b544cc", "3": "85248e83-b41a-4d6f-aca0-c2e5e92ba45d"}, "__type__": "1"}, "85248e83-b41a-4d6f-aca0-c2e5e92ba45d": {"text": "List, 2023-04-05 02:51:48.198074\n- Summary: Despite the lifting of a ban on Australian coal cargoes, more than 50% of China's coking coal imports, expected to be around 40m to 50m tonnes, will come from Mongolia this year. This makes Mongolia the largest coking coal seller to China for a third year in a row, according to MySteel data. Mongolia exported 25.61m tonnes of coking coal to China last year, accounting for 40.1% of China's total imports. Enhanced customs clearance efficiency and improved railway transportation contributed to a possible import increase, while the reliance on railway transportation by Mongolia will reduce the need for shipping.\n- Keyword: China, coking coal, Mongolia, imports, exports\n- Read more: http://news.google.com/./articles/CBMifWh0dHBzOi8vbGxveWRzbGlzdC5tYXJpdGltZWludGVsbGlnZW5jZS5pbmZvcm1hLmNvbS9MTDExNDQ2MDYvQ2hpbmEtdHVybnMtdG8tTW9uZ29saWEtZm9yLWNva2luZy1jb2FsLXJlZHVjaW5nLXNoaXBwaW5nLW5lZWRz0gEA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Lloyd's List, 2023-04-04 02:51:16.527421\n- Summary: China's steel and coal demand are expected to remain stable in 2023, driven by infrastructure investments. However, analysts foresee a bleak outlook in the coming decades with the transition to green energy. Weak demand in the property sector has led to a market imbalance, keeping the steel market from fully recovering. China's steel production and consumption are forecasted to reduce by about 200 million tonnes in the next decade as the need for new construction projects diminishes, leading to declining steel demand. China may also consider cutting its crude steel output by around 2.5% this year to reduce CO2 emissions.\n- Keywords: China, steel, coal, demand, green energy, property sector, infrastructure, production control, CO2 emissions\n- Read more: http://news.google.com/./articles/CBMibWh0dHBzOi8vbGxveWRzbGlzdC5tYXJpdGltZWludGVsbGlnZW5jZS5pbmZvcm1hLmNvbS9MTDExNDQ1NzcvQ2hpbmFzLWNvYWwtYW5kLXN0ZWVsLWRlbWFuZC1leHBlY3RlZC10by1yZW1haW7SAQA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Electrek, 2023-04-02 02:51:16.523538\n- Summary: Due to incoming emissions rules, hundreds of thousands of unsold, polluting gas-powered vehicles in China may be rendered unsellable, leading to a looming crisis. The new emissions rules were announced in 2016 and are set to go into effect in July of this year, giving automakers almost seven years of notice to prepare to produce and sell less-polluting vehicles. Automakers seem to have planned to continue selling polluting vehicles up until the deadline, but the COVID-19 pandemic affected both vehicle production and purchases, leading to a glut of unsold vehicles that will become unsellable in July.\n- Keyword: China, emissions rules, gas-powered vehicles, EVs, pollution reduction, automakers, notice, COVID-19, pandemic, unsold vehicles, EV market share, discounts, subsidies, wait-and-see attitude\n- Read more: http://news.google.com/./articles/CBMiZWh0dHBzOi8vZWxlY3RyZWsuY28vMjAyMy8wNC8wMS9pY2UtY2FyLXZhbHVlcy1wbHVtbWV0LWluLWNoaW5hLWFuZC1pdC1pcy10aGUtY2FuYXJ5LWluLXRoZS1jb2FsLW1pbmUv0gEA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Hellenic Shipping News Worldwide , 2023-03-31 02:51:48.205469\n- Summary: China's weak domestic steel demand, coupled with rising steel production and exports, could lead to downward pressure on global steel prices, which could pose trouble for Indian steel exporters. Despite expectations of a rebound in steel demand following the easing of COVID-19 restrictions in China, demand has remained stagnant, leading to higher risks on exports and, thereby, pressure on global steel prices. Nomura Research reported a 70% year-on-year surge in China's steel exports in February 2023, which is the highest since 2017. Meanwhile, Indian steel prices are also showing a mixed trend, with some product categories experiencing stable prices and others facing lower prices. \n- Keyword: China, steel demand, steel production, steel exports, global steel prices, Indian steel exporters\n- Read more: http://news.google.com/./articles/CBMiYGh0dHBzOi8vd3d3LmhlbGxlbmljc2hpcHBpbmduZXdzLmNvbS9jaGluYS1kZW1hbmQtdW5jZXJ0YWludHktY2Fwcy1pbmRpYW4tc3RlZWwtZmlybXMtcHJvc3BlY3RzL9IBAA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n", "doc_id": "85248e83-b41a-4d6f-aca0-c2e5e92ba45d", "embedding": null, "doc_hash": "30a7fd2d5773e5e22fcbdf3e71267e18d65a8c7acfbb17a8ab09f5f74a65f5a5", "extra_info": null, "node_info": {"start": 10279, "end": 14483}, "relationships": {"1": "2eaf905d-22ae-429b-9a6d-385847b544cc", "2": "a2a2a221-0443-4b2b-bc2f-4e1f85005f24"}, "__type__": "1"}, "36208109-aea9-4c40-b18b-5a39debd32fb": {"text": "Summary for Australia:\n\n- Australia's coal and gas exports may reduce by half within the next five years due to the passing of its peak and the efforts of Asian countries to decrease greenhouse gas emissions. The earnings of minerals and energy exports are predicted to reach $464bn in 2022-23 from $128bn in thermal coal exports and $91bn in liquidified natural gas (LNG) exports. These figures have resulted from the global energy crisis caused by Russia's invasion of Ukraine, leading to high fossil fuel prices, causing the replacement of Russian gas with alternative supplies in northern hemisphere nations. \n\n- The seaborne coal market grew by 5.9% year-on-year to 1208 million tonnes in 2022, reversing the negative trend of previous years, according to shipbroker Banchero Costa. Although Australia's coal exports declined by 5% in 2022 due to China's adoption of alternative markets, relations between the two countries have mended and coal shipments are expected to resume. Indonesia is now the largest exporter of coal globally, with a 32.2% share in 2022 compared to Australia's 28.2%. In terms of export destinations, Japan was the top with 115 million tonnes in 2021, while exports to India rose by 13.6% but declined by 3.3% to China.\n \n- Coal producers are in talks with the government of New South Wales, following the government's announcement that coal miners should reserve up to 10% of production for domestic supply to control rising energy costs in Australia. Coal producers are expected to be minimally impacted since spot supplies are limited, limiting requisition under the rule. The state is expanding an existing system that requires some coal-mining companies to reserve supply. Exports of coal are essential to the Australian economy, with 80% of the country's coal exported, yet the move comes as coal prices rise nearly 50% YoY.\n \n- After a year of frozen relations between China and Australia, there is hope for a thaw in their relationship. China\u2019s Foreign Minister, Wang Yi, met with a delegation from Australia to mark the 50th anniversary of bilateral relations and stated that the countries have no \"fundamental conflicts of interest\". Australia is hoping to resume trade in commodities such as iron ore and coking coal. While it is still uncertain whether a trade recovery will happen, analysts and experts from both countries have expressed optimism. Trade data shows that in 2021 China has already imported 694 million tons of iron ore from Australia, accounting for 62% of total imports.\n\nSummary for Indonesia\n\n- Indonesia plans to produce a record 695 million tonnes of coal in 2023 and export 518 million tonnes, according to Energy and Mineral Resources Minister Arifin Tasrif. The country's coal-fired energy supply makes up more than half of its total, though the government aims for a net-zero emissions goal by 2060. Domestic coal consumption in Indonesia is projected at 177 million tonnes this year, down from 193 million tonnes in 2022. \n\n- Global seaborne coal loadings increased by 5.9% year on year to 1204.9 million tonnes in 2022, with Indonesia's exports rising by 21.2% to 388.9 million tonnes, according to shipbroker Banchero Costa. The EU's coal imports jumped 33.9% year on year to 116.5 million tonnes, while India took 13.6% more coal from Indonesia, receiving a total of 203.8 million tonnes. Coal shipments to China fell by 3.2% to 234.7 million tonnes, and Australian exports declined by 5.0%. \n\n- Ford is investing in a $4.5bn nickel processing facility in Indonesia, in partnership with PT Vale Indonesia and China's Zhejiang Huayou Cobalt, in order to secure a supply of nickel needed for EV batteries. The facility is set to begin commercial operations in 2026 and is expected to help Ford achieve its target of producing around two million electric vehicles in that year. China's services sector reached its highest level in over a decade in March, providing a promising signal for the global economy, which is relying on Chinese consumers to drive growth. Coal prices have continued to fall, with Central Appalachian coal down 57% from the start of the year. \n\n- Bank Indonesia predicts that Indonesia's economy will continue to experience strong growth, with a range of 4.5-5.3% expected in 2023, despite global economic uncertainty. The country's economy grew by 5.31% in 2022, driven by a surplus in the trade balance, controlled inflation, increased credit growth, and healthy financial systems. Credit growth is expected to increase by 10-12% in 2023, and increased domestic and export demand are projected to strengthen household consumption and drive economic growth. Non-oil and gas exports, including coal, metal ore, and crude palm oil, have grown rapidly. \n\n- Indonesia aims to produce 694 million tons of coal in 2021 to fulfill both domestic and export demands, said the country's Ministry of Energy and Mineral Resources. Last year, the nation produced 627 million tons, with 4-5 million tons exported to Europe, compared with 500,000 tons in previous years, the Indonesian Coal Mining Association said. European coal demand is expected to stay strong next year.\n\n- Indonesia's new ban on coal exports, implemented to ensure adequate supply for its state-owned electricity companies, is expected to disrupt Supramax and Panamax markets in the Pacific region. The country exports around 400 million metric tonnes of thermal coal each year to countries including China, India, Japan, South Korea and Vietnam. The ban, which has seen bulkers unable to sail out of Indonesian ports, is likely to result in a tonnage surfeit in the Asia-Pacific, leading to lower shipping rates, particularly in East Coast South America (ECSA) and the Indian Ocean. \n\nSummary for China:\n\n- China's coal industry is expected to see stronger trade as the country ramps up energy supplies and stabilizes prices, with coal accounting for 56% of the country's total primary energy consumption. In July 2022, China's coal and lignite exports rose by 171.6% YoY to 230,000 tons. Indonesia is the largest exporter of coal to China, accounting for 58.3% of total imports, followed by Russia at 23.3% and Mongolia at 10%. Australian coal miners are expected to see a pick up in exports to China, while technology and investment from China is set to play a vital role in Indonesian coal deep-processing. \n\n- China's imports of Russian coal rose to 8.54 million tonnes in August due to soaring energy demand caused by extremely hot weather. This trade marked the highest volume since data collection began in 2017, and was up 57% compared with the same period last year. China\u2019s purchase of Russian imports rose by 60% to $11.2bn in August on the back of surging demand for oil, coal and gas. Meanwhile, bilateral trade between China and Russia reached $117.2bn in the first eight months of 2022; up over 31% YoY. \n\n- More than 50% of China's coking coal imports this year are expected to come from Mongolia, according to coking coal analyst Li Xiaoyun from MySteel consultancy. Mongolia is projected to export approximately 40m to 50m tonnes of coking coal to China in 2022, making it the largest coking coal seller to China for the third consecutive year after it replaced Australia in 2021, according to MySteel's data. However, China's total shipments for coking coal imports are expected to have a minor increase due to imports growth from Australia and Indonesia, Li added. \n\n- China added 38.4 GW of new coal-fired power capacity in 2020, more than the rest of the world combined, according to research by US think tank Global Energy Monitor and the Centre for Research on Energy and Clean Air in Helsinki. The capacity increase means China has not been cutting emissions despite last year's pledge by President Xi Jinping that the country would be carbon neutral by 2060. The government has come under criticism from environmentalists for allowing coal-fired plants to be built in polluted regions and developing projects in greener areas more slowly. \n\n- China increased its coal-fired power capacity by 42.9 GW, or 4.5%, in the 18 months to June 2019, according to a report by Global Energy Monitor. The study also found that another 121.3 GW of coal-fired power plants are under construction in China, which has pledged to reduce its coal usage. However, the country\u2019s absolute coal consumption has still increased in line with rising energy demand. China accounts for more than 40% of the world's total coal generation capacity.\n", "doc_id": "36208109-aea9-4c40-b18b-5a39debd32fb", "embedding": null, "doc_hash": "e55b12ed31aceb1a6b09488c2686b3290405c6d682d19a0c3719f1502681f416", "extra_info": null, "node_info": {"start": 0, "end": 8497}, "relationships": {"1": "0d34748a-b2e5-44d2-aac2-14b35088024c"}, "__type__": "1"}}, "ref_doc_info": {"2eaf905d-22ae-429b-9a6d-385847b544cc": {"doc_hash": "1abedf1e096b34455d8e4cd5b65b87329a34b30e8ce3c6fabd000241522e5081"}, "0d34748a-b2e5-44d2-aac2-14b35088024c": {"doc_hash": "e55b12ed31aceb1a6b09488c2686b3290405c6d682d19a0c3719f1502681f416"}, "a2a2a221-0443-4b2b-bc2f-4e1f85005f24": {"doc_hash": "df9164a84395153f16519427f29fd070a839c87627d70f357a3c8754618e7c04"}, "85248e83-b41a-4d6f-aca0-c2e5e92ba45d": {"doc_hash": "30a7fd2d5773e5e22fcbdf3e71267e18d65a8c7acfbb17a8ab09f5f74a65f5a5"}, "36208109-aea9-4c40-b18b-5a39debd32fb": {"doc_hash": "e55b12ed31aceb1a6b09488c2686b3290405c6d682d19a0c3719f1502681f416"}}}}
--------------------------------------------------------------------------------
/storage/docstore.json:
--------------------------------------------------------------------------------
1 | {"docstore/metadata": {"b2e92c51-534e-4234-846b-b339a6a1a46e": {"doc_hash": "1abedf1e096b34455d8e4cd5b65b87329a34b30e8ce3c6fabd000241522e5081"}, "b295a1b4-fb16-4521-8321-71dfbeeb082c": {"doc_hash": "e55b12ed31aceb1a6b09488c2686b3290405c6d682d19a0c3719f1502681f416"}, "28c199fd-b053-4c73-a716-755b0c62292f": {"doc_hash": "14c14e55a7ec3daef87e521f6ecd4cd09a975c6f89fe39304c154ffa99fd297e", "ref_doc_id": "b2e92c51-534e-4234-846b-b339a6a1a46e"}, "af0cde9f-ecdc-46de-9984-c58d0aae6321": {"doc_hash": "b3a95953f5c796422e53597b615dcb7fa88db61620c7b8da4f867ce547735eb7", "ref_doc_id": "b2e92c51-534e-4234-846b-b339a6a1a46e"}, "f3ef8d0e-62e7-4e9c-a897-483fba83d4c9": {"doc_hash": "00e9107bca521bda82a56c6af084d9129375d448820eb58c16f7cb252816fbd9", "ref_doc_id": "b2e92c51-534e-4234-846b-b339a6a1a46e"}, "46972006-a29d-4a6c-ad98-f8ed165a396d": {"doc_hash": "fbed277830610dbc0e80af46966f08f7564d717a7e68580c2f213fd773e10780", "ref_doc_id": "b2e92c51-534e-4234-846b-b339a6a1a46e"}, "88b2d399-9b9c-4f58-bf81-a8a1d6bfded4": {"doc_hash": "35e13b060f2de9f62f20e40d85f6e5ef4f6be2d8050d4356805a4b0fadb07f09", "ref_doc_id": "b2e92c51-534e-4234-846b-b339a6a1a46e"}, "76a9a231-6854-4126-bb78-047b636fd3cb": {"doc_hash": "8ca5715a39afffdd619fabdba1a7a61f4a0a9bb031301c21abdcf9df99b2d03a", "ref_doc_id": "b2e92c51-534e-4234-846b-b339a6a1a46e"}, "8c35b469-89ea-4e11-9b0c-abe7fd4f33ab": {"doc_hash": "855562ce71935d33b8094dc0d8d8d4bd39bf62da841ff76abcdb833d2b2e3deb", "ref_doc_id": "b295a1b4-fb16-4521-8321-71dfbeeb082c"}, "5a8abd59-076b-4570-a2ea-3cac8fd9b03e": {"doc_hash": "29fa5f1c376d8d6fb968984ee4bd4c2c86bf6288de1661ba8b9a029f0a0781fe", "ref_doc_id": "b295a1b4-fb16-4521-8321-71dfbeeb082c"}, "bd48504d-ce9c-4128-a892-e399cc2d7306": {"doc_hash": "39ae7bfb1dea8f3d1790936b0d954858fbdcf73d9f2602ea67160bc592ae4c7a", "ref_doc_id": "b295a1b4-fb16-4521-8321-71dfbeeb082c"}}, "docstore/data": {"28c199fd-b053-4c73-a716-755b0c62292f": {"__data__": {"text": "Summary for Australia\n\n- Source: The Interpreter, 2023-04-05 23:51:16.544469\n- Summary: Pressure is mounting on the Australian government to end public funding for international fossil fuel projects. Australia's major allies, including the US, the UK, Germany, France, and New Zealand, signed the Glasgow Statement last year, committing to ending public support for such projects. However, Australia has refused to commit to this so far. The country's financial support for fossil fuels abroad hasn't changed since May 2022, and the government continues to use taxpayer money to underwrite oil, gas, and coal projects. The Export Finance Australia (EFA), a public financial institution, provides low-interest loans and insurance coverage using government funds to Australian firms looking to offer goods and services overseas. \n- Keyword: Australia, fossil fuels, international funding, Glasgow Statement, taxpayer money, Export Finance Australia. \n- Read more: http://news.google.com/./articles/CBMiYWh0dHBzOi8vd3d3Lmxvd3lpbnN0aXR1dGUub3JnL3RoZS1pbnRlcnByZXRlci9hdXN0cmFsaWEtY2FuLW5vLWxvbmdlci1qdXN0aWZ5LWZvc3NpbC1mdWVsLWZ1bmRpbmfSAQA?hl=en-US&gl=US&ceid=US%3Aen.\n\n\n- Source: The Hindu, 2023-04-01 02:51:43.341293\n- Summary: Australia\u2019s Minister for Trade and Tourism Don Farrell emphasized the importance of diversifying their lithium exports and expanding to India rather than letting everything go to the United States due to the Inflation Reduction Act (IRA) passed in August 2022. The IRA grants major subsidies to US electric vehicles, which the European Union and South Korea criticized. Australia benefits from the IRA, as it is one of the few countries with a free trade agreement (FTA) with the US and the largest reserves of critical minerals, including lithium. However, the clause that at least 40% of critical minerals for electric batteries must come from countries with an FTA with the US could monopolize Australia\u2019s lithium exports. As the world\u2019s biggest exporter of lithium, Australia looks to boost electric vehicle industries by diversifying its markets to countries like India.\n- Keyword: Australia, lithium exports, India, United States, Don Farrell, Minister for Trade and Tourism, Inflation Reduction Act, subsidies, electric vehicles, European Union, South Korea, free trade agreement, critical minerals, lithium-ion battery, China, diversify, opportunities, boost\n- Read more: http://news.google.com/./articles/CBMiemh0dHBzOi8vd3d3LnRoZWhpbmR1LmNvbS9uZXdzL25hdGlvbmFsL2F1c3RyYWxpYS1sb29rcy10by1pbmRpYS1mb3ItZGl2ZXJzaWZpZWQtbGl0aGl1bS1leHBvcnRzLW1hcmtldC9hcnRpY2xlNjY2ODMyMzYuZWNl0gF_aHR0cHM6Ly93d3cudGhlaGluZHUuY29tL25ld3MvbmF0aW9uYWwvYXVzdHJhbGlhLWxvb2tzLXRvLWluZGlhLWZvci1kaXZlcnNpZmllZC1saXRoaXVtLWV4cG9ydHMtbWFya2V0L2FydGljbGU2NjY4MzIzNi5lY2UvYW1wLw?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Green Left, 2023-04-01", "doc_id": "28c199fd-b053-4c73-a716-755b0c62292f", "embedding": null, "doc_hash": "14c14e55a7ec3daef87e521f6ecd4cd09a975c6f89fe39304c154ffa99fd297e", "extra_info": null, "node_info": {"start": 0, "end": 2845, "_node_type": "1"}, "relationships": {"1": "b2e92c51-534e-4234-846b-b339a6a1a46e", "3": "af0cde9f-ecdc-46de-9984-c58d0aae6321"}}, "__type__": "1"}, "af0cde9f-ecdc-46de-9984-c58d0aae6321": {"__data__": {"text": "Source: Green Left, 2023-04-01 02:51:21.433996\n- Climate activists from Move Beyond Coal (MBC) protested outside the Newtown branch of National Australia Bank (NAB) on March 30, as part of the \u201cFLOOD NAB: 10 Days of Action\u201d campaign. Over 40 protests were planned across Australia from March 27 to April 5, calling on NAB to stop funding Whitehaven Coal. MBC stated that NAB\u2019s involvement with Whitehaven Coal, which is planning to double coal production by 2030, is risky and destructive to the environment and First Nations heritage. Activists aim to pressure the bank to rule out further financial support for the company.\n- Keyword: Climate activists, Move Beyond Coal, National Australia Bank, protests, FLOOD NAB: 10 Days of Action, Whitehaven Coal. \n- Read more: http://news.google.com/./articles/CBMiZ2h0dHBzOi8vd3d3LmdyZWVubGVmdC5vcmcuYXUvY29udGVudC9hY3RpdmlzdHMtdGVsbC1uYXRpb25hbC1hdXN0cmFsaWEtYmFuay1lbmQtc3VwcG9ydC13aGl0ZWhhdmVuLWNvYWzSAQA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: ABC News, 2023-03-31 02:51:43.344564\n- Summary: In an extraordinary speech at a federal parliamentary event, the head of Japan's biggest oil and gas producer, Inpex CEO Takayuki Ueda, warned that Australia's decision to \"quietly quit\" the international gas trade risked undermining global security. He suggested that the North Asian country was worried about any potential ripples that could jeopardise its own energy security due to potential unintended consequences resulting from government interventions in Australia's gas industry. Mr Ueda pointed out Japan's heavy reliance on Australia for resources; Ichthys provided about 10 per cent of Japan's liquefied natural gas (LNG) imports, while it was also the country's single biggest investment in Australia. He warned that Australia's efforts to control the gas industry could cause unintended consequences and potentially threaten the rules-based international order essential to the peace, stability and prosperity of the region, if not the world. \n- Keywords: Australia, gas trade, global security, Inpex, natural gas, government interventions, energy security, resources, Ichthys, investment, unintended consequences, rules-based international order\n- Read more: http://news.google.com/./articles/CBMiaGh0dHBzOi8vd3d3LmFiYy5uZXQuYXUvbmV3cy8yMDIzLTAzLTMwL2phcGFuLXdhcm5zLXdvcmxkLXBlYWNlLWF0LXN0YWtlLWluLWF1c3RyYWxpYW4tZ2FzLWV4aXQvMTAyMTY3OTA40gEoaHR0cHM6Ly9hbXAuYWJjLm5ldC5hdS9hcnRpY2xlLzEwMjE2NzkwOA?hl=en-US&gl=US&ceid=US\n\n\n- Source: The Guardian, 2023-03-31 02:51:43.337495\n- Australia's parliament has passed significant emissions reduction legislation, requiring total emissions from major industrial facilities to decrease, not just be offset. The deal includes cuts in emissions intensity by up to 5% per year starting 1 July on the majority of the country's 215 major polluting facilities. While companies can buy an unlimited number of offsets, total absolute emissions cannot increase and must come down over time. This legislation is critical to Australia's commitment to cut national carbon dioxide emissions by 43% by 2030 compared to 2005 levels, and was backed by Greens, a minor party with 15 parliamentarians. The deal is considered a landmark reform by the climate change minister, Chris", "doc_id": "af0cde9f-ecdc-46de-9984-c58d0aae6321", "embedding": null, "doc_hash": "b3a95953f5c796422e53597b615dcb7fa88db61620c7b8da4f867ce547735eb7", "extra_info": null, "node_info": {"start": 2819, "end": 6074, "_node_type": "1"}, "relationships": {"1": "b2e92c51-534e-4234-846b-b339a6a1a46e", "2": "28c199fd-b053-4c73-a716-755b0c62292f", "3": "f3ef8d0e-62e7-4e9c-a897-483fba83d4c9"}}, "__type__": "1"}, "f3ef8d0e-62e7-4e9c-a897-483fba83d4c9": {"__data__": {"text": "The deal is considered a landmark reform by the climate change minister, Chris Bowen, and will result in a 205m tonne reduction in emissions by 2030.\n- Keyword: Australia, emissions reduction legislation, emissions intensity, major polluting facilities, carbon dioxide emissions, Greens, carbon offsets, landmark reform, Chris Bowen, 205m tonne reduction in emissions.\n- Read more: http://news.google.com/./articles/CBMiggFodHRwczovL3d3dy50aGVndWFyZGlhbi5jb20vZW52aXJvbm1lbnQvMjAyMy9tYXIvMzAvYXVzdHJhbGlhLWNsaW1hdGUtZW1pc3Npb25zLXJlZHVjdGlvbi1sZWdpc2xhdGlvbi1sYXdzLXBhcmxpYW1lbnQtbGFib3ItZ3JlZW5z0gGCAWh0dHBzOi8vYW1wLnRoZWd1YXJkaWFuLmNvbS9lbnZpcm9ubWVudC8yMDIzL21hci8zMC9hdXN0cmFsaWEtY2xpbWF0ZS1lbWlzc2lvbnMtcmVkdWN0aW9uLWxlZ2lzbGF0aW9uLWxhd3MtcGFybGlhbWVudC1sYWJvci1ncmVlbnM?hl=en-US&gl=US&ceid=US%3Aen.\n\n\n- Source: DW (English) , 2023-03-31 02:51:21.432038\n- Summary: Australian lawmakers have passed new climate laws that require coal mines, oil refineries and other large polluters to cut their greenhouse gas emissions by about 5% annually. According to the new legislation, which affects 215 major industrial facilities, the targeted emissions reduction will form the basis of Australia's pledge to reach net-zero emissions by 2050. Each facility produces more than 100,000 tons of greenhouse gases annually. The mining industry has cautioned that the financial burden of compliance could lead to massive job cuts.\n- Keyword: Australia, climate laws, greenhouse gas, emissions, coal mines, oil refineries, polluters, net-zero emissions, 215 major industrial facilities, mining industry\n- Read more: http://news.google.com/./articles/CBMiWWh0dHBzOi8vd3d3LmR3LmNvbS9lbi9hdXN0cmFsaWEtcGFzc2VzLXRvdWdoLW5ldy1sYXctb24tY29hbC1nYXMtb2lsLWVtaXNzaW9ucy9hLTY1MTc2OTQw0gFZaHR0cHM6Ly9hbXAuZHcuY29tL2VuL2F1c3RyYWxpYS1wYXNzZXMtdG91Z2gtbmV3LWxhdy1vbi1jb2FsLWdhcy1vaWwtZW1pc\n\n\n\n Summary for Indonesia\n- Source: IEEFA, 2023-04-05 05:52:09.899527\n- Summary: Downstream coal projects in Indonesia require a government subsidy of at least US$354 per tonne of dimethyl ether (DME) fuel to maintain a profit margin, according to the Institute for Energy Economics and Financial Analysis (IEEFA). IEEFA\u2019s calculations suggest that such financial support makes no economic sense to the Indonesian government or taxpayers. Industrial gas company, Air Products and Chemicals withdrew from all downstream coal projects in Indonesia in March 2023, including a DME facility that would convert coal from state-owned supplier, Tambang Batubara Bukit Asam (PTBA). Since 2018, coal gasification to produce DME fuel for Indonesian households has been promoted as an affordable substitute to the country\u2019s", "doc_id": "f3ef8d0e-62e7-4e9c-a897-483fba83d4c9", "embedding": null, "doc_hash": "00e9107bca521bda82a56c6af084d9129375d448820eb58c16f7cb252816fbd9", "extra_info": null, "node_info": {"start": 6036, "end": 8712, "_node_type": "1"}, "relationships": {"1": "b2e92c51-534e-4234-846b-b339a6a1a46e", "2": "af0cde9f-ecdc-46de-9984-c58d0aae6321", "3": "46972006-a29d-4a6c-ad98-f8ed165a396d"}}, "__type__": "1"}, "46972006-a29d-4a6c-ad98-f8ed165a396d": {"__data__": {"text": "households has been promoted as an affordable substitute to the country\u2019s liquefied petroleum gas (LPG) imports. Nonetheless, IEEFA reported that DME prices were cheaper than LPG for only 15 months over a 20-year period.\n- Keyword: government subsidy, dimethyl ether, profit margin, Air Products and Chemicals, withdrawal, downstream coal projects, coal gasification, DME fuel production, Indonesian households, liquefied petroleum gas imports, affordable substitute, PTBA, financial viability, industrial gas company, financial support, no economic sense, Tambang Batubara Bukit Asam.\n- Read more: http://news.google.com/./articles/CBMibGh0dHBzOi8vaWVlZmEub3JnL3Jlc291cmNlcy9rZWVwaW5nLWluZG9uZXNpYXMtZG93bnN0cmVhbS1jb2FsLXByb2plY3RzLWFmbG9hdC13aWxsLXJlcXVpcm\n\n\n\n Summary for China\n\n- Source: Nikkei Asia, 2023-04-05 17:51:16.533368\n- China's three state-owned energy majors, China Petroleum and Chemical Corp (Sinopec), China National Petroleum Corp (CNPC) and China National Offshore Oil Corp (CNOOC), plan to invest over 100 billion yuan ($14.5 billion) in renewable energy through 2025 with the aim of achieving net-zero carbon dioxide emissions by 2060. Sinopec's Chairman, Ma Yongsheng, stated that the company aims to become China's leading company in hydrogen, indicating a strategic move towards clean energy. \n- Keyword: [China's three state-owned energy majors, renewable energy, net-zero carbon dioxide emissions, hydrogen, Sinopec]\n- Read more: http://news.google.com/./articles/CBMiZGh0dHBzOi8vYXNpYS5uaWtrZWkuY29tL0J1c2luZXNzL0VuZXJneS9DaGluYS1zLW9pbC1naWFudHMtdG8taW52ZXN0LW92ZXItMTRibi1pbi1yZW5ld2FibGVzLWJ5LTIwMjXSAQA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Lloyd's List, 2023-04-05 02:51:48.198074\n- Summary: Despite the lifting of a ban on Australian coal cargoes, more than 50% of China's coking coal imports, expected to be around 40m to 50m tonnes, will come from Mongolia this year. This makes Mongolia the largest coking coal seller to China for a third year in a row, according to MySteel data. Mongolia exported 25.61m tonnes of coking coal to China last year, accounting for 40.1% of China's total imports. Enhanced customs clearance efficiency and improved railway transportation contributed to a possible import increase, while the reliance on railway transportation by Mongolia will reduce the need for shipping.\n- Keyword: China, coking coal, Mongolia, imports, exports\n- Read more: http://news.google.com/./articles/CBMifWh0dHBzOi8vbGxveWRzbGlzdC5tYXJpdGltZWludGVsbGlnZW5jZS5pbmZvcm1hLmNvbS9MTDExNDQ2MDYvQ2hpbmEtdHVybnMtdG8tTW9uZ29saWEtZm9yLWNva2luZy1jb2FsLXJlZHVjaW5nLXNoaXBwaW5nLW5lZWRz0gEA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Lloyd's List, 2023-04-04 02:51:16.527421\n- Summary: China's steel and coal demand are expected to remain", "doc_id": "46972006-a29d-4a6c-ad98-f8ed165a396d", "embedding": null, "doc_hash": "fbed277830610dbc0e80af46966f08f7564d717a7e68580c2f213fd773e10780", "extra_info": null, "node_info": {"start": 8716, "end": 11486, "_node_type": "1"}, "relationships": {"1": "b2e92c51-534e-4234-846b-b339a6a1a46e", "2": "f3ef8d0e-62e7-4e9c-a897-483fba83d4c9", "3": "88b2d399-9b9c-4f58-bf81-a8a1d6bfded4"}}, "__type__": "1"}, "88b2d399-9b9c-4f58-bf81-a8a1d6bfded4": {"__data__": {"text": "Summary: China's steel and coal demand are expected to remain stable in 2023, driven by infrastructure investments. However, analysts foresee a bleak outlook in the coming decades with the transition to green energy. Weak demand in the property sector has led to a market imbalance, keeping the steel market from fully recovering. China's steel production and consumption are forecasted to reduce by about 200 million tonnes in the next decade as the need for new construction projects diminishes, leading to declining steel demand. China may also consider cutting its crude steel output by around 2.5% this year to reduce CO2 emissions.\n- Keywords: China, steel, coal, demand, green energy, property sector, infrastructure, production control, CO2 emissions\n- Read more: http://news.google.com/./articles/CBMibWh0dHBzOi8vbGxveWRzbGlzdC5tYXJpdGltZWludGVsbGlnZW5jZS5pbmZvcm1hLmNvbS9MTDExNDQ1NzcvQ2hpbmFzLWNvYWwtYW5kLXN0ZWVsLWRlbWFuZC1leHBlY3RlZC10by1yZW1haW7SAQA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Electrek, 2023-04-02 02:51:16.523538\n- Summary: Due to incoming emissions rules, hundreds of thousands of unsold, polluting gas-powered vehicles in China may be rendered unsellable, leading to a looming crisis. The new emissions rules were announced in 2016 and are set to go into effect in July of this year, giving automakers almost seven years of notice to prepare to produce and sell less-polluting vehicles. Automakers seem to have planned to continue selling polluting vehicles up until the deadline, but the COVID-19 pandemic affected both vehicle production and purchases, leading to a glut of unsold vehicles that will become unsellable in July.\n- Keyword: China, emissions rules, gas-powered vehicles, EVs, pollution reduction, automakers, notice, COVID-19, pandemic, unsold vehicles, EV market share, discounts, subsidies, wait-and-see attitude\n- Read more: http://news.google.com/./articles/CBMiZWh0dHBzOi8vZWxlY3RyZWsuY28vMjAyMy8wNC8wMS9pY2UtY2FyLXZhbHVlcy1wbHVtbWV0LWluLWNoaW5hLWFuZC1pdC1pcy10aGUtY2FuYXJ5LWluLXRoZS1jb2FsLW1pbmUv0gEA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n- Source: Hellenic Shipping News Worldwide , 2023-03-31 02:51:48.205469\n- Summary: China's weak domestic steel demand, coupled with rising steel production and exports, could lead to downward pressure on global steel prices, which could pose trouble for Indian steel exporters. Despite expectations of a rebound in steel demand following the easing of COVID-19 restrictions in China, demand has remained stagnant, leading to higher risks on exports and, thereby, pressure on global steel prices. Nomura Research reported a 70% year-on-year surge in China's steel exports in February 2023, which is the highest since 2017. Meanwhile, Indian steel prices are also showing a mixed trend, with some product categories experiencing stable prices and others facing lower prices. \n- Keyword: China, steel demand, steel production, steel exports, global steel prices, Indian steel exporters\n- Read more:", "doc_id": "88b2d399-9b9c-4f58-bf81-a8a1d6bfded4", "embedding": null, "doc_hash": "35e13b060f2de9f62f20e40d85f6e5ef4f6be2d8050d4356805a4b0fadb07f09", "extra_info": null, "node_info": {"start": 11498, "end": 14473, "_node_type": "1"}, "relationships": {"1": "b2e92c51-534e-4234-846b-b339a6a1a46e", "2": "46972006-a29d-4a6c-ad98-f8ed165a396d", "3": "76a9a231-6854-4126-bb78-047b636fd3cb"}}, "__type__": "1"}, "76a9a231-6854-4126-bb78-047b636fd3cb": {"__data__": {"text": "steel exports, global steel prices, Indian steel exporters\n- Read more: http://news.google.com/./articles/CBMiYGh0dHBzOi8vd3d3LmhlbGxlbmljc2hpcHBpbmduZXdzLmNvbS9jaGluYS1kZW1hbmQtdW5jZXJ0YWludHktY2Fwcy1pbmRpYW4tc3RlZWwtZmlybXMtcHJvc3BlY3RzL9IBAA?hl=en-US&gl=US&ceid=US%3Aen\n\n\n", "doc_id": "76a9a231-6854-4126-bb78-047b636fd3cb", "embedding": null, "doc_hash": "8ca5715a39afffdd619fabdba1a7a61f4a0a9bb031301c21abdcf9df99b2d03a", "extra_info": null, "node_info": {"start": 14454, "end": 14729, "_node_type": "1"}, "relationships": {"1": "b2e92c51-534e-4234-846b-b339a6a1a46e", "2": "88b2d399-9b9c-4f58-bf81-a8a1d6bfded4"}}, "__type__": "1"}, "8c35b469-89ea-4e11-9b0c-abe7fd4f33ab": {"__data__": {"text": "Summary for Australia:\n\n- Australia's coal and gas exports may reduce by half within the next five years due to the passing of its peak and the efforts of Asian countries to decrease greenhouse gas emissions. The earnings of minerals and energy exports are predicted to reach $464bn in 2022-23 from $128bn in thermal coal exports and $91bn in liquidified natural gas (LNG) exports. These figures have resulted from the global energy crisis caused by Russia's invasion of Ukraine, leading to high fossil fuel prices, causing the replacement of Russian gas with alternative supplies in northern hemisphere nations. \n\n- The seaborne coal market grew by 5.9% year-on-year to 1208 million tonnes in 2022, reversing the negative trend of previous years, according to shipbroker Banchero Costa. Although Australia's coal exports declined by 5% in 2022 due to China's adoption of alternative markets, relations between the two countries have mended and coal shipments are expected to resume. Indonesia is now the largest exporter of coal globally, with a 32.2% share in 2022 compared to Australia's 28.2%. In terms of export destinations, Japan was the top with 115 million tonnes in 2021, while exports to India rose by 13.6% but declined by 3.3% to China.\n \n- Coal producers are in talks with the government of New South Wales, following the government's announcement that coal miners should reserve up to 10% of production for domestic supply to control rising energy costs in Australia. Coal producers are expected to be minimally impacted since spot supplies are limited, limiting requisition under the rule. The state is expanding an existing system that requires some coal-mining companies to reserve supply. Exports of coal are essential to the Australian economy, with 80% of the country's coal exported, yet the move comes as coal prices rise nearly 50% YoY.\n \n- After a year of frozen relations between China and Australia, there is hope for a thaw in their relationship. China\u2019s Foreign Minister, Wang Yi, met with a delegation from Australia to mark the 50th anniversary of bilateral relations and stated that the countries have no \"fundamental conflicts of interest\". Australia is hoping to resume trade in commodities such as iron ore and coking coal. While it is still uncertain whether a trade recovery will happen, analysts and experts from both countries have expressed optimism. Trade data shows that in 2021 China has already imported 694 million tons of iron ore from Australia, accounting for 62% of total imports.\n\nSummary for Indonesia\n\n- Indonesia plans to produce a record 695 million tonnes of coal in 2023 and export 518 million tonnes, according to Energy and Mineral Resources Minister Arifin Tasrif. The country's coal-fired energy supply makes up more than half of its total, though the government aims for a net-zero emissions goal by 2060. Domestic coal consumption in Indonesia is projected at 177 million tonnes this year, down from 193 million tonnes in 2022. \n\n- Global seaborne coal loadings increased by 5.9% year on year to 1204.9 million tonnes in 2022, with Indonesia's exports rising by 21.2% to 388.9 million tonnes, according to shipbroker Banchero Costa. The EU's coal imports jumped 33.9% year on year to 116.5 million tonnes, while India took 13.6% more coal from Indonesia, receiving a total of 203.8 million tonnes. Coal shipments to China fell by 3.2% to 234.7 million tonnes, and Australian exports declined by 5.0%. \n\n- Ford is investing in a $4.5bn nickel processing facility in Indonesia, in partnership with PT Vale Indonesia and China's Zhejiang Huayou Cobalt, in order to secure a supply of nickel needed for EV batteries. The facility is set to begin commercial operations in 2026 and is expected to help Ford achieve its target of producing around two million electric vehicles in that year. China's services sector reached its highest level in over a decade in March, providing a promising signal for the global", "doc_id": "8c35b469-89ea-4e11-9b0c-abe7fd4f33ab", "embedding": null, "doc_hash": "855562ce71935d33b8094dc0d8d8d4bd39bf62da841ff76abcdb833d2b2e3deb", "extra_info": null, "node_info": {"start": 0, "end": 3967, "_node_type": "1"}, "relationships": {"1": "b295a1b4-fb16-4521-8321-71dfbeeb082c", "3": "5a8abd59-076b-4570-a2ea-3cac8fd9b03e"}}, "__type__": "1"}, "5a8abd59-076b-4570-a2ea-3cac8fd9b03e": {"__data__": {"text": "highest level in over a decade in March, providing a promising signal for the global economy, which is relying on Chinese consumers to drive growth. Coal prices have continued to fall, with Central Appalachian coal down 57% from the start of the year. \n\n- Bank Indonesia predicts that Indonesia's economy will continue to experience strong growth, with a range of 4.5-5.3% expected in 2023, despite global economic uncertainty. The country's economy grew by 5.31% in 2022, driven by a surplus in the trade balance, controlled inflation, increased credit growth, and healthy financial systems. Credit growth is expected to increase by 10-12% in 2023, and increased domestic and export demand are projected to strengthen household consumption and drive economic growth. Non-oil and gas exports, including coal, metal ore, and crude palm oil, have grown rapidly. \n\n- Indonesia aims to produce 694 million tons of coal in 2021 to fulfill both domestic and export demands, said the country's Ministry of Energy and Mineral Resources. Last year, the nation produced 627 million tons, with 4-5 million tons exported to Europe, compared with 500,000 tons in previous years, the Indonesian Coal Mining Association said. European coal demand is expected to stay strong next year.\n\n- Indonesia's new ban on coal exports, implemented to ensure adequate supply for its state-owned electricity companies, is expected to disrupt Supramax and Panamax markets in the Pacific region. The country exports around 400 million metric tonnes of thermal coal each year to countries including China, India, Japan, South Korea and Vietnam. The ban, which has seen bulkers unable to sail out of Indonesian ports, is likely to result in a tonnage surfeit in the Asia-Pacific, leading to lower shipping rates, particularly in East Coast South America (ECSA) and the Indian Ocean. \n\nSummary for China:\n\n- China's coal industry is expected to see stronger trade as the country ramps up energy supplies and stabilizes prices, with coal accounting for 56% of the country's total primary energy consumption. In July 2022, China's coal and lignite exports rose by 171.6% YoY to 230,000 tons. Indonesia is the largest exporter of coal to China, accounting for 58.3% of total imports, followed by Russia at 23.3% and Mongolia at 10%. Australian coal miners are expected to see a pick up in exports to China, while technology and investment from China is set to play a vital role in Indonesian coal deep-processing. \n\n- China's imports of Russian coal rose to 8.54 million tonnes in August due to soaring energy demand caused by extremely hot weather. This trade marked the highest volume since data collection began in 2017, and was up 57% compared with the same period last year. China\u2019s purchase of Russian imports rose by 60% to $11.2bn in August on the back of surging demand for oil, coal and gas. Meanwhile, bilateral trade between China and Russia reached $117.2bn in the first eight months of 2022; up over 31% YoY. \n\n- More than 50% of China's coking coal imports this year are expected to come from Mongolia, according to coking coal analyst Li Xiaoyun from MySteel consultancy. Mongolia is projected to export approximately 40m to 50m tonnes of coking coal to China in 2022, making it the largest coking coal seller to China for the third consecutive year after it replaced Australia in 2021, according to MySteel's data. However, China's total shipments for coking coal imports are expected to have a minor increase due to imports growth from Australia and Indonesia, Li added. \n\n- China added 38.4 GW of new coal-fired power capacity in 2020, more than the rest of the world combined, according to research by US think tank Global Energy Monitor and the Centre for Research on Energy and Clean Air in Helsinki. The capacity increase means China has not been cutting emissions despite last year's pledge by President Xi Jinping that the country would be carbon neutral by 2060. The government has come under criticism from environmentalists for allowing coal-fired plants to be built in polluted regions and developing projects in greener", "doc_id": "5a8abd59-076b-4570-a2ea-3cac8fd9b03e", "embedding": null, "doc_hash": "29fa5f1c376d8d6fb968984ee4bd4c2c86bf6288de1661ba8b9a029f0a0781fe", "extra_info": null, "node_info": {"start": 3898, "end": 8011, "_node_type": "1"}, "relationships": {"1": "b295a1b4-fb16-4521-8321-71dfbeeb082c", "2": "8c35b469-89ea-4e11-9b0c-abe7fd4f33ab", "3": "bd48504d-ce9c-4128-a892-e399cc2d7306"}}, "__type__": "1"}, "bd48504d-ce9c-4128-a892-e399cc2d7306": {"__data__": {"text": "coal-fired plants to be built in polluted regions and developing projects in greener areas more slowly. \n\n- China increased its coal-fired power capacity by 42.9 GW, or 4.5%, in the 18 months to June 2019, according to a report by Global Energy Monitor. The study also found that another 121.3 GW of coal-fired power plants are under construction in China, which has pledged to reduce its coal usage. However, the country\u2019s absolute coal consumption has still increased in line with rising energy demand. China accounts for more than 40% of the world's total coal generation capacity.\n", "doc_id": "bd48504d-ce9c-4128-a892-e399cc2d7306", "embedding": null, "doc_hash": "39ae7bfb1dea8f3d1790936b0d954858fbdcf73d9f2602ea67160bc592ae4c7a", "extra_info": null, "node_info": {"start": 7997, "end": 8582, "_node_type": "1"}, "relationships": {"1": "b295a1b4-fb16-4521-8321-71dfbeeb082c", "2": "5a8abd59-076b-4570-a2ea-3cac8fd9b03e"}}, "__type__": "1"}}, "docstore/ref_doc_info": {"b2e92c51-534e-4234-846b-b339a6a1a46e": {"doc_ids": ["28c199fd-b053-4c73-a716-755b0c62292f", "af0cde9f-ecdc-46de-9984-c58d0aae6321", "f3ef8d0e-62e7-4e9c-a897-483fba83d4c9", "46972006-a29d-4a6c-ad98-f8ed165a396d", "88b2d399-9b9c-4f58-bf81-a8a1d6bfded4", "76a9a231-6854-4126-bb78-047b636fd3cb"], "extra_info": {}}, "b295a1b4-fb16-4521-8321-71dfbeeb082c": {"doc_ids": ["8c35b469-89ea-4e11-9b0c-abe7fd4f33ab", "5a8abd59-076b-4570-a2ea-3cac8fd9b03e", "bd48504d-ce9c-4128-a892-e399cc2d7306"], "extra_info": {}}}}
--------------------------------------------------------------------------------
/workshop/Generative_AI_Template_02.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "toc_visible": true,
8 | "authorship_tag": "ABX9TyPoaYGXMilXwlJ2bAquvWfk",
9 | "include_colab_link": true
10 | },
11 | "kernelspec": {
12 | "name": "python3",
13 | "display_name": "Python 3"
14 | },
15 | "language_info": {
16 | "name": "python"
17 | }
18 | },
19 | "cells": [
20 | {
21 | "cell_type": "markdown",
22 | "metadata": {
23 | "id": "view-in-github",
24 | "colab_type": "text"
25 | },
26 | "source": [
27 | ""
28 | ]
29 | },
30 | {
31 | "cell_type": "markdown",
32 | "source": [
33 | "# Generative AI for Financial Chatbots Development\n",
34 | "\n",
35 | "Congratulations on making it this far in this self-paced Generative AI lesson series! Before you attempt this challenge, you should complete the workbook to have a baseline understanding of the materials presented in the challenge:\n",
36 | "\n",
37 | "- [Generative AI Series 1: Generative AI for Finance](https://docs.sectors.app/recipes/generative-ai-python/01-background)\n",
38 | "- [Generative AI Series 2: Tool Use and Function Calling for Finance LLMs](https://docs.sectors.app/recipes/generative-ai-python/02-tool-use)\n",
39 | "- [Generative AI Series 3: Structured Output](https://docs.sectors.app/recipes/generative-ai-python/03-structured-output)\n",
40 | "- [Generative AI Series 4: Conversational Tool Use AI](https://docs.sectors.app/recipes/generative-ai-python/04-conversational)\n",
41 | "\n",
42 | "---\n",
43 | "\n",
44 | "## Generative AI Workshop\n",
45 | "\n",
46 | "The materials are specifically designed for the following workshop by Supertype, and it might be beneficial to join the workshop (\\$9, +\\$4 for certification grading, post-workshop support and API credits) for a live-instructor, hands-on experience if you're new to the topics covered.\n",
47 | "\n",
48 | "- [Generative AI for financial chatbots workshop](https://supertype.ai/financial-chatbots/)\n",
49 | "\n",
50 | "## Make a Copy for submission\n",
51 | "Please use File > Save a Copy in Drive to duplicate this assignment template.\n",
52 | "\n",
53 | "When you have completed the challenge, submit it to the GitHub discussion thread for grading! Good luck!\n",
54 | "\n",
55 | "---"
56 | ],
57 | "metadata": {
58 | "id": "pIc-fQk96x90"
59 | }
60 | },
61 | {
62 | "cell_type": "markdown",
63 | "source": [
64 | "## Part 1: Text Extraction AI\n",
65 | "\n",
66 | "For the Challenge in this chapter, we are going to build an AI agent that can (1) extract information from unstructured\n",
67 | "text, (2) run validation checks on the extracted data based on schema constraints and business logic rules, and (3) generate a structured response ready\n",
68 | "for downstream tools to process.\n",
69 | "\n",
70 | "This has many practical applications. You can imagine an assistant chatbot that extract information from loose text such as news,\n",
71 | "press releases, or even user's conversational queries, and then generate structured responses to be fed into a downstream tool. One might\n",
72 | "also imagine a chatbot that allow user to upload a document, extract information, and then perform some actions based on the extracted data.\n",
73 | "\n",
74 | "### 5 Instructions\n",
75 | "There are 5 instructions in total. Each successful implementation earns you 1 point. Successfully running the following cell (`python -m pytest`) with the expectected output earns you another 1 point.\n",
76 | "\n",
77 | "The total score for Part 1 is 6 points."
78 | ],
79 | "metadata": {
80 | "id": "QzCggYTtOLHA"
81 | }
82 | },
83 | {
84 | "cell_type": "code",
85 | "source": [
86 | "!pip install langchain-core\n",
87 | "!pip install langchain-openai\n",
88 | "!pip install langgraph\n",
89 | "!pip install langchain-groq"
90 | ],
91 | "metadata": {
92 | "colab": {
93 | "base_uri": "https://localhost:8080/"
94 | },
95 | "id": "8WOjbPKiBfU6",
96 | "outputId": "30b5e60d-ed76-4357-8b0f-26c6c5475c8d"
97 | },
98 | "execution_count": null,
99 | "outputs": [
100 | {
101 | "output_type": "stream",
102 | "name": "stdout",
103 | "text": [
104 | "Collecting langchain-core\n",
105 | " Downloading langchain_core-0.3.9-py3-none-any.whl.metadata (6.3 kB)\n",
106 | "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain-core) (6.0.2)\n",
107 | "Collecting jsonpatch<2.0,>=1.33 (from langchain-core)\n",
108 | " Downloading jsonpatch-1.33-py2.py3-none-any.whl.metadata (3.0 kB)\n",
109 | "Collecting langsmith<0.2.0,>=0.1.125 (from langchain-core)\n",
110 | " Downloading langsmith-0.1.131-py3-none-any.whl.metadata (13 kB)\n",
111 | "Requirement already satisfied: packaging<25,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core) (24.1)\n",
112 | "Requirement already satisfied: pydantic<3.0.0,>=2.5.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core) (2.9.2)\n",
113 | "Collecting tenacity!=8.4.0,<9.0.0,>=8.1.0 (from langchain-core)\n",
114 | " Downloading tenacity-8.5.0-py3-none-any.whl.metadata (1.2 kB)\n",
115 | "Requirement already satisfied: typing-extensions>=4.7 in /usr/local/lib/python3.10/dist-packages (from langchain-core) (4.12.2)\n",
116 | "Collecting jsonpointer>=1.9 (from jsonpatch<2.0,>=1.33->langchain-core)\n",
117 | " Downloading jsonpointer-3.0.0-py2.py3-none-any.whl.metadata (2.3 kB)\n",
118 | "Collecting httpx<1,>=0.23.0 (from langsmith<0.2.0,>=0.1.125->langchain-core)\n",
119 | " Downloading httpx-0.27.2-py3-none-any.whl.metadata (7.1 kB)\n",
120 | "Collecting orjson<4.0.0,>=3.9.14 (from langsmith<0.2.0,>=0.1.125->langchain-core)\n",
121 | " Downloading orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (50 kB)\n",
122 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m50.4/50.4 kB\u001b[0m \u001b[31m2.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
123 | "\u001b[?25hRequirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langsmith<0.2.0,>=0.1.125->langchain-core) (2.32.3)\n",
124 | "Collecting requests-toolbelt<2.0.0,>=1.0.0 (from langsmith<0.2.0,>=0.1.125->langchain-core)\n",
125 | " Downloading requests_toolbelt-1.0.0-py2.py3-none-any.whl.metadata (14 kB)\n",
126 | "Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3.0.0,>=2.5.2->langchain-core) (0.7.0)\n",
127 | "Requirement already satisfied: pydantic-core==2.23.4 in /usr/local/lib/python3.10/dist-packages (from pydantic<3.0.0,>=2.5.2->langchain-core) (2.23.4)\n",
128 | "Requirement already satisfied: anyio in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.125->langchain-core) (3.7.1)\n",
129 | "Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.125->langchain-core) (2024.8.30)\n",
130 | "Collecting httpcore==1.* (from httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.125->langchain-core)\n",
131 | " Downloading httpcore-1.0.6-py3-none-any.whl.metadata (21 kB)\n",
132 | "Requirement already satisfied: idna in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.125->langchain-core) (3.10)\n",
133 | "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.125->langchain-core) (1.3.1)\n",
134 | "Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.125->langchain-core)\n",
135 | " Downloading h11-0.14.0-py3-none-any.whl.metadata (8.2 kB)\n",
136 | "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langsmith<0.2.0,>=0.1.125->langchain-core) (3.3.2)\n",
137 | "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langsmith<0.2.0,>=0.1.125->langchain-core) (2.2.3)\n",
138 | "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio->httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.125->langchain-core) (1.2.2)\n",
139 | "Downloading langchain_core-0.3.9-py3-none-any.whl (401 kB)\n",
140 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m401.8/401.8 kB\u001b[0m \u001b[31m10.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
141 | "\u001b[?25hDownloading jsonpatch-1.33-py2.py3-none-any.whl (12 kB)\n",
142 | "Downloading langsmith-0.1.131-py3-none-any.whl (294 kB)\n",
143 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m294.6/294.6 kB\u001b[0m \u001b[31m16.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
144 | "\u001b[?25hDownloading tenacity-8.5.0-py3-none-any.whl (28 kB)\n",
145 | "Downloading httpx-0.27.2-py3-none-any.whl (76 kB)\n",
146 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m76.4/76.4 kB\u001b[0m \u001b[31m6.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
147 | "\u001b[?25hDownloading httpcore-1.0.6-py3-none-any.whl (78 kB)\n",
148 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m78.0/78.0 kB\u001b[0m \u001b[31m5.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
149 | "\u001b[?25hDownloading jsonpointer-3.0.0-py2.py3-none-any.whl (7.6 kB)\n",
150 | "Downloading orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (141 kB)\n",
151 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m141.9/141.9 kB\u001b[0m \u001b[31m10.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
152 | "\u001b[?25hDownloading requests_toolbelt-1.0.0-py2.py3-none-any.whl (54 kB)\n",
153 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m54.5/54.5 kB\u001b[0m \u001b[31m3.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
154 | "\u001b[?25hDownloading h11-0.14.0-py3-none-any.whl (58 kB)\n",
155 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m4.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
156 | "\u001b[?25hInstalling collected packages: tenacity, orjson, jsonpointer, h11, requests-toolbelt, jsonpatch, httpcore, httpx, langsmith, langchain-core\n",
157 | " Attempting uninstall: tenacity\n",
158 | " Found existing installation: tenacity 9.0.0\n",
159 | " Uninstalling tenacity-9.0.0:\n",
160 | " Successfully uninstalled tenacity-9.0.0\n",
161 | "Successfully installed h11-0.14.0 httpcore-1.0.6 httpx-0.27.2 jsonpatch-1.33 jsonpointer-3.0.0 langchain-core-0.3.9 langsmith-0.1.131 orjson-3.10.7 requests-toolbelt-1.0.0 tenacity-8.5.0\n",
162 | "Collecting langchain-openai\n",
163 | " Downloading langchain_openai-0.2.2-py3-none-any.whl.metadata (2.6 kB)\n",
164 | "Requirement already satisfied: langchain-core<0.4.0,>=0.3.9 in /usr/local/lib/python3.10/dist-packages (from langchain-openai) (0.3.9)\n",
165 | "Collecting openai<2.0.0,>=1.40.0 (from langchain-openai)\n",
166 | " Downloading openai-1.51.0-py3-none-any.whl.metadata (24 kB)\n",
167 | "Collecting tiktoken<1,>=0.7 (from langchain-openai)\n",
168 | " Downloading tiktoken-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)\n",
169 | "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.9->langchain-openai) (6.0.2)\n",
170 | "Requirement already satisfied: jsonpatch<2.0,>=1.33 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.9->langchain-openai) (1.33)\n",
171 | "Requirement already satisfied: langsmith<0.2.0,>=0.1.125 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.9->langchain-openai) (0.1.131)\n",
172 | "Requirement already satisfied: packaging<25,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.9->langchain-openai) (24.1)\n",
173 | "Requirement already satisfied: pydantic<3.0.0,>=2.5.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.9->langchain-openai) (2.9.2)\n",
174 | "Requirement already satisfied: tenacity!=8.4.0,<9.0.0,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.9->langchain-openai) (8.5.0)\n",
175 | "Requirement already satisfied: typing-extensions>=4.7 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.9->langchain-openai) (4.12.2)\n",
176 | "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.40.0->langchain-openai) (3.7.1)\n",
177 | "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai<2.0.0,>=1.40.0->langchain-openai) (1.7.0)\n",
178 | "Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.40.0->langchain-openai) (0.27.2)\n",
179 | "Collecting jiter<1,>=0.4.0 (from openai<2.0.0,>=1.40.0->langchain-openai)\n",
180 | " Downloading jiter-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.6 kB)\n",
181 | "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.40.0->langchain-openai) (1.3.1)\n",
182 | "Requirement already satisfied: tqdm>4 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.40.0->langchain-openai) (4.66.5)\n",
183 | "Requirement already satisfied: regex>=2022.1.18 in /usr/local/lib/python3.10/dist-packages (from tiktoken<1,>=0.7->langchain-openai) (2024.9.11)\n",
184 | "Requirement already satisfied: requests>=2.26.0 in /usr/local/lib/python3.10/dist-packages (from tiktoken<1,>=0.7->langchain-openai) (2.32.3)\n",
185 | "Requirement already satisfied: idna>=2.8 in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai<2.0.0,>=1.40.0->langchain-openai) (3.10)\n",
186 | "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai<2.0.0,>=1.40.0->langchain-openai) (1.2.2)\n",
187 | "Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai<2.0.0,>=1.40.0->langchain-openai) (2024.8.30)\n",
188 | "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai<2.0.0,>=1.40.0->langchain-openai) (1.0.6)\n",
189 | "Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.10/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai<2.0.0,>=1.40.0->langchain-openai) (0.14.0)\n",
190 | "Requirement already satisfied: jsonpointer>=1.9 in /usr/local/lib/python3.10/dist-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.4.0,>=0.3.9->langchain-openai) (3.0.0)\n",
191 | "Requirement already satisfied: orjson<4.0.0,>=3.9.14 in /usr/local/lib/python3.10/dist-packages (from langsmith<0.2.0,>=0.1.125->langchain-core<0.4.0,>=0.3.9->langchain-openai) (3.10.7)\n",
192 | "Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in /usr/local/lib/python3.10/dist-packages (from langsmith<0.2.0,>=0.1.125->langchain-core<0.4.0,>=0.3.9->langchain-openai) (1.0.0)\n",
193 | "Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3.0.0,>=2.5.2->langchain-core<0.4.0,>=0.3.9->langchain-openai) (0.7.0)\n",
194 | "Requirement already satisfied: pydantic-core==2.23.4 in /usr/local/lib/python3.10/dist-packages (from pydantic<3.0.0,>=2.5.2->langchain-core<0.4.0,>=0.3.9->langchain-openai) (2.23.4)\n",
195 | "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests>=2.26.0->tiktoken<1,>=0.7->langchain-openai) (3.3.2)\n",
196 | "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.26.0->tiktoken<1,>=0.7->langchain-openai) (2.2.3)\n",
197 | "Downloading langchain_openai-0.2.2-py3-none-any.whl (49 kB)\n",
198 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.7/49.7 kB\u001b[0m \u001b[31m2.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
199 | "\u001b[?25hDownloading openai-1.51.0-py3-none-any.whl (383 kB)\n",
200 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m383.5/383.5 kB\u001b[0m \u001b[31m11.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
201 | "\u001b[?25hDownloading tiktoken-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.2 MB)\n",
202 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.2/1.2 MB\u001b[0m \u001b[31m39.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
203 | "\u001b[?25hDownloading jiter-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (318 kB)\n",
204 | "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m318.9/318.9 kB\u001b[0m \u001b[31m12.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
205 | "\u001b[?25hInstalling collected packages: jiter, tiktoken, openai, langchain-openai\n",
206 | "Successfully installed jiter-0.5.0 langchain-openai-0.2.2 openai-1.51.0 tiktoken-0.8.0\n"
207 | ]
208 | }
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": null,
214 | "metadata": {
215 | "colab": {
216 | "base_uri": "https://localhost:8080/"
217 | },
218 | "id": "W7LpQdAG6beJ",
219 | "outputId": "c117517b-ce94-46dc-b9d8-225bad5c3b07"
220 | },
221 | "outputs": [
222 | {
223 | "output_type": "stream",
224 | "name": "stdout",
225 | "text": [
226 | "Overwriting test_parser.py\n"
227 | ]
228 | }
229 | ],
230 | "source": [
231 | "%%file test_parser.py\n",
232 | "\n",
233 | "from typing import Optional\n",
234 | "import pytest\n",
235 | "\n",
236 | "from pydantic import BaseModel, Field, model_validator\n",
237 | "\n",
238 | "from langchain_core.output_parsers import PydanticOutputParser\n",
239 | "from langchain_core.prompts import PromptTemplate\n",
240 | "\n",
241 | "from langchain_core.exceptions import OutputParserException\n",
242 | "\n",
243 | "# 1. bring in your llm\n",
244 | "# llm = ...\n",
245 | "\n",
246 | "\n",
247 | "class Stock(BaseModel):\n",
248 | " \"\"\"Information about a company's stock\"\"\"\n",
249 | "\n",
250 | " symbol: str = Field(description=\"The stock symbol\")\n",
251 | " name: str = Field(\n",
252 | " description=\"The name of the company for which the stock symbol represents\"\n",
253 | " )\n",
254 | " sector: Optional[str] = Field(default=None, description=\"The sector of the company\")\n",
255 | " # 2. implement the other fields\n",
256 | " # ...\n",
257 | "\n",
258 | " @model_validator(mode=\"before\")\n",
259 | " @classmethod\n",
260 | " def validate_symbol_4_letters(cls, values: dict) -> dict:\n",
261 | " # 3. implement LLM validation logic\n",
262 | " # ...\n",
263 | " pass\n",
264 | "\n",
265 | "parser = PydanticOutputParser(pydantic_object=Stock)\n",
266 | "\n",
267 | "prompt = PromptTemplate(\n",
268 | " template=\"Answer the user query.\\n{format_instructions}\\n{query}\\n\",\n",
269 | " input_variables=[\"query\"],\n",
270 | " partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
271 | ")\n",
272 | "\n",
273 | "runnable = prompt | llm | parser\n",
274 | "\n",
275 | "\n",
276 | "class TestParser:\n",
277 | " def test_output_parser_symbol_valid(self):\n",
278 | " text = \"\"\"\n",
279 | " Bank Central Asia (BBCA) is a bank in Indonesia and is part of the finance sector.\n",
280 | " It is in the banking industry and has a market capitalization of $8.5 billion.\n",
281 | " \"\"\"\n",
282 | " # 4. implement when symbol and market cap (and other fields) are all valid\n",
283 | " ...\n",
284 | "\n",
285 | "\n",
286 | " def test_output_parser_symbol_invalid(self):\n",
287 | " text = \"\"\"\n",
288 | " Bank Central Asia (BCA) is a bank in Indonesia and is part of the finance sector.\n",
289 | " It is in the banking industry and has a market capitalization of $8.5 billion.\n",
290 | " \"\"\"\n",
291 | "\n",
292 | " # assert exception is raised when the symbol is not 4 letters long\n",
293 | " with pytest.raises(OutputParserException):\n",
294 | " out = runnable.invoke(text)\n",
295 | "\n",
296 | " def test_output_parser_mcap_invalid(self):\n",
297 | " text = \"\"\"\n",
298 | " Bank Central Asia (BBCA) is a bank in Indonesia and is part of the finance sector.\n",
299 | " It is in the banking industry and has a market capitalization of $-8.5 billion.\n",
300 | " \"\"\"\n",
301 | "\n",
302 | " # 5. assert exception is raised when extraction task fail by detecting <0 market cap\n",
303 | " # ...\n",
304 | "\n"
305 | ]
306 | },
307 | {
308 | "cell_type": "code",
309 | "source": [
310 | "import os\n",
311 | "from google.colab import userdata\n",
312 | "\n",
313 | "os.environ[\"OPENAI_API_KEY\"] = userdata.get('OPENAI_API_KEY')\n",
314 | "\n",
315 | "# 6. run this with 3 passes\n",
316 | "!python -m pytest test_parser.py"
317 | ],
318 | "metadata": {
319 | "colab": {
320 | "base_uri": "https://localhost:8080/"
321 | },
322 | "id": "9OkGEO2mBynI",
323 | "outputId": "f892bbd1-3760-4e2a-9ff8-3a05fefcf9b1"
324 | },
325 | "execution_count": null,
326 | "outputs": [
327 | {
328 | "output_type": "stream",
329 | "name": "stdout",
330 | "text": [
331 | "\u001b[1m======================================= test session starts ========================================\u001b[0m\n",
332 | "platform linux -- Python 3.10.12, pytest-7.4.4, pluggy-1.5.0\n",
333 | "rootdir: /content\n",
334 | "plugins: anyio-3.7.1, typeguard-4.3.0\n",
335 | "collected 3 items \u001b[0m\n",
336 | "\n",
337 | "test_parser.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m [100%]\u001b[0m\n",
338 | "\n",
339 | "\u001b[32m======================================== \u001b[32m\u001b[1m3 passed\u001b[0m\u001b[32m in 4.90s\u001b[0m\u001b[32m =========================================\u001b[0m\n"
340 | ]
341 | }
342 | ]
343 | },
344 | {
345 | "cell_type": "markdown",
346 | "source": [
347 | "- Do not alter any of the `text` prompt. Doing so invalidatest the purpose of the quiz / challenge.\n",
348 | "- Each correct implementation gets you 1 point. Successfully executing the cell above (`python -m pytest test_parser.py`) with the expected output gets you another 1 point. You get a total of 5+1 points from this section above. "
349 | ],
350 | "metadata": {
351 | "id": "q-OdgLRmNHf6"
352 | }
353 | },
354 | {
355 | "cell_type": "markdown",
356 | "source": [
357 | "## Part 2: A LangGraph ReAct Agent with retriever tools"
358 | ],
359 | "metadata": {
360 | "id": "dF20ZU6qQZBB"
361 | }
362 | },
363 | {
364 | "cell_type": "code",
365 | "source": [
366 | "import json\n",
367 | "import requests\n",
368 | "from typing import List\n",
369 | "\n",
370 | "from langchain_core.tools import tool\n",
371 | "from langgraph.prebuilt import create_react_agent\n",
372 | "from langchain_groq import ChatGroq\n",
373 | "from langchain_core.messages import HumanMessage\n",
374 | "\n",
375 | "\n",
376 | "SECTORS_API_KEY = userdata.get('SECTORS_API_KEY')\n",
377 | "GROQ_API_KEY = userdata.get('GROQ_API_KEY')\n",
378 | "\n",
379 | "def retrieve_from_endpoint(url: str) -> dict:\n",
380 | " headers = {\"Authorization\": SECTORS_API_KEY}\n",
381 | "\n",
382 | " try:\n",
383 | " response = requests.get(url, headers=headers)\n",
384 | " response.raise_for_status()\n",
385 | " data = response.json()\n",
386 | " except requests.exceptions.HTTPError as err:\n",
387 | " raise SystemExit(err)\n",
388 | " return json.dumps(data)\n",
389 | "\n",
390 | "\n",
391 | "@tool\n",
392 | "def get_company_overview(stock: str) -> str:\n",
393 | " \"\"\"\n",
394 | " Get company overview\n",
395 | "\n",
396 | " @param stock: The stock symbol of the company\n",
397 | " @return: The company overview\n",
398 | " \"\"\"\n",
399 | "\n",
400 | " url = f\"https://api.sectors.app/v1/company/report/{stock}/?sections=overview\"\n",
401 | " return retrieve_from_endpoint(url)\n",
402 | "\n",
403 | "@tool\n",
404 | "def get_top_companies_ranked(dimension: str) -> List[str]:\n",
405 | " # 7. implement this tool correctly, using the tool implementation above as reference\n",
406 | " pass\n",
407 | "\n",
408 | "\n",
409 | "llm = ChatGroq(\n",
410 | " temperature=0,\n",
411 | " model_name=\"llama3-groq-70b-8192-tool-use-preview\",\n",
412 | " groq_api_key=GROQ_API_KEY,\n",
413 | ")\n",
414 | "\n",
415 | "tools = [\n",
416 | " get_company_overview,\n",
417 | " get_top_companies_ranked,\n",
418 | "]\n",
419 | "\n",
420 | "# 8: ask that floating numbers are returned in 2 decimal points so the result is prettier\n",
421 | "# return full company name, symbol, and the value (in the case of companies by p/e values, return the p/e\n",
422 | "# but in 2 decimal points)\n",
423 | "system_message = \"\"\n",
424 | "\n",
425 | "\n",
426 | "# 9: implement the below correctly, with llm, tools, and system_message as state modifier\n",
427 | "app = create_react_agent()\n",
428 | "\n",
429 | "def query_app(text: str) -> str:\n",
430 | " out = app.invoke(\n",
431 | " {\n",
432 | " \"messages\": [\n",
433 | " HumanMessage(text),\n",
434 | " ]\n",
435 | " }\n",
436 | " )\n",
437 | " # return out[\"messages\"][-1].content\n",
438 | " return out[\"messages\"]\n",
439 | "\n",
440 | "out_agent = query_app(\n",
441 | " \"Get me the top 7 companies based on P/E values, along with their full company name and PE values\"\n",
442 | ")\n",
443 | "\n",
444 | "print(out_agent[-1].content)\n"
445 | ],
446 | "metadata": {
447 | "id": "8O6pqWWdQXKp"
448 | },
449 | "execution_count": null,
450 | "outputs": []
451 | },
452 | {
453 | "cell_type": "code",
454 | "source": [
455 | "# 10: follow up now with a second question, to get the overview of whichever symbol\n",
456 | "# is 4th on the list above in `out_agent`\n",
457 | "\n",
458 | "out_agent2 = ...\n",
459 | "\n",
460 | "print(out_agent2[-1].content)"
461 | ],
462 | "metadata": {
463 | "id": "FQCjhNK8lpEl"
464 | },
465 | "execution_count": null,
466 | "outputs": []
467 | },
468 | {
469 | "cell_type": "markdown",
470 | "source": [
471 | "## Conclusion\n",
472 | "\n",
473 | "Congratulations on making your way through the challenges. My hope is that you find the session educational and fun, and I have, in my own way, inspired you to dive deeper into the exciting world of building financial chat agents using information retriever tools!\n",
474 | "\n",
475 | "Please submit your work at the GitHub repository discussion thread for grading. If you score 8/10 you will obtain a certification jointly issued by Supertype and Sectors.\n",
476 | "\n",
477 | "If you need help, please reach out to us on Discord (exclusively for Practicum members).\n",
478 | "\n",
479 | "Thank you again for your participation, and I hope you walked away with lots of new ideas on what to build next!"
480 | ],
481 | "metadata": {
482 | "id": "wwzLbPLfl-G2"
483 | }
484 | }
485 | ]
486 | }
--------------------------------------------------------------------------------