┏━━━━━━━━━━━┳━━━━━━━━━━━┓\n┃ species ┃ island ┃\n┡━━━━━━━━━━━╇━━━━━━━━━━━┩\n│ string │ string │\n├───────────┼───────────┤\n│ Adelie │ Torgersen │\n│ Adelie │ Biscoe │\n│ Adelie │ Dream │\n│ Gentoo │ Biscoe │\n│ Chinstrap │ Dream │\n└───────────┴───────────┘\n\n```\n:::\n:::\n\n\nI would argue in this case there wasn't any real **computation** done by our\n**calls** to the Ibis table -- we were just retrieving some relatively static\nmetadata -- but we could have done some more complex computations (on any of 18+\ndata platforms).\n\n## Thought leadership\n\nTODO: human rewrite\n\nIn the realm of Generative AI, particularly when working with Language Learning\nModels (LLMs), understanding the concept of 'context' is crucial. Context, in this\ndomain, refers to the inputs that are fed into an LLM, and the corresponding\noutputs they generate. This post breaks down the complexities of this process into\nunderstandable fragments, including retrieval of context, its augmentation, and,\nthereafter, the generation of a response.\n\nAn illustrative example is provided, showcasing a database interaction. It\ndemonstrates how the data retrieved can be used to augment the context before the\nbot generates a response. This valuable insight underlines the practical\napplication of the theory, reinforcing the understanding of the readers.\n\nWe also venture into the difference between simple static metadata retrieval and\nthe more intricate computations. This distinction echoes the breadth and depth of\nthe processes involved in Generative AI.\n\nAs we continue to explore and unravel the potential of Generative AI and LLMs,\nthis post serves as a fundamental building block. It creates a pathway for\nenthusiasts and professionals alike to delve deeper into this exciting field. By\nbreaking down complex concepts into comprehensible segments, it fosters an\nenvironment of learning and growth.\n\nThis marks just the beginning of our journey into the world of Generative AI. As\nwe dig deeper, we will continue to explore, learn and share with our readers. Stay\ntuned for more insightful content in this series. [1]\n\n[1] https://github.com/ibis-project/ibis-birdbrain\n\n## Next steps\n\nYou can get involved with [Ibis\nBirdbrain](https://github.com/ibis-project/ibis-birdbrain), our open-source data\n& AI project for building next-generation natural language interfaces to data.\n\n[Read the next post in this series](../llms-and-data-pt3).\n\n", 5 | "supporting": [ 6 | "index_files/figure-html" 7 | ], 8 | "filters": [], 9 | "includes": { 10 | "include-in-header": [ 11 | "\n\n\n" 12 | ] 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /docs/posts/llms-and-data-pt0/index.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "An introduction to Marvin and Ibis" 3 | author: "Cody Peterson" 4 | date: "2023-10-12" 5 | execute: 6 | warning: false 7 | categories: 8 | - "LLMs and data" 9 | --- 10 | 11 | ## Introduction 12 | 13 | In this "LLMs and data" series, we'll explore how to apply large-language models 14 | (LLMs) to data analytics. We'll walk through the steps to build Ibis Birdbrain. 15 | 16 | Throughout the series, we'll be using 17 | [Marvin](https://www.askmarvin.ai/welcome/overview/) and 18 | [Ibis](https://ibis-project.org). A brief introduction to each is provided 19 | below. 20 | 21 | ## Marvin 22 | 23 | [Marvin](https://www.askmarvin.ai/welcome/overview/) is an AI engineering 24 | framework that makes it easy to build up to an interactive conversational 25 | application. 26 | 27 | Marvin makes calls to an AI platform. You typically use an API key set as an 28 | environment variable -- in this case, we'll load a `.env` file that contians 29 | secrets for the AI platform that Marvin will use. We also set the large language 30 | model model. 31 | 32 | ```{python} 33 | import marvin # <1> 34 | 35 | from rich import print # <1> 36 | from time import sleep # <1> 37 | from dotenv import load_dotenv # <1> 38 | 39 | load_dotenv() # <2> 40 | 41 | # increase accuracy 42 | marvin.settings.llm_model = "openai/gpt-4" # <3> 43 | # decrease cost 44 | # marvin.settings.llm_model = "openai/gpt-3.5-turbo" # <3> 45 | 46 | test_str = "working with data and LLMs on 18+ data platforms is easy!" # <4> 47 | test_str 48 | ``` 49 | 50 | 1. Import the libraries we need. 51 | 2. Load the environment variable to setup Marvin to call our OpenAI account. 52 | 3. Configure the LLM model to use. 53 | 4. Some text to test on 54 | 55 | ### Functions 56 | 57 | AI functions are one of the building blocks in Marvin and allow yout to specify 58 | a typed python function with no code -- only a docstring -- to achieve a wide 59 | variety of tasks. 60 | 61 | We'll demonstrate this with an AI function that trnaslates text: 62 | 63 | ```{python} 64 | @marvin.ai_fn 65 | def translate(text: str, from_: str = "English", to: str = "Spanish") -> str: 66 | """translates the text""" 67 | 68 | translate(test_str) 69 | ``` 70 | 71 | ```{python} 72 | # | code-fold: true 73 | sleep(1) # <1> 74 | ``` 75 | 76 | 1. Avoid rate-limiting by waiting. 77 | 78 | ```{python} 79 | translate(translate(test_str), from_="Spanish", to="English") 80 | ``` 81 | 82 | ```{python} 83 | # | code-fold: true 84 | sleep(3) # <1> 85 | ``` 86 | 87 | 1. Avoid rate-limiting by waiting. 88 | 89 | ### Models 90 | 91 | AI models are another building block for generating python classes from input 92 | text. It's a great way to build structured data from unstructured data that can 93 | be customized for your needs. 94 | 95 | We'll demosntrate this with an AI model that translates text: 96 | 97 | ```{python} 98 | from pydantic import BaseModel, Field 99 | 100 | # decrease cost 101 | marvin.settings.llm_model = "openai/gpt-3.5-turbo" 102 | 103 | @marvin.ai_model 104 | class ExtractParts(BaseModel): 105 | """Extracts parts of a sentence""" 106 | subject: str = Field(..., description="The subject of the sentence.") 107 | objects: list[str] = Field(..., description="The objects of the sentence.") 108 | predicate: str = Field(..., description="The predicate of the sentence.") 109 | modifiers: list[str] = Field(..., description="The modifiers of the sentence.") 110 | 111 | ExtractParts(test_str) 112 | ``` 113 | 114 | ```{python} 115 | # | code-fold: true 116 | sleep(1) # <1> 117 | ``` 118 | 119 | 1. Avoid rate-limiting by waiting. 120 | 121 | ### Classifiers 122 | 123 | AI classifiers are another building block for generating python classes from 124 | input text. It's the most efficient (time and cost) method for applying LLMs as 125 | it only results in a single output token, selecting an output in a specified 126 | Enum. 127 | 128 | We'll demonstrate this by classifying the language of some text: 129 | 130 | ```{python} 131 | from enum import Enum 132 | 133 | # increase accuracy 134 | marvin.settings.llm_model = "openai/gpt-4" 135 | 136 | @marvin.ai_classifier 137 | class IdentifyLanguage(Enum): 138 | """Identifies the language of the text""" 139 | 140 | english = "English" 141 | spanish = "Spanish" 142 | 143 | 144 | IdentifyLanguage(test_str).value 145 | ``` 146 | 147 | ```{python} 148 | # | code-fold: true 149 | sleep(1) # <1> 150 | ``` 151 | 152 | 1. Avoid rate-limiting by waiting. 153 | 154 | ```{python} 155 | IdentifyLanguage(translate(test_str)).value 156 | ``` 157 | 158 | ```{python} 159 | # | code-fold: true 160 | sleep(3) # <1> 161 | ``` 162 | 163 | 1. Avoid rate-limiting by waiting. 164 | 165 | ## Ibis 166 | 167 | [Ibis](https://ibis-project.org) is the portable Python dataframe library that 168 | enables Ibis Birdbrain to work on many data platforms at native scale. 169 | 170 | Ibis makes calls to a data platform, providing an API but pushing the compute to 171 | (local or remote) query engines and storage. DuckDB is the default and we'll 172 | typically use it for demo puroses. You can work with an in-memory instance, but 173 | we'll often create a database file from example data: 174 | 175 | ```{python} 176 | import ibis # <1> 177 | 178 | con = ibis.connect("duckdb://penguins.ddb") # <2> 179 | t = ibis.examples.penguins.fetch() # <2> 180 | t = con.create_table("penguins", t.to_pyarrow(), overwrite=True) # <2> 181 | ``` 182 | 183 | 1. Import the libraries we need. 184 | 2. Setup the demo datain an Ibis backend. 185 | 186 | You will typically connect to an existing data platform via your corresponding 187 | Ibis backend and have access to a number of tables: 188 | 189 | ```{python} 190 | import ibis # <1> 191 | 192 | ibis.options.interactive = True # <2> 193 | 194 | con = ibis.connect("duckdb://penguins.ddb") # <3> 195 | t = con.table("penguins") # <3> 196 | ``` 197 | 198 | 1. Import Ibis. 199 | 2. Configure Ibis (interactive). 200 | 3. Connect to the data and load a table into a variable. 201 | 202 | ### Backend 203 | 204 | A backend provides the connection and basic management of the data platform. 205 | Above, we created the `con` variable that is an instance of a DuckDB backend: 206 | 207 | ```{python} 208 | con 209 | ``` 210 | 211 | It usually contains some tables: 212 | 213 | ```{python} 214 | con.list_tables() 215 | ``` 216 | 217 | We can access some internals of Ibis to see what backends are available: 218 | 219 | ::: {.callout-tip} 220 | Don't rely on accessing internals of Ibis in production. 221 | ::: 222 | 223 | ```{python} 224 | backends = [entrypoint.name for entrypoint in ibis.util.backend_entry_points()] 225 | backends 226 | ``` 227 | 228 | ### Table 229 | 230 | You typically work with a table, conventionally named `t` for demo or 231 | exploratory purposes: 232 | 233 | ```{python} 234 | t 235 | ``` 236 | 237 | When working with many tables, you should name them descriptively. 238 | 239 | ### Schema 240 | 241 | A table has a schema that Ibis maps to the data platform's data types: 242 | 243 | ```{python} 244 | t.schema() 245 | ``` 246 | 247 | ## LLMs and data: Marvin and Ibis 248 | 249 | You can use Marvin and Ibis together to easily apply LLMs to data. 250 | 251 | ```{python} 252 | from ibis.expr.schema import Schema 253 | from ibis.expr.types.relations import Table 254 | 255 | @marvin.ai_fn 256 | def sql_select( 257 | text: str, table_name: str = t.get_name(), schema: Schema = t.schema() 258 | ) -> str: 259 | """writes the SQL SELECT statement to query the table according to the text""" 260 | 261 | 262 | query = "the unique combination of species and islands" 263 | sql = sql_select(query).strip(";") 264 | sql 265 | ``` 266 | 267 | ```{python} 268 | t.sql(sql) 269 | ``` 270 | 271 | ```{python} 272 | # | code-fold: true 273 | sleep(3) # <1> 274 | ``` 275 | 276 | 1. Avoid rate-limiting by waiting. 277 | 278 | ```{python} 279 | t.sql(sql_select(query + " and include their counts in from highest to lowest").strip(";")) 280 | ``` 281 | 282 | ## Next steps 283 | 284 | You can get involved with [Ibis 285 | Birdbrain](https://github.com/ibis-project/ibis-birdbrain), our open-source data 286 | & AI project for building next-generation natural language interfaces to data. 287 | 288 | [Read the next post in this series](../llms-and-data-pt1). 289 | -------------------------------------------------------------------------------- /docs/_freeze/site_libs/clipboard/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.11 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1
TableAttachment\n **guid**: 5992ed9a-8a14-46c6-9da8-afdb3644a23d\n **time**: 2024-03-05 11:22:40.051587\n **name**: penguins\n **desc**: \nibis.Schema {\n species string\n island string\n bill_length_mm float64\n bill_depth_mm float64\n flipper_length_mm int64\n body_mass_g int64\n sex string\n year int64\n}\n **table**:\n┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ body_mass_g ┃ sex ┃ year ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ string │ string │ float64 │ float64 │ int64 │ int64 │ string │ int64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ 3750 │ male │ 2007 │\n│ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ 3800 │ female │ 2007 │\n│ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ 3250 │ female │ 2007 │\n│ Adelie │ Torgersen │ NULL │ NULL │ NULL │ NULL │ NULL │ 2007 │\n│ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ 3450 │ female │ 2007 │\n│ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ 3650 │ male │ 2007 │\n│ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ 3625 │ female │ 2007 │\n│ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ 4675 │ male │ 2007 │\n│ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ 3475 │ NULL │ 2007 │\n│ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ 4250 │ NULL │ 2007 │\n│ … │ … │ … │ … │ … │ … │ … │ … │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n```\n:::\n\n:::\n:::\n\n\nNotice the name, description (schema), and preview are automatically populated.\n\n## CodeAttachment\n\nA `CodeAttachment` contains code -- typically Python or SQL:\n\n::: {#89ecfbf6 .cell execution_count=3}\n``` {.python .cell-code}\nfrom ibis_birdbrain.attachments import CodeAttachment\n\na3 = CodeAttachment(content=\"select 1 as id\", language=\"sql\")\na3\n```\n\n::: {.cell-output .cell-output-display execution_count=3}\n```\nCodeAttachment\n **guid**: 8138742b-dcef-4735-b452-8481468e12da\n **time**: 2024-03-05 11:22:40.087917\n **name**: None\n **desc**: None\n **language**: sql\n **code**:\nselect 1 as id\n```\n:::\n:::\n\n\n",
6 | "supporting": [
7 | "attachments_files"
8 | ],
9 | "filters": [],
10 | "includes": {
11 | "include-in-header": [
12 | "\n\n\n"
13 | ]
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/ibis_birdbrain/tasks/sql.py:
--------------------------------------------------------------------------------
1 | # imports
2 | import ibis
3 | import marvin
4 |
5 | from Levenshtein import ratio
6 |
7 | from ibis_birdbrain.tasks import Task
8 | from ibis_birdbrain.logging import log
9 | from ibis_birdbrain.messages import Email, Message, Messages
10 | from ibis_birdbrain.attachments import (
11 | Attachments,
12 | TableAttachment,
13 | SQLAttachment,
14 | ErrorAttachment,
15 | DatabaseAttachment,
16 | )
17 |
18 |
19 | @ibis.udf.scalar.python
20 | def levenshtein_ratio(text1: str, text2: str) -> float:
21 | return ratio(text1, text2)
22 |
23 | # tasks
24 | class SearchTextTask(Task):
25 | """Ibis Birdbrain task to search cached text and SQL pair."""
26 |
27 | def __init__(
28 | self, name: str = "search-cached-question", description: str = "Ibis Birdbrain search task"
29 | ) -> None:
30 | """Initialize the searchtask."""
31 | super().__init__(name=name, description=description)
32 |
33 | def __call__(self, messages: Messages) -> Message:
34 | """Search text task."""
35 | log.info("Search text task.")
36 |
37 | try:
38 | cached_table = messages[0].attachments.get_attachment_by_type(TableAttachment)[0].open()
39 | question = messages[-1].body
40 | matched_res = cached_table.mutate(
41 | similarity=levenshtein_ratio(ibis._.question, question)
42 | ).filter(
43 | ibis._.similarity > 0.75 # Threshold
44 | ).order_by(
45 | ibis._.similarity.desc()
46 | ).limit(1).to_pandas()
47 |
48 | log.info(f"similarity = {matched_res.iloc[0]['similarity']}")
49 | log.info(f"sql = {matched_res.iloc[0]['sql']}")
50 |
51 | sql_attachment = SQLAttachment(
52 | dialect=matched_res.iloc[0]['dialect'],
53 | content=matched_res.iloc[0]['sql']
54 | )
55 |
56 | return Email(
57 | body="search cached question called",
58 | attachments=[sql_attachment],
59 | to_address=messages[-1].from_address,
60 | from_address=self.name,
61 | )
62 | except Exception as e:
63 | return Email(
64 | body=f"No similar question found",
65 | to_address=messages[-1].from_address,
66 | from_address=self.name,
67 | )
68 |
69 |
70 | class TextToSQLTask(Task):
71 | """Ibis Birdbrain task to turn text into SQL."""
72 |
73 | def __init__(
74 | self, name: str = "text-to-SQL", description: str = "Ibis Birdbrain SQL task"
75 | ) -> None:
76 | """Initialize the SQL task."""
77 | super().__init__(name=name, description=description)
78 |
79 | def __call__(self, message: Message) -> Message:
80 | """Text to SQL task."""
81 | log.info("Text to SQL task")
82 |
83 | # get the database attachment and table attachments
84 | table_attachments = message.attachments.get_attachment_by_type(TableAttachment)
85 | database_attachment = message.attachments.get_attachment_by_type(DatabaseAttachment)
86 |
87 | assert table_attachments is not None, "No table attachments found"
88 | assert database_attachment is not None, "No database attachment found"
89 |
90 | dialect = database_attachment.open().name
91 |
92 | # generate the SQL
93 | sql = self.text_to_sql(
94 | text=message.body,
95 | tables=table_attachments,
96 | data_description=database_attachment.description,
97 | dialect=dialect,
98 | )
99 | sql_attachment = SQLAttachment(dialect=dialect, content=sql)
100 |
101 | # generate the response message
102 | response_message = Email(
103 | body="text to sql called",
104 | attachments=[sql_attachment],
105 | to_address=message.from_address,
106 | from_address=self.name,
107 | )
108 | return response_message
109 |
110 | @staticmethod
111 | @marvin.fn
112 | def _text_to_sql(
113 | text: str, tables: Attachments, data_description: str, dialect: str
114 | ) -> str:
115 | """
116 | Generates correct, simple, and human-readable SQL based on the input
117 | `text`, `tables, and `data_description`, returns a SQL SELECT statement
118 | in the `dialect`.
119 |
120 | The `text` will contain a query in natural language to be answered.
121 |
122 | The `tables` will contain the table names and their schemas, alongside
123 | some metadata that can be ignored. DO NOT change the spelling or casing
124 | and only generate SQL for the provided tables.
125 |
126 | DO NOT add a LIMIT unless specifically told otherwise.
127 |
128 | Return (select) ALL possible columns unless specifically told otherwise.
129 |
130 | After joins, ensure that the columns are correctly qualified with the table name.
131 | """
132 |
133 | def text_to_sql(
134 | self, text: str, tables: Attachments, data_description: str, dialect="duckdb"
135 | ) -> str:
136 | """Convert text to SQL."""
137 | return (
138 | self._text_to_sql(
139 | text=text,
140 | tables=tables,
141 | data_description=data_description,
142 | dialect=dialect,
143 | )
144 | .strip()
145 | .strip(";")
146 | )
147 |
148 |
149 | class ExecuteSQLTask(Task):
150 | """Ibis Birdbrain task to execute SQL."""
151 |
152 | def __init__(
153 | self, name: str = "execute-SQL", description: str = "Ibis Birdbrain SQL task"
154 | ) -> None:
155 | """Initialize the SQL task."""
156 | super().__init__(name=name, description=description)
157 |
158 | def __call__(self, message: Message) -> Message:
159 | """Execute the SQL task."""
160 | log.info("Executing the SQL task")
161 |
162 | # get the database attachment and sql attachments
163 | sql_attachment = message.attachments.get_attachment_by_type(SQLAttachment)
164 | database_attachment = message.attachments.get_attachment_by_type(DatabaseAttachment)
165 |
166 | con = database_attachment.open()
167 | sql = sql_attachment.open()
168 |
169 | # execute the SQL
170 | try:
171 | table = con.sql(sql)
172 | attachment = TableAttachment(name="table", content=table)
173 | except Exception as e:
174 | log.error("Error executing the SQL")
175 | log.error("SQL: " + sql)
176 | log.error(e)
177 | attachment = ErrorAttachment(name="error", content=str(e))
178 |
179 | response_message = Email(
180 | body="execute SQL called",
181 | attachments=[attachment, sql_attachment],
182 | to_address=message.from_address,
183 | from_address=self.name,
184 | )
185 | return response_message
186 |
187 |
188 | class FixSQLTask(Task):
189 | """Ibis Birdbrain task to fix SQL."""
190 |
191 | def __init__(
192 | self, name: str = "fix-SQL", description: str = "Ibis Birdbrain SQL task"
193 | ) -> None:
194 | """Initialize the SQL task."""
195 | super().__init__(name=name, description=description)
196 |
197 | def __call__(self, message: Message) -> Message:
198 | """Fix the SQL task."""
199 | log.info("Fixing the SQL task")
200 |
201 | database_attachment = message.attachments.get_attachment_by_type(DatabaseAttachment)
202 | table_attachments = message.attachments.get_attachment_by_type(TableAttachment)
203 | sql_attachment = message.attachments.get_attachment_by_type(SQLAttachment)
204 | error_attachment = message.attachments.get_attachment_by_type(ErrorAttachment)
205 |
206 | assert database_attachment is not None, "No database attachment found"
207 | assert table_attachments is not None, "No table attachments found"
208 | assert sql_attachment is not None, "No SQL attachment found"
209 | assert error_attachment is not None, "No error attachment found"
210 |
211 | sql = self.fix_text_to_sql(
212 | text=message.body,
213 | sql=sql_attachment.open(),
214 | error=error_attachment.open(),
215 | tables=table_attachments,
216 | data_description=database_attachment.description,
217 | dialect=sql_attachment.dialect
218 | )
219 |
220 | response_message = Email(
221 | body="fix SQL called",
222 | attachments=[SQLAttachment(dialect=sql_attachment.dialect, content=sql)],
223 | to_address=message.from_address,
224 | from_address=self.name,
225 | )
226 | return response_message
227 |
228 | @staticmethod
229 | @marvin.fn
230 | def _fix_text_to_sql(
231 | text: str,
232 | sql: str,
233 | error: str,
234 | tables: Attachments,
235 | data_description: str,
236 | dialect: str,
237 | ) -> str:
238 | """
239 | Fixes the `sql` to answer the `text` based on the `error`.
240 |
241 | Using `tables, and `data_description`, returns a SQL SELECT statement
242 | in the `dialect` that fixes the input SQL.
243 |
244 | The `text` will contain a query in natural language to be answered.
245 |
246 | The `tables` will contain the table names and their schemas, alongside
247 | some metadata that can be ignored. DO NOT change the spelling or casing
248 | and only generate SQL for the provided tables.
249 |
250 | DO NOT add a LIMIT unless specifically told otherwise.
251 |
252 | Return (select) ALL possible columns unless specifically told otherwise.
253 |
254 | After joins, ensure that the columns are correctly qualified with the table name.
255 | """
256 |
257 | def fix_text_to_sql(
258 | self,
259 | text: str,
260 | sql: str,
261 | error: str,
262 | tables: Attachments,
263 | data_description: str,
264 | dialect="duckdb",
265 | ) -> str:
266 | """Convert text to SQL."""
267 | return (
268 | self._fix_text_to_sql(
269 | text=text,
270 | sql=sql,
271 | error=error,
272 | tables=tables,
273 | data_description=data_description,
274 | dialect=dialect,
275 | )
276 | .strip()
277 | .strip(";")
278 | )
279 |
--------------------------------------------------------------------------------
/docs/posts/llms-and-data-pt1/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Three approaches"
3 | author: "Cody Peterson"
4 | date: "2023-10-13"
5 | execute:
6 | warning: false
7 | categories:
8 | - "LLMs and data"
9 | ---
10 |
11 | ## Introduction
12 |
13 | The thought of using natural language to transform and analyze data is
14 | appealing. This post assumes familiarity with Marvin and Ibis -- [read the
15 | previous post in the series for a quick overview](../llms-and-data-pt0).
16 |
17 | ## Approaches
18 |
19 | When discussed at Voltron Data, we identified three distinct approaches to
20 | applying LLMs to data analytics that can be implemented today:
21 |
22 | 1. LLM writes an analytic code
23 | 2. LLM writes an analytic subroutine
24 | 3. Use LLM in an analytic subroutine
25 |
26 | While these three approaches are not an exhaustive list of how LLMs can be
27 | applied to data, they can be easily understood and implemented with Ibis and
28 | Marvin in a few lines of code. Together with these two open-source tools, we can
29 | build a natural language interface for data analytics that supports 18+
30 | backends.
31 |
32 | But first, let's demonstrate the three approaches.
33 |
34 | ### Approach 1: LLM writes analytic code
35 |
36 | State of the art (SoTA) LLMs are decent at generating SQL out of the box. We can
37 | be clever to handle errors, retries, and more, but in its simplest form:
38 |
39 | ```{python}
40 | # | code-fold: true
41 | import ibis # <1>
42 | import marvin # <1>
43 |
44 | from rich import print # <1>
45 | from time import sleep # <1>
46 | from dotenv import load_dotenv # <1>
47 |
48 | load_dotenv() # <2>
49 |
50 | con = ibis.connect("duckdb://penguins.ddb") # <3>
51 | t = ibis.examples.penguins.fetch() # <3>
52 | t = con.create_table("penguins", t.to_pyarrow(), overwrite=True) # <3>
53 | ```
54 |
55 | 1. Import the libraries we need.
56 | 2. Load the environment variable to setup Marvin to call our OpenAI account.
57 | 3. Setup the demo datain an Ibis backend.
58 |
59 | ```{python}
60 | import ibis # <1>
61 | import marvin # <1>
62 |
63 | from ibis.expr.schema import Schema # <1>
64 | from ibis.expr.types.relations import Table # <1>
65 |
66 |
67 | ibis.options.interactive = True # <2>
68 | marvin.settings.llm_model = "openai/gpt-4" # <2>
69 | ```
70 |
71 | 1. Import Ibis and Marvin.
72 | 2. Configure Ibis and Marvin
73 |
74 | ```{python}
75 | @marvin.ai_fn # <1>
76 | def _generate_sql_select(
77 | text: str, table_name: str, table_schema: Schema
78 | ) -> str: # <1>
79 | """Generate SQL SELECT from text.""" # <1>
80 |
81 |
82 | def sql_from_text(text: str, t: Table) -> Table: # <2>
83 | """Run SQL from text.""" # <2>
84 | return t.sql(_generate_sql_select(text, t.get_name(), t.schema()).strip(";")) # <2>
85 | ```
86 |
87 | 1. A non-deterministic, LLM-powered AI function.
88 | 2. A deterministic, human-authored function that calls the AI function.
89 |
90 | ```{python}
91 | t2 = sql_from_text("the unique combination of species and islands", t)
92 | t2
93 | ```
94 |
95 | ```{python}
96 | # | code-fold: true
97 | sleep(3) # <1>
98 | ```
99 |
100 | 1. Avoid rate-limiting by waiting.
101 |
102 | ```{python}
103 | t3 = sql_from_text(
104 | "the unique combination of species and islands, with their counts, ordered from highest to lowest, and name that column just 'count'",
105 | t,
106 | )
107 | t3
108 | ```
109 |
110 | ```{python}
111 | # | code-fold: true
112 | sleep(3) # <1>
113 | ```
114 |
115 | 1. Avoid rate-limiting by waiting.
116 |
117 | This works well-enough for simple cases and can be expanded to handle complex
118 | ones. In many scenarios, it may be easier to express a query in English or
119 | another language than to write it in SQL, especially if working across multiple
120 | SQL dialects.
121 |
122 | SQL isn't standard, with many dialects across data platforms. Ibis works around
123 | this by providing a standard Python API for analytic code but must make
124 | compromises to support many data platforms, often via SQL in their native
125 | dialect. [Substrait](https://substrait.io) is a newer project that aims to solve
126 | this problem by providing a standard, portable, and extensible intermediary
127 | representation (IR) for data transformation code that Ibis and data platforms
128 | could all standardize on. Substrait is still in the early stages of development,
129 | but it's worth keeping an eye on and will be adopted in Ibis once supported
130 | across many data platforms.
131 |
132 | For now, we'll focus on generating SQL and Python analytical code with LLMs.
133 |
134 | ### Approach 2: LLM writes an analytical subroutine
135 |
136 | If more complex logic needs to be expressed, SoTA LLMs are also decent at
137 | writing Python and a number of other programming languages that are used in
138 | analytical subroutines. Many data platforms support user-defined functions
139 | (UDFs) in Python or some other language. We'll stick to scalar Python UDFs via
140 | DuckDB to demonstrate the concept:
141 |
142 | ```{python}
143 | @marvin.ai_fn # <1>
144 | def _generate_python_function(text: str) -> str: # <1>
145 | """Generate a simple, typed, correct Python function from text.""" # <1>
146 |
147 |
148 | def create_udf_from_text(text: str) -> str: # <2>
149 | """Create a UDF from text.""" # <2>
150 | return f"""
151 | import ibis
152 |
153 | @ibis.udf.scalar.python
154 | {_generate_python_function(text)}
155 | """.strip() # <2>
156 | ```
157 |
158 | 1. A non-deterministic, LLM-powered AI function.
159 | 2. A deterministic, human-authored function that calls the AI function.
160 |
161 | ```{python}
162 | udf = create_udf_from_text(
163 | "a function named count_vowels that given an input string, returns an int w/ the number of vowels (y_included as a boolean option defaulted to False)"
164 | )
165 | print(udf)
166 | exec(udf)
167 | ```
168 |
169 | ```{python}
170 | # | code-fold: true
171 | sleep(3) # <1>
172 | ```
173 |
174 | 1. Avoid rate-limiting by waiting.
175 |
176 | ```{python}
177 | t4 = t3.mutate(
178 | species_vowel_count=count_vowels(t3.species),
179 | island_vowel_count=count_vowels(t3.island),
180 | )
181 | t4
182 | ```
183 |
184 | ```{python}
185 | # | code-fold: true
186 | sleep(3) # <1>
187 | ```
188 |
189 | 1. Avoid rate-limiting by waiting.
190 |
191 | In this case, there's no reason not to have a human in the loop reviewing the
192 | output code and committing it for production use. This could be useful for quick
193 | prototyping or, given a box of tools in the form of UDFs,
194 | working through a natural language interface.
195 |
196 | ### Approach 3: Use LLM in an analytical subroutine
197 |
198 | We can also call the LLM once-per-row in the table via a subroutine. For
199 | variety, we'll use an [AI model](https://www.askmarvin.ai/components/ai_model/)
200 | instead of an [AI function](https://www.askmarvin.ai/components/ai_function/):
201 |
202 | ```{python}
203 | from pydantic import BaseModel, Field # <1>
204 |
205 | # decrease cost
206 | marvin.settings.llm_model = "openai/gpt-3.5-turbo-16k" # <2>
207 |
208 |
209 | @marvin.ai_model # <3>
210 | class VowelCounter(BaseModel): # <3>
211 | """Count vowels in a string.""" # <3>
212 |
213 | include_y: bool = Field(False, description="Include 'y' as a vowel.") # <3>
214 | # num_a: int = Field(..., description="The number of 'a' vowels.") # <3>
215 | # num_e: int = Field(..., description="The number of 'e' vowels.") # <3>
216 | # num_i: int = Field(..., description="The number of 'i' vowels.") # <3>
217 | # num_o: int = Field(..., description="The number of 'o' vowels.") # <3>
218 | # num_u: int = Field(..., description="The number of 'u' vowels.") # <3>
219 | # num_y: int = Field(..., description="The number of 'y' vowels.") # <3>
220 | num_total: int = Field(..., description="The total number of vowels.") # <3>
221 |
222 |
223 | VowelCounter("hello world") # <4>
224 | ```
225 |
226 | 1. Additional imports for Pydantic.
227 | 2. Configure Marvin to use a cheaper model.
228 | 3. A non-deterministic, LLM-powered AI model.
229 | 4. Call the AI model on some text.
230 |
231 | Then we'll have the LLM write the UDF that calls the LLM, just to be fancy:
232 |
233 | ```{python}
234 | udf = create_udf_from_text(
235 | "a function named count_vowels_ai that given an input string, calls VowelCounter on it and returns the num_total attribute of that result"
236 | )
237 | print(udf)
238 | exec(udf)
239 | ```
240 |
241 | ```{python}
242 | # | code-fold: true
243 | sleep(3) # <1>
244 | ```
245 |
246 | 1. Avoid rate-limiting by waiting.
247 |
248 | ```{python}
249 | t5 = t3.mutate(
250 | species_vowel_count=count_vowels_ai(t3.species),
251 | island_vowel_count=count_vowels_ai(t3.island),
252 | )
253 | t5
254 | ```
255 |
256 | Notice that in this UDF, unlike in the previous example, a LLM is being called
257 | (possibly several times) for each row in the table. This is a very expensive
258 | operation and we'll need to be careful about how we use it in practice.
259 |
260 | ```{python}
261 | # | code-fold: true
262 | sleep(3) # <1>
263 | ```
264 |
265 | 1. Avoid rate-limiting by waiting.
266 |
267 | ## Summary
268 |
269 | To summarize this post:
270 |
271 | ```{python}
272 | from rich import print
273 |
274 | with open("index.qmd", "r") as f:
275 | self_text = f.read()
276 |
277 | # increase accuracy
278 | marvin.settings.llm_model = "openai/gpt-4"
279 |
280 | @marvin.ai_model
281 | class Summary(BaseModel):
282 | """Summary of text."""
283 |
284 | summary_line: str = Field(..., description="The one-line summary of the text.")
285 | summary_paragraph: str = Field(
286 | ..., description="The one-paragraph summary of the text."
287 | )
288 | conclusion: str = Field(
289 | ..., description="The conclusion the reader should draw from the text."
290 | )
291 | key_points: list[str] = Field(..., description="The key points of the text.")
292 | critiques: list[str] = Field(
293 | ..., description="Professional, fair critiques of the text."
294 | )
295 | suggested_improvements: list[str] = Field(
296 | ..., description="Suggested improvements for the text."
297 | )
298 | sentiment: float = Field(..., description="The sentiment of the text.")
299 | sentiment_label: str = Field(..., description="The sentiment label of the text.")
300 | author_bias: str = Field(..., description="The author bias of the text.")
301 |
302 |
303 | print(Summary(self_text))
304 | ```
305 |
306 | ## Next steps
307 |
308 | You can get involved with [Ibis
309 | Birdbrain](https://github.com/ibis-project/ibis-birdbrain), our open-source data
310 | & AI project for building next-generation natural language interfaces to data.
311 |
312 | [Read the next post in this series](../llms-and-data-pt2).
313 |
--------------------------------------------------------------------------------
/docs/_freeze/tutorials/python/execute-results/html.json:
--------------------------------------------------------------------------------
1 | {
2 | "hash": "5473aeb233c43d87b1bfa8acc656ed98",
3 | "result": {
4 | "engine": "jupyter",
5 | "markdown": "---\ntitle: 'Tutorial: Python'\n---\n\n\n\n## Prerequisites\n\n1. [Install Ibis Birdbrain](/install.qmd)\n\n## Overview\n\nYou can use Ibis Birdbrain in Python.\n\n## Setup the bot\n\nFirst, import relevant modules:\n\n\n::: {#935cf68c .cell execution_count=1}\n``` {.python .cell-code}\nimport ibis\n\nfrom ibis_birdbrain import Bot\n```\n:::\n\n\nSet Ibis interactive mode:\n\n::: {#9ee1c955 .cell execution_count=2}\n``` {.python .cell-code}\nibis.options.interactive = True\n```\n:::\n\n\n### Create an Ibis connection\n\nCreate an Ibis connection to your database:\n\n::: {.callout-warning}\nWe'll create a demo database for this tutorial.\n:::\n\n::: {#1b36ceee .cell execution_count=3}\n``` {.python .cell-code}\ncon = ibis.connect(\"duckdb://penguins.ddb\")\ncon.create_table(\n \"penguins\", ibis.examples.penguins.fetch().to_pyarrow(), overwrite=True\n)\ncon = ibis.connect(\"duckdb://penguins.ddb\")\ncon.list_tables()\n```\n\n::: {.cell-output .cell-output-stderr}\n```\nINFO:pins.cache:cache file: /Users/cody/Library/Caches/pins-py/gcs_332a30997e141da0e08f15fbfae8b3c3ec90463922d117a96fa3b1bef85a2a4c/penguins/20230905T090411Z-9aae2/data.txt\nINFO:pins.cache:cache file: /Users/cody/Library/Caches/pins-py/gcs_332a30997e141da0e08f15fbfae8b3c3ec90463922d117a96fa3b1bef85a2a4c/penguins/20230905T090411Z-9aae2/penguins.csv.gz\n```\n:::\n\n::: {.cell-output .cell-output-display execution_count=3}\n```\n['penguins']\n```\n:::\n:::\n\n\n### Create the bot\n\nYou'll create the bot by passing in the connection:\n\n::: {.callout-tip}\nFor increased accuracy, you should also pass in a `data_description` containing\ninformation about the dataset. This could be fetched from the database itself,\nmanually created, or otherwise obtained.\n\nYou should not include table names and schemas -- this will be inferred\nautomatically.\n:::\n\n::: {#f4eab662 .cell execution_count=4}\n``` {.python .cell-code}\nbot = Bot(con=con, data_description=\"the Palmer Penguins dataset\")\nbot\n```\n\n::: {.cell-output .cell-output-stderr}\n```\nINFO:root:Bot birdbrain initialized...\n```\n:::\n\n::: {.cell-output .cell-output-display execution_count=4}\n```\nTo: user\nFrom: birdbrain\nSubject: give me the counts of pengu...\nSent at: 2024-03-05 12:18:51.951695\nMessage: 67c02541-9406-4de5-b99e-21cc5dc8d3c5\nIbis Birdbrain has attached the results.\nAttachments:\nCodeAttachment\n **guid**: e2152990-296e-4b92-a159-05af61b5334a\n **time**: 2024-03-05 12:18:51.949484\n **name**: None\n **desc**: None\n **language**: sql\n **code**:\nSELECT species, island, COUNT(*) AS count\nFROM penguins\nGROUP BY species, island\nORDER BY COUNT(*) DESC\nTableAttachment\n **guid**: e6acb87c-b4ca-4fa7-8292-638d3fc0b6e8\n **time**: 2024-03-05 12:18:51.951600\n **name**: None\n **desc**: \nibis.Schema {\n species string\n island string\n count int64\n}\n **table**:\n┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓\n┃ species ┃ island ┃ count ┃\n┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━┩\n│ string │ string │ int64 │\n├───────────┼───────────┼───────┤\n│ Gentoo │ Biscoe │ 124 │\n│ Chinstrap │ Dream │ 68 │\n│ Adelie │ Dream │ 56 │\n│ Adelie │ Torgersen │ 52 │\n│ Adelie │ Biscoe │ 44 │\n└───────────┴───────────┴───────┘\n```\n:::\n\n:::\n:::\n\n\n### Get attachments\n\nYou can get the table from the attachment:\n\n::: {#822b287e .cell execution_count=6}\n``` {.python .cell-code}\nt = res.attachments[-1].open()\nt\n```\n\n::: {.cell-output .cell-output-display execution_count=6}\n```{=html}\n┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓\n┃ species ┃ island ┃ count ┃\n┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━┩\n│ string │ string │ int64 │\n├───────────┼───────────┼───────┤\n│ Gentoo │ Biscoe │ 124 │\n│ Chinstrap │ Dream │ 68 │\n│ Adelie │ Dream │ 56 │\n│ Adelie │ Torgersen │ 52 │\n│ Adelie │ Biscoe │ 44 │\n└───────────┴───────────┴───────┘\n\n```\n:::\n:::\n\n\nAnd do whatever you want with it:\n\n::: {#f616ff8d .cell execution_count=7}\n``` {.python .cell-code}\nt.order_by(ibis._[\"count\"].asc())\n```\n\n::: {.cell-output .cell-output-display execution_count=7}\n```{=html}\n
┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓\n┃ species ┃ island ┃ count ┃\n┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━┩\n│ string │ string │ int64 │\n├───────────┼───────────┼───────┤\n│ Adelie │ Biscoe │ 44 │\n│ Adelie │ Torgersen │ 52 │\n│ Adelie │ Dream │ 56 │\n│ Chinstrap │ Dream │ 68 │\n│ Gentoo │ Biscoe │ 124 │\n└───────────┴───────────┴───────┘\n\n```\n:::\n:::\n\n\n## Next steps\n\nExplore some data with Ibis Birdbrain and [let us know how it\ngoes!](https://github.com/ibis-project/ibis-birdbrain/issues/new)\n\n", 6 | "supporting": [ 7 | "python_files/figure-html" 8 | ], 9 | "filters": [], 10 | "includes": { 11 | "include-in-header": [ 12 | "\n\n\n" 13 | ] 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright Ibis developers 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------