├── Dockerfile ├── LICENSE ├── README.md ├── api ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-310.pyc │ └── server.cpython-310.pyc └── server.py ├── model ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-310.pyc │ ├── agent.cpython-310.pyc │ ├── diffbot.cpython-310.pyc │ └── prompts.cpython-310.pyc ├── agent.py ├── diffbot.py └── prompts.py ├── requirements.txt ├── run.sh ├── service.cfg └── static ├── index.html ├── privacy_policy.txt └── terms_of_service.txt /Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image 2 | FROM python:3.10 3 | 4 | # Set working directory 5 | WORKDIR /app 6 | 7 | # Copy requirements file and install dependencies 8 | COPY requirements.txt . 9 | RUN pip3 install -r requirements.txt 10 | 11 | RUN mkdir /var/log/build 12 | 13 | # Copy API server file into container 14 | COPY api ./api 15 | COPY model ./model 16 | COPY static ./static 17 | 18 | ENV HOST_DOMAIN=${HOST_DOMAIN} 19 | ENV APP_URL_PATH=${APP_URL_PATH} 20 | ENV STATIC_FILE_DIR=${STATIC_FILE_DIR} 21 | ENV TORNADO_SERVER_PORT=${TORNADO_SERVER_PORT} 22 | ENV REQUEST_LOG_FILE=${REQUEST_LOG_FILE} 23 | 24 | ENV DIFFBOT_API_KEY=${DIFFBOT_API_KEY} 25 | ENV OPENAI_API_KEY=${OPENAI_API_KEY} 26 | ENV TWILIO_AUTH_TOKEN=${TWILIO_AUTH_TOKEN} 27 | 28 | ENV TELNYX_API_KEY=${TELNYX_API_KEY} 29 | ENV TELNYX_PROFILE_ID=${TELNYX_PROFILE_ID} 30 | ENV TELNYX_PHONE_NUMBER=${TELNYX_PHONE_NUMBER} 31 | 32 | ARG APP_URL_PATH 33 | RUN sed -i "s#\$APP_URL_PATH#${APP_URL_PATH}#g" ./static/index.html 34 | 35 | # Expose port for API server 36 | EXPOSE 8888 37 | 38 | # Start API server 39 | CMD ["python3", "-m", "api.server"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 a16z-infra 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ☀️ Sunlight 2 | 3 | ## Introduction 4 | 5 | **Sunlight** is an open-source tool for analyzing written content on the internet using large language models (LLMs). 6 | 7 | The first release of Sunlight is designed to analyze writing for bias. When you're writing a blog post, article, report, etc, you can use Sunlight for a second opinion to ensure fairness and a balanced perspective. (It just has to be hosted somewhere on the internet.) 8 | 9 | ### ☀️ [Live demo here!](https://trysunlight.ai) ☀️ 10 | 11 | If you input a public URL, Sunlight will generate: 12 | - A list of factual claims in the linked content 13 | - A critique of the bias, if any, in the content 14 | - A pithy label for the perspective shown 15 | 16 | ## How to Contribute 17 | 18 | This currently uses GPT-4, so it makes a lot of mistakes. The prompts are all included in this repo, and we'd love help from the community improving them. Ways you can contribute include: 19 | - Improve the prompts and submit a PR! 20 | - Log examples of bad analysis as [Issues](https://github.com/a16z-infra/sunlight/issues), including the original link and explanation of what Sunlight got wrong 21 | - Add new functionality (e.g. a text box to analyze draft content) 22 | - Fork this repo to build other types of writing analysis apps 23 | 24 | ## Local Installation 25 | 26 | Getting started with Sunlight is very easy, thanks to containerization. Follow these simple steps to install and run Sunlight on your system: 27 | 28 | ### Prerequisites 29 | 30 | Before proceeding, ensure you have a recent version of Docker installed. If you need to install Docker, please follow the instructions on the [Docker installation page](https://docs.docker.com/get-docker/). 31 | 32 | ### Setup Instructions 33 | 34 | 1. Clone this repository and navigate to the resulting directory. 35 | 2. Edit the `service.cfg` file with your API credentials for Diffbot and OpenAI. Include Telnyx credentials if you wish to enable the SMS integration. 36 | 3. Execute `run.sh`. This will: 37 | 38 | - Load the environment variables from `service.cfg`. 39 | - Build a Docker image with all necessary dependencies. 40 | - Run the Docker image, which includes the backend API server along with a demo web UI. 41 | 42 | ``` 43 | git clone https://github.com/a16z-infra/sunlight 44 | vi service.cfg 45 | . run.sh 46 | ``` 47 | 48 | ### Using Sunlight 49 | 50 | After executing `run.sh`, Sunlight will be operational. Access the demo web UI by visiting: 51 | 52 | http://localhost:8888/static/index.html 53 | 54 | Follow the on-page instructions to configure the target document and model version. 55 | 56 | ## How does this work? 57 | 58 | Sunlight runs a simple backend service that interfaces with LLMs for the interesting semantic analysis. You can check this repository for implementation details, but here's a basic overview of what happens under the hood: 59 | 60 | - **Input**: Users submit a link to their writing via SMS or the web UI. 61 | - **Pre-processing**: The API server fetches page tags and then calls out to [Diffbot](https://diffbot.com) for structured metadata, including the title and body text, upon job submission. 62 | - **Processing**: Text is passed through a short cascade of parameterized prompts for semantic analysis. 63 | - **Evaluation**: Suggestions for improvement are passed back to the user. 64 | 65 | ## What are the prompts? 66 | 67 | Sunlight uses a series of carefully crafted prompts to guide the LLMs in assessing your writing. You can find the current versions [here](https://github.com/a16z-infra/sunlight/blob/main/model/prompts.py). Here’s how it works: 68 | 69 | 1. **Fact extraction**: identify, condense, and reorder claims made in your writing 70 | 2. **Bias analysis**: assess your piece's underlying perspective in a brief report 71 | 3. **Slant tagging**: using the bias report, assign a short label for categorization 72 | 73 | ## Disclaimer 74 | 75 | Please read our [Terms of Service](https://trysunlight.ai/static/terms_of_service.txt) and [Privacy Policy](https://trysunlight.ai/static/privacy_policy.txt) before using Sunlight. 76 | 77 | Sunlight may produce inaccurate information. This demo utilizes experimental large language model technology to generate its outputs, and we make no guarantees or promises regarding the accuracy or suitability of its analysis. This demo makes no claim to correct factual errors in the underlying source content. 78 | 79 | It is incapable of providing investment advice, legal advice or marketing financial advisory advice, or soliciting investors or clients (and it is not intended to be utilized by investors); therefore its output should not be used as such. 80 | 81 | --- 82 | 83 | Thanks for checking out our tool and reading this far. We hope Sunlight is useful to you. Community contributions, feedback, and insights are what make open-source projects successful; please reach out with any comments or suggestions. 84 | 85 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a16z-infra/sunlight/a756f385ce1c375b0b9676620d6f732e74b2d9c4/api/__init__.py -------------------------------------------------------------------------------- /api/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a16z-infra/sunlight/a756f385ce1c375b0b9676620d6f732e74b2d9c4/api/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /api/__pycache__/server.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a16z-infra/sunlight/a756f385ce1c375b0b9676620d6f732e74b2d9c4/api/__pycache__/server.cpython-310.pyc -------------------------------------------------------------------------------- /api/server.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ThreadPoolExecutor 2 | import copy 3 | import json 4 | import logging 5 | import multiprocessing 6 | import os 7 | import random 8 | import string 9 | from urllib.parse import urlparse 10 | 11 | from lxml import etree 12 | import telnyx 13 | import tornado.gen 14 | from tornado.httpclient import AsyncHTTPClient, HTTPRequest 15 | import tornado.ioloop 16 | import tornado.web 17 | from tornado.web import StaticFileHandler 18 | 19 | from model.agent import Agent 20 | 21 | 22 | HOST_DOMAIN = os.environ['HOST_DOMAIN'] 23 | APP_URL_PATH = os.environ['APP_URL_PATH'] 24 | STATIC_FILE_DIR = os.environ['STATIC_FILE_DIR'] 25 | TORNADO_SERVER_PORT = int(os.environ['TORNADO_SERVER_PORT']) 26 | NUM_AGENTS = 6 27 | 28 | JOB_ID_CHARS = string.ascii_lowercase + string.ascii_uppercase + string.digits 29 | JOB_STATUS_CHECK_INTERVAL_MS = 1000 30 | 31 | telnyx.api_key = os.environ['TELNYX_API_KEY'] 32 | TELNYX_PROFILE_ID = os.environ['TELNYX_PROFILE_ID'].encode() 33 | TELNYX_PHONE_NUMBER = os.environ['TELNYX_PHONE_NUMBER'] 34 | 35 | UNSUPPORTED_TLDS = [ 36 | 'seekingalpha.com', 37 | 'zacks.com', 38 | 'tipranks.com', 39 | 'marketbeat.com', 40 | 'wallstreetzen.com', 41 | 'benzinga.com', 42 | 'indmoney.com', 43 | 'marketwatch.com', 44 | 'marketscreener.com', 45 | 'stockrover.com', 46 | 'fool.com', 47 | 'trade-ideas.com', 48 | 'invest.aaii.com', 49 | 'yieldstreet.com', 50 | 'mindfultrader.com', 51 | 'tradingview.com', 52 | 'alpharesearch.io', 53 | 'morningstar.com', 54 | 'investech.com', 55 | 'kiplinger.com', 56 | 'stockunlock.com', 57 | 'barrons.com', 58 | 'moneyweek.com', 59 | 'investors.com', 60 | 'readthejoe.com', 61 | 'finance.yahoo.com', 62 | 'google.com/finance', 63 | ] 64 | 65 | 66 | def get_job_id(): 67 | return ''.join(random.choices(JOB_ID_CHARS, k=8)) 68 | 69 | 70 | class FetchOpenGraphTags(tornado.web.RequestHandler): 71 | MAX_REQUEST_LEN = 512 72 | EXECUTOR = ThreadPoolExecutor(max_workers=32) 73 | 74 | def initialize(self, cache): 75 | self.cache = cache 76 | 77 | @tornado.gen.coroutine 78 | def post(self): 79 | if len(self.request.body) > self.MAX_REQUEST_LEN or 'url' not in self.request.body.decode('utf-8'): 80 | self.set_status(400) 81 | return 82 | 83 | url = json.loads(self.request.body)['url'] 84 | 85 | if url in self.cache: 86 | open_graph_tags = self.cache[url] 87 | else: 88 | open_graph_tags = yield self.fetch_url_meta_tags(url) 89 | if type(open_graph_tags) is dict: 90 | open_graph_tags['tld'] = urlparse(url).netloc 91 | self.cache[url] = open_graph_tags 92 | 93 | if open_graph_tags is not None: 94 | self.set_status(200) 95 | self.write(json.dumps(open_graph_tags)) 96 | else: 97 | self.set_status(400) 98 | 99 | @tornado.gen.coroutine 100 | def fetch_url_meta_tags(self, url): 101 | http_client = AsyncHTTPClient() 102 | request = HTTPRequest(url, method='GET', streaming_callback=self.handle_streaming_response) 103 | try: 104 | _ = yield http_client.fetch(request) 105 | except: # NB: Cleanest way to cancel a streaming request, unfortunately 106 | pass 107 | 108 | if hasattr(self, 'html_fragment') and type(self.html_fragment) is str: 109 | return self.find_meta_tags(self.html_fragment) 110 | else: 111 | return None 112 | 113 | def handle_streaming_response(self, chunk): 114 | if hasattr(self, 'html_fragment'): 115 | self.html_fragment += chunk.decode('utf-8') 116 | if '' in self.html_fragment or '' in self.html_fragment: 117 | raise Exception('Cancel request') # NB: Cleanest way to cancel a streaming request, unfortunately 118 | else: 119 | self.html_fragment = chunk.decode('utf-8') 120 | 121 | @staticmethod 122 | def find_meta_tags(html_fragment): 123 | parser = etree.HTMLParser() 124 | tree = etree.fromstring(html_fragment, parser) 125 | 126 | metadata = {} 127 | 128 | # Check for Open Graph meta tags 129 | og_title = tree.xpath('//meta[@property="og:title"]/@content') 130 | if og_title: 131 | metadata['title'] = og_title[0] 132 | 133 | og_description = tree.xpath('//meta[@property="og:description"]/@content') 134 | if og_description: 135 | metadata['description'] = og_description[0] 136 | 137 | og_image = tree.xpath('//meta[@property="og:image"]/@content') 138 | if og_image: 139 | metadata['image'] = og_image[0] 140 | 141 | # Check for Dublin Core meta tags as fallback 142 | if 'title' not in metadata: 143 | dc_title = tree.xpath('//meta[@name="DC.Title"]/@content') 144 | if dc_title: 145 | metadata['title'] = dc_title[0] 146 | 147 | if 'description' not in metadata: 148 | dc_description = tree.xpath('//meta[@name="DC.Description"]/@content') 149 | if dc_description: 150 | metadata['description'] = dc_description[0] 151 | 152 | # Check for Twitter card meta tags as final fallback 153 | if 'title' not in metadata: 154 | twitter_title = tree.xpath('//meta[@name="twitter:title"]/@content') 155 | if twitter_title: 156 | metadata['title'] = twitter_title[0] 157 | 158 | if 'description' not in metadata: 159 | twitter_description = tree.xpath('//meta[@name="twitter:description"]/@content') 160 | if twitter_description: 161 | metadata['description'] = twitter_description[0] 162 | 163 | if 'image' not in metadata: 164 | twitter_image = tree.xpath('//meta[@name="twitter:image"]/@content') 165 | if twitter_image: 166 | metadata['image'] = twitter_image[0] 167 | 168 | return metadata if len(metadata) > 0 else None 169 | 170 | 171 | class SubmitUrlHandler(tornado.web.RequestHandler): 172 | def initialize(self, in_queue, job_statuses, job_ids_by_url): 173 | self.in_queue = in_queue 174 | self.job_statuses = job_statuses 175 | self.job_ids_by_url = job_ids_by_url 176 | 177 | def post(self): 178 | logging.info(f'Received {self.request.body}') 179 | request = json.loads(self.request.body) 180 | url, model = request['url'], request['model'] 181 | 182 | self.set_status(200) 183 | if not url.startswith('http://') and not url.startswith('https://'): 184 | self.write({'error': 'INVALID_URL'}) 185 | elif any(tld in url for tld in UNSUPPORTED_TLDS): 186 | self.write({'error': 'UNSUPPORTED_URL'}) 187 | elif url in self.job_ids_by_url: 188 | self.write({'job_id': self.job_ids_by_url[url]}) 189 | else: 190 | job_id = get_job_id() 191 | self.in_queue.put({'job_id': job_id, 'job_idx': self.job_statuses['JOB_IDX'], 'url': url, 'model': model}) 192 | self.job_statuses[job_id] = {'status': 'Queued', 'job_idx': self.job_statuses['JOB_IDX'], 'url': url} 193 | self.job_ids_by_url[url] = job_id 194 | self.job_statuses['JOB_IDX'] += 1 195 | self.write({'job_id': job_id}) 196 | 197 | 198 | class FetchJobStatusHandler(tornado.web.RequestHandler): 199 | def initialize(self, out_queue, job_statuses): 200 | self.out_queue = out_queue 201 | self.job_statuses = job_statuses 202 | 203 | def get(self): 204 | while not self.out_queue.empty(): 205 | job_id, status_msg = self.out_queue.get() 206 | self.job_statuses[job_id] = status_msg 207 | 208 | job_id = self.get_argument('job_id') 209 | status_dict = self.job_statuses.get(job_id, {'status': 'Job unknown'}) 210 | if 'job_idx' in status_dict: 211 | status_dict = copy.copy(status_dict) 212 | status_dict['queue_position'] = status_dict['job_idx'] - self.job_statuses['LAST_JOB_IDX'] 213 | del status_dict['job_idx'] 214 | self.set_status(200) 215 | self.write(json.dumps(status_dict)) 216 | 217 | 218 | class TelnyxSMSHandler(tornado.web.RequestHandler): 219 | def initialize(self, in_queue, job_statuses): 220 | self.in_queue = in_queue 221 | self.job_statuses = job_statuses 222 | 223 | def post(self): 224 | if TELNYX_PROFILE_ID not in self.request.body: 225 | raise tornado.web.HTTPError(403) 226 | 227 | try: 228 | message = json.loads(self.request.body) 229 | except: 230 | raise tornado.web.HTTPError(400) 231 | 232 | if 'data' not in message or 'event_type' not in message['data']: 233 | raise tornado.web.HTTPError(400) 234 | 235 | if message['data']['event_type'] != 'message.received': 236 | self.set_status(200) 237 | return 238 | 239 | if 'payload' not in message['data'] or \ 240 | 'text' not in message['data']['payload'] or \ 241 | 'from' not in message['data']['payload'] or \ 242 | 'phone_number' not in message['data']['payload']['from']: 243 | raise tornado.web.HTTPError(400) 244 | 245 | text, phone_number = message['data']['payload']['text'], message['data']['payload']['from']['phone_number'] 246 | 247 | job_id = get_job_id() 248 | self.in_queue.put({'job_id': job_id, 'url': text, 'phone_number': phone_number}) 249 | self.job_statuses[job_id] = {'status': 'Queued', 'url': text} 250 | 251 | self.set_status(200) 252 | self.finish() 253 | 254 | 255 | class StaticFileHandlerWithDefaultContentType(StaticFileHandler): 256 | def set_extra_headers(self, path): 257 | self.set_header('Content-Type', 'text/html') 258 | 259 | 260 | @tornado.gen.coroutine 261 | def dequeue_job_statuses(out_queue, job_statuses): 262 | while not out_queue.empty(): 263 | job_id, status_msg = out_queue.get() 264 | 265 | if type(status_msg) is dict and status_msg['status'] == 'Complete' and 'phone_number' in status_msg['result']: 266 | phone_number = status_msg['result']['phone_number'] 267 | reply_msg = f'Analyzed {status_msg["result"]["headline"]}; read at {HOST_DOMAIN}?job_id={job_id}' 268 | yield send_sms(phone_number, reply_msg) 269 | 270 | job_statuses[job_id] = status_msg 271 | 272 | 273 | @tornado.gen.coroutine 274 | def send_sms(reply_number, reply_msg): 275 | try: 276 | message = telnyx.Message.create(from_=TELNYX_PHONE_NUMBER, to=reply_number, text=reply_msg) 277 | 278 | if message['errors']: 279 | logging.error(f'SMS reply to {reply_number} contained errors, status: {message["errors"]}') 280 | except: 281 | logging.error(f'Failed to send SMS reply to {reply_number}', exc_info=True) 282 | 283 | 284 | if __name__ == '__main__': 285 | logging.basicConfig(filename='/var/log/build/sunlight.out', level=logging.INFO) 286 | logging.getLogger('tornado.application').setLevel(logging.WARNING) 287 | 288 | with multiprocessing.Manager() as manager: 289 | in_queue, out_queue = multiprocessing.Queue(), multiprocessing.Queue() 290 | 291 | job_statuses = manager.dict() 292 | job_statuses['JOB_IDX'], job_statuses['LAST_JOB_IDX'] = 1, 0 293 | job_ids_by_url = manager.dict() 294 | 295 | [Agent(in_queue, out_queue).start() for _ in range(NUM_AGENTS)] 296 | 297 | tornado.ioloop.PeriodicCallback( 298 | lambda: dequeue_job_statuses(out_queue, job_statuses), 299 | JOB_STATUS_CHECK_INTERVAL_MS, 300 | ).start() 301 | 302 | app = tornado.web.Application([ 303 | (r'/fetch_open_graph_tags', FetchOpenGraphTags, {'cache': {}}), 304 | (r'/submit_url', SubmitUrlHandler, {'in_queue': in_queue, 'job_statuses': job_statuses, 'job_ids_by_url': job_ids_by_url}), 305 | (r'/submit_sms', TelnyxSMSHandler, {'in_queue': in_queue, 'job_statuses': job_statuses}), 306 | (r'/fetch_job_status', FetchJobStatusHandler, {'out_queue': out_queue, 'job_statuses': job_statuses}), 307 | (r'/(.*)', StaticFileHandlerWithDefaultContentType, {'path': '/app/'}), 308 | ], static_path=STATIC_FILE_DIR) 309 | 310 | app.listen(TORNADO_SERVER_PORT) 311 | tornado.ioloop.IOLoop.current().start() 312 | -------------------------------------------------------------------------------- /model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a16z-infra/sunlight/a756f385ce1c375b0b9676620d6f732e74b2d9c4/model/__init__.py -------------------------------------------------------------------------------- /model/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a16z-infra/sunlight/a756f385ce1c375b0b9676620d6f732e74b2d9c4/model/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /model/__pycache__/agent.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a16z-infra/sunlight/a756f385ce1c375b0b9676620d6f732e74b2d9c4/model/__pycache__/agent.cpython-310.pyc -------------------------------------------------------------------------------- /model/__pycache__/diffbot.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a16z-infra/sunlight/a756f385ce1c375b0b9676620d6f732e74b2d9c4/model/__pycache__/diffbot.cpython-310.pyc -------------------------------------------------------------------------------- /model/__pycache__/prompts.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a16z-infra/sunlight/a756f385ce1c375b0b9676620d6f732e74b2d9c4/model/__pycache__/prompts.cpython-310.pyc -------------------------------------------------------------------------------- /model/agent.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import fcntl 3 | import json 4 | import logging 5 | import multiprocessing 6 | import os 7 | from threading import Thread 8 | 9 | from langchain.callbacks.base import BaseCallbackHandler 10 | from langchain.chains import LLMChain 11 | from langchain.chat_models import ChatOpenAI 12 | from langchain.prompts import PromptTemplate 13 | import tiktoken 14 | 15 | from .diffbot import DiffbotClient 16 | from .prompts import BIAS_REPORT, FACTUAL_CLAIMS, SLANT_DESCRIPTION 17 | 18 | 19 | DIFFBOT_API_KEY = os.environ['DIFFBOT_API_KEY'] 20 | REQUEST_LOG_FILE = os.environ['REQUEST_LOG_FILE'] 21 | 22 | MAX_MODEL_CONTEXT = { 23 | 'gpt-3.5-turbo': 4096, 24 | 'text-davinci-003': 4096, 25 | 'gpt-4': 8192, 26 | } 27 | 28 | 29 | class OpenAIStreamHandler(BaseCallbackHandler): 30 | def __init__(self, stream_queue, *args, **kwargs): 31 | super(OpenAIStreamHandler, self).__init__(*args, **kwargs) 32 | self.stream_queue = stream_queue 33 | 34 | def on_llm_new_token(self, token, *args, **kwargs): 35 | self.stream_queue.put(token) 36 | 37 | def on_llm_end(self, *args, **kwargs): 38 | self.stream_queue.put(False) 39 | 40 | 41 | class Agent(multiprocessing.Process): 42 | def __init__(self, in_queue, out_queue): 43 | super(Agent, self).__init__() 44 | logging.basicConfig(filename='/var/log/build/sunlight.out', level=logging.INFO) 45 | 46 | self.in_queue = in_queue 47 | self.out_queue = out_queue 48 | 49 | self.fact_prompt = PromptTemplate(input_variables=['headline', 'body'], template=FACTUAL_CLAIMS) 50 | self.critique_prompt = PromptTemplate(input_variables=['headline', 'body'], template=BIAS_REPORT) 51 | self.slant_prompt = PromptTemplate(input_variables=['bias_report'], template=SLANT_DESCRIPTION) 52 | 53 | gpt35 = ChatOpenAI(model_name='gpt-3.5-turbo', temperature=0.0, request_timeout=300) 54 | davinci = ChatOpenAI(model_name='text-davinci-003', temperature=0.0, request_timeout=300) 55 | gpt4 = ChatOpenAI(model_name='gpt-4', temperature=0.0, request_timeout=900) 56 | 57 | self.stream_queue = multiprocessing.Queue() 58 | gpt4_stream = ChatOpenAI( 59 | model_name='gpt-4', 60 | temperature=0.0, 61 | streaming=True, 62 | callbacks=[OpenAIStreamHandler(stream_queue=self.stream_queue)], 63 | request_timeout=900, 64 | ) 65 | 66 | self.fact_chains = { 67 | 'gpt-3.5-turbo': LLMChain(llm=gpt35, prompt=self.fact_prompt), 68 | 'text-davinci-003': LLMChain(llm=davinci, prompt=self.fact_prompt), 69 | 'gpt-4': LLMChain(llm=gpt4_stream, prompt=self.fact_prompt), 70 | } 71 | 72 | self.critique_chains = { 73 | 'gpt-3.5-turbo': LLMChain(llm=gpt35, prompt=self.critique_prompt), 74 | 'text-davinci-003': LLMChain(llm=davinci, prompt=self.critique_prompt), 75 | 'gpt-4': LLMChain(llm=gpt4_stream, prompt=self.critique_prompt), 76 | } 77 | 78 | self.slant_chains = { 79 | 'gpt-3.5-turbo': LLMChain(llm=gpt35, prompt=self.slant_prompt), 80 | 'text-davinci-003': LLMChain(llm=davinci, prompt=self.slant_prompt), 81 | 'gpt-4': LLMChain(llm=gpt4, prompt=self.slant_prompt), 82 | } 83 | 84 | self._load_processed_jobs() 85 | 86 | def run(self): 87 | logging.basicConfig(filename='/var/log/build/sunlight.out', level=logging.INFO) 88 | diffbot = DiffbotClient() 89 | 90 | while True: 91 | next_job = self.in_queue.get() 92 | job_id = next_job['job_id'] 93 | self.out_queue.put(('LAST_JOB_IDX', next_job['job_idx'])) 94 | url = next_job['url'] 95 | # XXX: Uncomment to configure model via client request 96 | model = 'gpt-4' # next_job.get('model', 'gpt-3.5-turbo') 97 | phone_number = next_job.get('phone_number') 98 | 99 | logging.info(f'Processing job {job_id} for URL {url} using {model}') 100 | 101 | try: 102 | self._update_job_status(job_id, url, 'Fetching article') 103 | response = diffbot.request(url, DIFFBOT_API_KEY, 'article') 104 | 105 | if 'errorCode' in response: 106 | logging.error(f'Diffbot request for {url} failed with error: {json.dumps(response)}') 107 | raise Exception('REMOTE_ERROR') 108 | 109 | original_headline = response['objects'][0]['title'] 110 | original_body = response['objects'][0]['text'] 111 | 112 | self._update_job_status(job_id, url, 'Checking context length') 113 | trimmed_body = self._check_content_length(model, original_headline, original_body) 114 | 115 | result = {} 116 | 117 | self._update_job_status(job_id, url, 'Analyzing claims') 118 | Thread( 119 | target=self.fact_chains[model].run, 120 | kwargs={'headline': original_headline, 'body': trimmed_body}, 121 | ).start() 122 | result['factual_claims'], stream_active = u'', True 123 | while stream_active: 124 | next_token = self.stream_queue.get() 125 | if type(next_token) is bool: 126 | stream_active = next_token 127 | else: 128 | result['factual_claims'] += next_token 129 | self._update_job_status(job_id, url, 'Analyzing claims', result=result, log_status=False) 130 | result['factual_claims'] = result['factual_claims'].strip() 131 | 132 | self._update_job_status(job_id, url, 'Critiquing bias', result=result) 133 | 134 | Thread( 135 | target=self.critique_chains[model].run, 136 | kwargs={'headline': original_headline, 'body': trimmed_body}, 137 | ).start() 138 | 139 | result['bias_report'], stream_active = u'', True 140 | while stream_active: 141 | next_token = self.stream_queue.get() 142 | if type(next_token) is bool: 143 | stream_active = next_token 144 | else: 145 | result['bias_report'] += next_token 146 | self._update_job_status(job_id, url, 'Identifying slant', result=result, log_status=False) 147 | result['bias_report'] = result['bias_report'].strip() 148 | 149 | self._update_job_status(job_id, url, 'Identifying slant', result=result) 150 | result['original_slant'] = self.slant_chains['gpt-3.5-turbo'].run(bias_report=result['bias_report']) 151 | 152 | if phone_number: 153 | result['phone_number'] = phone_number 154 | 155 | self._write_processed_job(job_id, url, result) 156 | self._update_job_status(job_id, url, 'Complete', result=result) 157 | except Exception as err: 158 | self._update_job_status(job_id, url, 'Failed', error=str(err)) 159 | 160 | def _write_processed_job(self, job_id, url, result): 161 | log_msg = result | {'job_id': job_id, 'url': url, 'run_time': datetime.utcnow().isoformat()} 162 | with open(REQUEST_LOG_FILE, 'a') as log_file: 163 | fcntl.flock(log_file, fcntl.LOCK_EX) 164 | log_file.write(json.dumps(log_msg) + '\n') 165 | fcntl.flock(log_file, fcntl.LOCK_UN) 166 | 167 | def _load_processed_jobs(self): 168 | if not os.path.exists(REQUEST_LOG_FILE): 169 | logging.warning('Could not load processed jobs from disk; file does not exist') 170 | return 171 | 172 | with open(REQUEST_LOG_FILE, 'r') as log_file: 173 | fcntl.flock(log_file, fcntl.LOCK_EX) 174 | num_loaded = 0 175 | for line in log_file: 176 | processed_job = json.loads(line) 177 | self._update_job_status( 178 | processed_job['job_id'], 179 | processed_job['url'], 180 | status='Complete', 181 | result={ 182 | 'factual_claims': processed_job['factual_claims'], 183 | 'bias_report': processed_job['bias_report'], 184 | 'original_slant': processed_job['original_slant'], 185 | }, 186 | ) 187 | num_loaded += 1 188 | fcntl.flock(log_file, fcntl.LOCK_UN) 189 | logging.info(f'Loaded {num_loaded} processed jobs from disk') 190 | 191 | def _update_job_status(self, job_id, url, status, result=None, error=None, log_status=True): 192 | if log_status: 193 | logging.info(f'Job {job_id} now {status}') 194 | status_msg = {'status': status, 'url': url} 195 | if result: 196 | status_msg['result'] = result 197 | if error: 198 | status_msg['error'] = error 199 | self.out_queue.put((job_id, status_msg)) 200 | 201 | def _check_content_length(self, model, headline, body): 202 | encoding = tiktoken.get_encoding('cl100k_base') 203 | max_tokens = int(MAX_MODEL_CONTEXT[model] * 3/5) 204 | 205 | headline_tokens, body_tokens = encoding.encode(headline), encoding.encode(body) 206 | if len(headline_tokens) + len(body_tokens) < max_tokens: 207 | return body 208 | else: 209 | logging.warning(f'Truncating body from {len(body_tokens)} to {max_tokens - len(headline_tokens)}') 210 | return encoding.decode(body_tokens[:max_tokens - len(headline_tokens)]) + '... [END ARTICLE]' 211 | 212 | def _stream_llm_output(self, job_id, url, result, output_field, output_prefix, status, llm_chain, chain_params): 213 | self._update_job_status(job_id, url, status) 214 | Thread(target=llm_chain.run, kwargs=chain_params).start() 215 | result[output_field], stream_active = output_prefix, True 216 | while stream_active: 217 | next_token = self.stream_queue.get() 218 | if type(next_token) is bool: 219 | stream_active = next_token 220 | else: 221 | result[output_field] += next_token 222 | self._update_job_status(job_id, url, status, result=result) 223 | result[output_field] = result[output_field].strip() 224 | -------------------------------------------------------------------------------- /model/diffbot.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | class DiffbotClient(object): 5 | 6 | BASE_API_URL = 'http://api.diffbot.com' 7 | TIMEOUT_MS = 15000 8 | 9 | def request(self, url, token, api, version=3): 10 | ''' Issue a request to the Diffbot API and return the response if valid JSON ''' 11 | params = {'url': url, 'token': token, 'timeout': self.TIMEOUT_MS} 12 | 13 | try: 14 | response = requests.get(f'{self.BASE_API_URL}/v{version}/{api}', params=params, timeout=self.TIMEOUT_MS) 15 | response.raise_for_status() 16 | except: 17 | raise Exception('REMOTE_ERROR') 18 | 19 | return response.json() 20 | -------------------------------------------------------------------------------- /model/prompts.py: -------------------------------------------------------------------------------- 1 | FACTUAL_CLAIMS = u'''Summarize the factual claims made in this article in a bulleted list separated by \u2022 unless it is too short. 2 | Instructions: 3 | 1. Order the facts by decreasing importance 4 | 2. Use extremely concise, simple language 5 | 3. If the article is very short or truncated, request that user elaborate or re-host. 6 | 7 | ### Headline: 8 | {headline} 9 | 10 | ### Body: 11 | {body}: 12 | 13 | ### Factual Claims: 14 | ''' 15 | 16 | BIAS_REPORT = '''Critique the following possibly-biased article unless it is too short. 17 | Instructions: 18 | 1. Identify any bias -- especially political bias. 19 | 2. If the article is fair, be fair in your critique. If it is biased, be harsh and critical about the issues. 20 | 3. Use specific examples and quote directly where possible. 21 | 4. Call out any opinion, hyperbole, and speculation. 22 | 5. Assess where this article lies on the political spectrum. 23 | 6. Write the critique as 3-5 paragraphs separated by two (2) newline characters. 24 | 7. If the article is very short or truncated, explain the problem in one paragraph and do not critique it. 25 | 26 | ### Headline: 27 | {headline} 28 | 29 | ### Body: 30 | {body} 31 | 32 | ### Critical Review: 33 | ''' 34 | 35 | SLANT_DESCRIPTION = '''Describe the slant critiqued in the following Bias Report in 1-2 words. Be creative, pithy, and accurate. 36 | Example slants: Fair, Left-leaning, Extreme Right, Environmentalist, Bitcoin Maximalist, Conservative, Conspiracist, Impartial 37 | 38 | ### Bias Report: 39 | {bias_report} 40 | 41 | ### Slant: 42 | ''' 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | langchain==0.0.327 2 | lxml==4.9.3 3 | openai==0.28.1 4 | six==1.16.0 5 | telnyx==2.0.0 6 | tiktoken==0.5.1 7 | tornado==6.3.3 8 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | source service.cfg 2 | 3 | # Build the Docker image 4 | docker build \ 5 | -t sunlight \ 6 | --build-arg APP_URL_PATH=$APP_URL_PATH \ 7 | . 8 | 9 | # Run the Docker container and map port 8888 to the host 10 | docker run \ 11 | -p 8888:8888 \ 12 | -e HOST_DOMAIN=$HOST_DOMAIN \ 13 | -e APP_URL_PATH=$APP_URL_PATH \ 14 | -e STATIC_FILE_DIR=$STATIC_FILE_DIR \ 15 | -e TORNADO_SERVER_PORT=$TORNADO_SERVER_PORT \ 16 | -e REQUEST_LOG_FILE=$REQUEST_LOG_FILE \ 17 | -e DIFFBOT_API_KEY=$DIFFBOT_API_KEY \ 18 | -e OPENAI_API_KEY=$OPENAI_API_KEY \ 19 | -e TWILIO_AUTH_TOKEN=$TWILIO_AUTH_TOKEN \ 20 | -e TELNYX_API_KEY=$TELNYX_API_KEY \ 21 | -e TELNYX_PROFILE_ID=$TELNYX_PROFILE_ID \ 22 | -e TELNYX_PHONE_NUMBER=$TELNYX_PHONE_NUMBER \ 23 | sunlight 24 | -------------------------------------------------------------------------------- /service.cfg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export HOST_DOMAIN="http://localhost:8888" 4 | export APP_URL_PATH="" 5 | export STATIC_FILE_DIR="/app/static" 6 | export TORNADO_SERVER_PORT="8888" 7 | export REQUEST_LOG_FILE="/var/log/build/sunlight_requests.jsonl" 8 | 9 | export DIFFBOT_API_KEY="PLACEHOLDER" 10 | export OPENAI_API_KEY="PLACEHOLDER" 11 | export TWILIO_AUTH_TOKEN="PLACEHOLDER" 12 | 13 | export TELNYX_API_KEY="PLACEHOLDER" 14 | export TELNYX_PROFILE_ID="PLACEHOLDER" 15 | export TELNYX_PHONE_NUMBER="PLACEHOLDER" 16 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sunlight ☀️ 6 | 122 | 123 | 124 |

Sunlight 🎯

125 | 126 |
127 |
128 |
129 | 130 |
131 |
132 | 133 | 134 |
135 |
136 | 137 | 138 | 139 | 140 | 141 | 142 |
143 |
144 |
145 | 146 |
147 | 148 | 158 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /static/privacy_policy.txt: -------------------------------------------------------------------------------- 1 | [DE-SPIN] PRIVACY POLICY 2 | 3 | Last Updated: [Insert Date] 4 | This Privacy Policy describes how [Company Name] (“we”, “us,” “our,” or the “Company”) collects, uses and discloses information about individuals who use our website ([  ]) and any related services, tools and features (collectively, the “Services”). For the purposes of this Privacy Policy, “you” and “your” means you as the user of the Services, whether you are a customer, website visitor or another individual whose information we have collected pursuant to this Privacy Policy. 5 | 6 | Please read this Privacy Policy carefully. By using, accessing, or downloading any of the Services, you agree to the collection, use, and disclosure of your information as described in this Privacy Policy. If you do not agree to this Privacy Policy, please do not use, access or download any of the Services. 7 | 1. CHANGES TO THIS PRIVACY POLICY 8 | We may modify this Privacy Policy from time to time in which case we will update the “Last Updated” date at the top of this Privacy Policy. If we make material changes to the way in which we use information we collect, we will use reasonable efforts to notify you (such as by emailing you at the last email address you provided us, by posting notice of such changes on the Services, or by other means consistent with applicable law) and will take additional steps as required by applicable law. If you do not agree to any updates to this Privacy Policy, please do not access or continue to use the Services. 9 | 2. HOW WE COLLECT AND USE YOUR INFORMATION 10 | When you access or use the Services, we collect certain categories of information about you from a variety of sources. 11 | 12 | Information We Collect Directly from You 13 | 14 | Some features of the Services may require you to directly provide us with certain information about yourself. You may elect not to provide this information, but doing so may prevent you from using or accessing these features. Information that you directly submit through our Services may include: 15 | 16 | • Any articles or links to articles that you input into the Services. We use this information to provide you with the Services. 17 | • [Any information you choose to include in communications with us, for example, when sending a message through the Services.] 18 | Information We Collect Automatically 19 | 20 | We also automatically collect certain information about your interaction with the Services (“Usage Data”). To do this, we may use cookies, web beacons/clear gifs and other geolocation tracking technologies (“Tracking Technologies”). Usage Data includes: 21 | 22 | • The following device information: IP address[, unique device identifier, device type and operating system.] 23 | • [The following browser information: browser type.] 24 | • [The following location information: [approximate/precise] geolocation.] 25 | • [The following information regarding your interaction with the Services: log data, date and time stamps, clickstream data and ad impressions.] 26 | We use Usage Data to prevent fraud [and] better understand user interaction with the Services [please insert any additional uses for above list of data points – e.g., tailor features and content to you, market to you, provide you with offers or promotions and run analytics]. For more information on how we use Tracking Technologies and your choices, see the section below, Cookies and Other Tracking Technologies. 27 | 28 | [Information We Obtain from Third Parties 29 | 30 | Finally, we may obtain information about you from third parties, such as information we collect by going directly to third parties. This includes information we may collect from certain third-party social media platforms (i.e., Twitter). We use this information to better understand what is being said about the Services on these social media platforms. 31 | 32 | Any information we obtain from third parties will be treated in accordance with this Privacy Policy. We are not responsible or liable for the accuracy of the information provided to us by third parties and are not responsible for any third party’s policies or practices. For more information, see the section below, Third Party Websites and Links.] 33 | 34 | In addition to the foregoing, we may use any of the above information to provide the Services, comply with any applicable legal obligations, to enforce any applicable terms of service, and to protect or defend the Services, our rights, and the rights of our users or others. 35 | 3. COOKIES AND OTHER TRACKING TECHNOLOGIES 36 | Most browsers accept cookies automatically, but you may be able to control the way in which your devices permit the use of Tracking Technologies. If you so choose, you may block or delete our cookies from your browser; however, blocking or deleting cookies may cause some of the Services, including certain features and general functionality, to work incorrectly. If you have questions regarding the specific information about you that we process or retain, as well as your choices regarding our collection and use practices, please contact us using the information listed below. 37 | 38 | [To opt out of tracking by Google Analytics, click here.] 39 | 40 | Your browser settings may allow you to transmit a “do not track” signal, “opt-out preference” signal, or other mechanism for exercising your choice regarding the collection of your information when you visit various websites. Like many websites, our website is not designed to respond to such signals, and we do not use or disclose your information in any way that would legally require us to recognize opt-out preference signals. To learn more about “do not track” signals, you can visit http://www.allaboutdnt.com/. 41 | 4. HOW WE DISCLOSE YOUR INFORMATION 42 | In certain circumstances, we may disclose your information to third parties for legitimate purposes subject to this Privacy Policy. Such circumstances may include: 43 | 44 | • With vendors or other service providers (e.g., cloud storage providers) 45 | • [With our affiliates or otherwise within our corporate group] 46 | • In connection with or anticipation of an asset sale, merger, bankruptcy, or other business transaction 47 | • To comply with applicable law or any obligations thereunder, including cooperation with law enforcement, judicial orders, and regulatory inquiries 48 | • To enforce any applicable terms of service and ensure the safety and security of the Company and/or its users 49 | • With professional advisors, such as auditors, law firms, or accounting firms 50 | 5. USER GENERATED CONTENT 51 | Certain of the Services may permit you to submit certain content or other materials (“User-Generated Content” or “UGC”). We or others may store, display, reproduce, publish, or otherwise use UGC, and may or may not attribute it to you. Others may also have access to UGC and may have the ability to share it with third parties. If you choose to submit UGC to any public area of the Services, your UGC will be considered “public” and will be accessible by anyone, including the Company. 52 | Please note that we do not control who will have access to the information that you choose to make available to others, and cannot ensure that parties who have access to such information will respect your privacy or keep it secure. We are not responsible for the privacy or security of any information that you make publicly available on the features permitting creation of UGC or what others do with information you share with them on such platforms. We are not responsible for the accuracy, use or misuse of any UGC that you disclose or receive from third parties through the forums or email lists. 53 | 6. [THIRD PARTY WEBSITES AND LINKS 54 | We may provide links to websites or other online platforms operated by third parties. If you follow links to sites not affiliated or controlled by us, you should review their privacy and security policies and other terms and conditions. We do not guarantee and are not responsible for the privacy or security of these sites, including the accuracy, completeness, or reliability of information found on these sites. Information you provide on public or semi-public venues, including information you share on third-party social networking platforms (such as Facebook or Twitter) may also be viewable by other users of the Services and/or users of those third-party platforms without limitation as to its use by us or by a third party. Our inclusion of such links does not, by itself, imply any endorsement of the content on such platforms or of their owners or operators, except as disclosed on the Services.] 55 | 7. CHILDREN’S PRIVACY 56 | We do not seek or knowingly collect any personal information about children under 13 years of age. If we become aware that we have unknowingly collected information about a child under 13 years of age, we will make commercially reasonable efforts to delete such information. If you are the parent or guardian of a child under 13 years of age who has provided us with their personal information, you may contact us using the below information to request that it be deleted. 57 | 8. SECURITY AND RETENTION OF YOUR INFORMATION 58 | Please be aware that, despite our reasonable efforts to protect your information, no security measures are perfect or impenetrable, and we cannot guarantee “perfect security.” Any information you send to us electronically, while using the Services or otherwise interacting with us, may not be secure while in transit. We recommend that you do not use unsecure channels to communicate sensitive or confidential information to us. 59 | 9. HOW TO CONTACT US 60 | Should you have any questions about our privacy practices or this Privacy Policy, please email us at [EMAIL]. 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /static/terms_of_service.txt: -------------------------------------------------------------------------------- 1 | [DE-SPIN] TERMS OF SERVICE 2 | Last Revised on August [  ], 2023 3 | Please take the time to read these Terms of Service (these “Terms”) for the website, [insert url] (the “Website”) operated on behalf of [Insert Legal Entity Name] (“Company”, “we” or “us”). The Website, and any applications, content, tools, features and functionality offered on or through our Website are collectively referred to as the “Services”. 4 | These Terms govern your access to and use of the Services. Please read these Terms carefully, as they include important information about your legal rights. By accessing and/or using the Services, you are agreeing to these Terms. If you do not understand or agree to these Terms, please do not use the Services. 5 | For purposes of these Terms, “you” and “your” means you as the user of the Services. If you use the Services on behalf of a company or other entity then “you” includes you and that entity, and you represent and warrant that (a) you are an authorized representative of the entity with the authority to bind the entity to these Terms, and (b) you agree to these Terms on the entity’s behalf. 6 | SECTION 8 CONTAINS AN ARBITRATION CLAUSE AND CLASS ACTION WAIVER. BY AGREEING TO THESE TERMS, YOU AGREE (A) TO RESOLVE ALL DISPUTES (WITH LIMITED EXCEPTION) RELATED TO THE COMPANY’S SERVICES AND/OR PRODUCTS THROUGH BINDING INDIVIDUAL ARBITRATION, WHICH MEANS THAT YOU WAIVE ANY RIGHT TO HAVE THOSE DISPUTES DECIDED BY A JUDGE OR JURY, AND (B) TO WAIVE YOUR RIGHT TO PARTICIPATE IN CLASS ACTIONS, CLASS ARBITRATIONS, OR REPRESENTATIVE ACTIONS, AS SET FORTH BELOW. YOU HAVE THE RIGHT TO OPT-OUT OF THE ARBITRATION CLAUSE AND THE CLASS ACTION WAIVER AS EXPLAINED IN SECTION 8. 7 | TABLE OF CONTENTS 8 | 1. OVERVIEW OF SERVICES 1 9 | 2. WHO MAY USE THE SERVICES 2 10 | 3. LOCATION OF OUR PRIVACY POLICY 2 11 | 4. RIGHTS WE GRANT YOU 2 12 | 5. OWNERSHIP AND CONTENT 4 13 | 6. THIRD PARTY SERVICES AND MATERIALS 6 14 | 7. DISCLAIMERS, LIMITATIONS OF LIABILITY AND INDEMNIFICATION 6 15 | 8. ARBITRATION AND CLASS ACTION WAIVER 8 16 | 9. ADDITIONAL PROVISIONS 10 17 | 18 | 1. OVERVIEW OF SERVICES 19 | 1.1 The Services allow you to submit text inputs such as URL addresses of online news articles or similar source information (such inputs and the content of any such articles or other source information, the “Input Content”). The Services then utilize artificial intelligence technology to generate an output based on your Input Content (“Output”). 20 | 1.2 You may not direct the Services to generate any Output in violation of any applicable intellectual property right, contractual restriction or other law. Further, you may not utilize the Services to facilitate the provision of investment advice or to analyze or assess any investment advice or investment advisory service . By submitting any Input Content through the Services, you represent and warrant that you have, or have obtained, all rights, licenses, consents, permissions, power and/or authority necessary to submit and use (and allow us to use) such Input Content in connection with the Services. You represent and warrant that your submission of an Input Content in connection with your use of the Services, including to generate an Output, will not breach any law or any third party’s terms and conditions associated with such Input Content. 21 | 1.3 The Output is intended, but not guaranteed, to provide a summary of such Input Content as well as a report of the potential biases contained in the original Input Content or other original source information. The Services are incapable of marketing or soliciting interest in investment advisory services or investment products (and should not be utilized by anyone, including consumers of advisory services and securities investors, to analyze or assess such services or products). The Services also cannot provide investment advice or legal advice. As such, the Services and their Output should not be treated as any of the foregoing (or utilized to provide any of the foregoing). The Services are not able to independently verify any factual claims in the Input Content, or correct all inaccuracies in the Input Content or other original source information. Accordingly, we make no representations or warranties related to any Output, including as to the Output’s accuracy, completeness or suitability. 22 | 1.4 Neither the Services nor their Output constitute an offer to sell or the solicitation of an offer to purchase any security, and no investment advisory services are solicited or provided through the Services or the Output. For the avoidance of doubt, neither the Services nor their Output are provided by an investment adviser and are not investment advisory services of a16z Perennial Management L.P., AH Capital Management L.L.C, or any of their affiliates (whether currently in existence or existing in the future). 23 | 2. WHO MAY USE THE SERVICES 24 | You must be 13 years of age or older to use the Services. Minors under the age of majority in their jurisdiction but that are at least 13 years of age are only permitted to use the Services if the minor’s parent or guardian accepts these Terms on the minor’s behalf prior to use of the Services. Children under the age of 13 are not permitted to use the Services. By using the Services, you represent and warrant that you meet these requirements. 25 | 3. LOCATION OF OUR PRIVACY POLICY 26 | 3.1 Privacy Policy. Our Privacy Policy describes how we handle the information you provide to us when you use the Services. For an explanation of our privacy practices, please visit our Privacy Policy located at [  ]. 27 | 4. RIGHTS WE GRANT YOU 28 | 4.1 Right to Use Services. We hereby permit you to use the Services for your personal non-commercial use only, provided that you comply with these Terms in connection with all such use. If any software, content or other materials owned or controlled by us are distributed to you as part of your use of the Services, we hereby grant you, a personal, non-assignable, non-sublicensable, non-transferrable, and non-exclusive right and license to access and display such software, content and materials provided to you as part of the Services, in each case for the sole purpose of enabling you to use the Services as permitted by these Terms. Your access and use of the Services may be interrupted from time to time for any of several reasons, including, without limitation, the malfunction of equipment, periodic updating, maintenance or repair of the Service or other actions that Company, in its sole discretion, may elect to take. 29 | 4.2 Restrictions On Your Use of the Services. You may not do any of the following in connection with your use of the Services, unless applicable laws or regulations prohibit these restrictions or you have our written permission to do so: 30 | (a) download, modify, copy, distribute, transmit, display, perform, reproduce, duplicate, publish, license, create derivative works from, or offer for sale any proprietary software owned by the Company and deployed for the Services, except for temporary files that are automatically cached by your web browser for display purposes, or as otherwise expressly permitted in these Terms; 31 | (b) duplicate, decompile, reverse engineer, disassemble or decode the Services (including any underlying idea or algorithm), or attempt to do any of the same; 32 | (c) use, reproduce or remove any copyright, trademark, service mark, trade name, slogan, logo, image, or other proprietary notation displayed on or through the Services; 33 | (d) use automation software (bots), hacks, modifications (mods) or any other unauthorized third-party software designed to modify the Services; 34 | (e) exploit the Services for any commercial purpose, except as specifically permitted by the Company in connection with your permitted use of the Services, including without limitation communicating or facilitating any commercial advertisement or solicitation; 35 | (f) use the Services to create or develop any competing products or services or to power, enable, or train other artificial intelligence and machine learning models, tools or technologies; 36 | (g) access or use the Services in any manner that could disable, overburden, damage, disrupt or impair the Services or interfere with any other party’s access to or use of the Services or use any device, software or routine that causes the same; 37 | (h) attempt to gain unauthorized access to, interfere with, damage or disrupt the Services, accounts registered to other users, or the computer systems or networks connected to the Services; 38 | (i) circumvent, remove, alter, deactivate, degrade or thwart any technological measure or content protections of the Services; 39 | (j) use any robot, spider, crawlers, scraper, or other automatic device, process, software or queries that intercepts, “mines,” scrapes, extracts, or otherwise accesses the Services to monitor, extract, copy or collect information or data from or through the Services, or engage in any manual process to do the same; 40 | (k) introduce any viruses, trojan horses, worms, logic bombs or other materials that are malicious or technologically harmful into our systems; 41 | (l) submit, transmit, display, generate, create, perform, post or store any content that is inaccurate, unlawful, defamatory, infringing, obscene, lewd, lascivious, filthy, excessively violent, pornographic, invasive of privacy or publicity rights, harassing, threatening, abusive, inflammatory, harmful, hateful, cruel or insensitive, deceptive, or otherwise objectionable, use the Services for illegal, harassing, bullying, unethical or disruptive purposes, or otherwise use the Services in a manner that is infringing, obscene, lewd, lascivious, filthy, excessively violent, harassing, harmful, hateful, cruel or insensitive, deceptive, threatening, abusive, inflammatory, pornographic, inciting, organizing, promoting or facilitating violence or criminal or harmful activities, defamatory, obscene or otherwise objectionable; 42 | (m) submit any personal information of any person under the age of 18 through the Services; 43 | (n) use the Services in violation of any Third Party Software License Terms (as defined below); 44 | (o) violate any applicable law or regulation in connection with your access to or use of the Services; or 45 | (p) access or use the Services in any way not expressly permitted by these Terms. 46 | 4.3 Beta Offerings. From time to time, we may, in our sole discretion, include certain test or beta features or products in the Services (“Beta Offerings”) as we may designate from time to time. Your use of any Beta Offering is completely voluntary. The Beta Offerings are provided on an “as is” basis and may contain errors, defects, bugs, or inaccuracies that could cause failures, corruption or loss of data and information from any connected device. You acknowledge and agree that all use of any Beta Offering is at your sole risk. If we provide you any Beta Offerings on a closed beta or confidential basis, we will notify you of such as part of your use of the Beta Offerings. For any such confidential Beta Offerings, you agree to not disclose, divulge, display, or otherwise make available any of the Beta Offerings without our prior written consent. 47 | 5. OWNERSHIP AND CONTENT 48 | 5.1 Ownership of the Services. The Services, including their “look and feel” (e.g., text, graphics, images, logos), proprietary content, information and other materials, and any technology used to generate any Output, are protected under copyright, trademark and other intellectual property laws. You agree that the Company and/or its licensors own all right, title and interest in and to the Services (including any and all intellectual property rights therein) and you agree not to take any action(s) inconsistent with such ownership interests. We and our licensors reserve all rights in connection with the Services and its content (other than Your Content), including, without limitation, the exclusive right to create derivative works. 49 | 5.2 Ownership of Trademarks. The Company’s name, trademarks, logo and all related names, logos, product and service names, designs and slogans are trademarks of the Company or its affiliates or licensors. Other names, logos, product and service names, designs and slogans that appear on the Services are the property of their respective owners, who may or may not be affiliated with, connected to, or sponsored by us. 50 | 5.3 Your Content License Grant. In connection with your use of the Services, you may be able to post, upload, or submit content and other information (such as your Input Content) on or through the Services (“Your Content”). As between the Company and you, the Company does not claim any ownership in Your Content[, including any Output associated with your Input Content], provided that the Company or its affiliates or their respective licensors own and will continue to own any software or technology that was used to generate any Output. In order to operate the Service, we must obtain from you certain license rights in Your Content so that actions we take in operating the Service are not considered legal violations. Accordingly, by using the Service and uploading Your Content, you grant us a license to access, use, host, cache, store, reproduce, transmit, display, publish, distribute, and modify Your Content to operate, improve, promote and provide the Services, including to reproduce, transmit, display, publish, and distribute the Output based on your Input Content. You agree that these rights and licenses are royalty free, transferable, sub-licensable, worldwide and irrevocable (for so long as Your Content is stored with us), and include a right for us to make Your Content available to, and pass these rights along to, others with whom we have contractual relationships related to the provision of the Services, solely for the purpose of operating, improving and providing such Services, and to otherwise permit access to or disclose Your Content to third parties if we determine such access is necessary to comply with our legal obligations. To the fullest extent permitted by applicable law, the Company reserves the right, and has absolute discretion, to remove, screen, edit, or delete any of Your Content at any time, for any reason, and without notice. By posting or submitting Your Content through the Services, you represent and warrant that you have, or have obtained, all rights, licenses, consents, permissions, power and/or authority necessary to grant the rights granted herein for Your Content. 51 | 5.4 Notice of Infringement – DMCA (Copyright) Policy 52 | If you believe that any text, graphics, photos, audio, videos or other materials or works uploaded, downloaded or appearing on the Services have been copied in a way that constitutes copyright infringement, you may submit a notification to our copyright agent in accordance with 17 USC 512(c) of the Digital Millennium Copyright Act (the “DMCA”), by providing the following information in writing: 53 | (a) identification of the copyrighted work that is claimed to be infringed; 54 | (b) identification of the allegedly infringing material that is requested to be removed, including a description of where it is located on the Service; 55 | (c) information for our copyright agent to contact you, such as an address, telephone number and e-mail address; 56 | (d) a statement that you have a good faith belief that the identified, allegedly infringing use is not authorized by the copyright owners, its agent or the law; 57 | (e) a statement that the information above is accurate, and under penalty of perjury, that you are the copyright owner or the authorized person to act on behalf of the copyright owner; and 58 | (f) the physical or electronic signature of a person authorized to act on behalf of the owner of the copyright or of an exclusive right that is allegedly infringed. 59 | Notices of copyright infringement claims should be sent by mail to: [Company Legal Entity], Attn: [DMCA Agent], [insert DMCA Agent Contact Info]; or by e-mail to [insert DMCA Agent email address]. It is our policy, in appropriate circumstances and at our discretion, to disable or terminate the accounts of users who repeatedly infringe copyrights or intellectual property rights of others. 60 | A user of the Services who has uploaded or posted materials identified as infringing as described above may supply a counter-notification pursuant to sections 512(g)(2) and (3) of the DMCA. When we receive a counter-notification, we may reinstate the posts or material in question, in our sole discretion. To file a counter-notification with us, you must provide a written communication (by fax or regular mail or by email) that sets forth all of the items required by sections 512(g)(2) and (3) of the DMCA. Please note that you will be liable for damages if you materially misrepresent that content or an activity is not infringing the copyrights of others. 61 | 5.5 Ownership of Feedback. We welcome feedback, comments and suggestions for improvements to the Services (“Feedback”). You acknowledge and expressly agree that any contribution of Feedback does not and will not give or grant you any right, title or interest in the Services or in any such Feedback. All Feedback becomes the sole and exclusive property of the Company, and the Company may use and disclose Feedback in any manner and for any purpose whatsoever without further notice or compensation to you and without retention by you of any proprietary or other right or claim. You hereby assign to the Company any and all right, title and interest (including, but not limited to, any patent, copyright, trade secret, trademark, show-how, know-how, moral rights and any and all other intellectual property right) that you may have in and to any and all Feedback. 62 | 6. THIRD PARTY SERVICES AND MATERIALS 63 | 6.1 Use of Third Party Materials in the Services. Certain Services may display, include or make available content, data, information, applications, software or materials from third parties (“Third Party Materials”) or provide links to certain third party websites. Third Party Materials include the open source software or other third party software, such as third party large language models, that are included in the artificial intelligence and machine learning models you access or use through the Services. By using the Services, you acknowledge and agree that the Company is not responsible for examining or evaluating the content, accuracy, completeness, availability, timeliness, validity, copyright compliance, legality, decency, quality or any other aspect of such Third Party Materials or websites. We do not warrant or endorse and do not assume and will not have any liability or responsibility to you or any other person for any third-party services, Third Party Materials or third-party websites, or for any other materials, products, or services of third parties. Third Party Materials and links to other websites are provided solely as a convenience to you. Your access and use of Third Party Materials may be governed by additional terms and conditions of the provider of such Third Party Materials, which you agree to comply with. 64 | 7. DISCLAIMERS, LIMITATIONS OF LIABILITY AND INDEMNIFICATION 65 | 7.1 Disclaimers. 66 | (a) Your access to and use of the Services and any Output are at your own risk. You understand and agree that the Services and any Output are provided to you on an “AS IS” and “AS AVAILABLE” basis. Without limiting the foregoing, to the maximum extent permitted under applicable law, the Company, its parents, affiliates, related companies, officers, directors, employees, agents, representatives, partners and licensors (the “the Company Entities”) DISCLAIM ALL WARRANTIES AND CONDITIONS, WHETHER EXPRESS OR IMPLIED, OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. 67 | (b) The Company Entities make no warranty or representation and disclaim all responsibility and liability for: (a) the completeness, accuracy, availability, timeliness, security or reliability of the Services and any Output; (b) the infringement of the rights of any third party in your use of any Output; (c) any harm to your computer system, loss of data, or other harm that results from your access to or use of the Services; (d) the operation or compatibility with any other application or any particular system or device; (e) whether the Services will meet your requirements or be available on an uninterrupted, secure or error-free basis; and (f) the deletion of, or the failure to store or transmit, Your Content, Outputs and other communications maintained by the Services. 68 | (c) The Outputs generated by the Services are for entertainment purposes only. The Services are not error-free and may generate Outputs containing incorrect information. No advice or information, whether oral or written, obtained from the Company Entities or through the Services, will create any warranty or representation not expressly made herein. You should not rely on the Services or any Output for advice of any kind, including medical, legal, investment, financial or other professional advice. Any Output is not a substitute for advice from a qualified professional. 69 | (d) THE LAWS OF CERTAIN JURISDICTIONS, INCLUDING THE STATE OF NEW JERSEY, DO NOT ALLOW LIMITATIONS ON IMPLIED WARRANTIES OR THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES AS SET FORTH IN SECTION 7.2 BELOW. IF THESE LAWS APPLY TO YOU, SOME OR ALL OF THE ABOVE DISCLAIMERS, EXCLUSIONS, OR LIMITATIONS MAY NOT APPLY TO YOU, AND YOU MAY HAVE ADDITIONAL RIGHTS. 70 | (e) THE COMPANY ENTITIES TAKE NO RESPONSIBILITY AND ASSUME NO LIABILITY FOR ANY CONTENT THAT YOU, ANOTHER USER, OR A THIRD PARTY CREATES, UPLOADS, POSTS, SENDS, RECEIVES, OR STORES ON OR THROUGH OUR SERVICES, INCLUDING ANY OUTPUT. 71 | (f) YOU UNDERSTAND AND AGREE THAT YOU MAY BE EXPOSED TO CONTENT THAT MIGHT BE OFFENSIVE, ILLEGAL, MISLEADING, OR OTHERWISE INAPPROPRIATE, NONE OF WHICH THE COMPANY ENTITIES WILL BE RESPONSIBLE FOR. 72 | 7.2 Limitations of Liability. TO THE EXTENT NOT PROHIBITED BY LAW, YOU AGREE THAT IN NO EVENT WILL THE COMPANY ENTITIES BE LIABLE (A) FOR DAMAGES OF ANY KIND, INCLUDING INDIRECT SPECIAL, EXEMPLARY, INCIDENTAL, CONSEQUENTIAL OR PUNITIVE DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF USE, DATA OR PROFITS, BUSINESS INTERRUPTION OR ANY OTHER DAMAGES OR LOSSES, ARISING OUT OF OR RELATED TO YOUR USE OR INABILITY TO USE THE SERVICES), HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY, WHETHER UNDER THESE TERMS OR OTHERWISE ARISING IN ANY WAY IN CONNECTION WITH THE SERVICES, OUTPUTS OR THESE TERMS AND WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) EVEN IF THE COMPANY ENTITIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, OR (B) FOR ANY OTHER CLAIM, DEMAND OR DAMAGES WHATSOEVER RESULTING FROM OR ARISING OUT OF OR IN CONNECTION WITH THESE TERMS, OUTPUTS, OR THE DELIVERY, USE OR PERFORMANCE OF THE SERVICES OR OUTPUTS. THE COMPANY ENTITIES’ TOTAL LIABILITY TO YOU FOR ANY DAMAGES FINALLY AWARDED SHALL NOT EXCEED THE GREATER OF ONE HUNDRED DOLLARS ($100.00), OR THE AMOUNT YOU PAID THE COMPANY ENTITIES, IF ANY, IN THE PAST SIX (6) MONTHS FOR THE SERVICES (OR OFFERINGS PURCHASED ON THE SERVICES) GIVING RISE TO THE CLAIM. THE FOREGOING LIMITATIONS WILL APPLY EVEN IF THE ABOVE STATED REMEDY FAILS OF ITS ESSENTIAL PURPOSE. 73 | 7.3 Indemnification. By entering into these Terms and accessing or using the Services, you agree that you shall defend, indemnify and hold the Company Entities harmless from and against any and all claims, costs, damages, losses, liabilities and expenses (including attorneys’ fees and costs) incurred by the Company Entities arising out of or in connection with: (a) your violation or breach of any term of these Terms or any applicable law or regulation; (b) your violation of any rights of any third party, including through your generation or use of any Output; (c) your misuse of the Services; (d) Your Content, or (e) your negligence or wilful misconduct. If you are obligated to indemnify any Company Entity hereunder, then you agree that Company (or, at its discretion, the applicable Company Entity) will have the right, in its sole discretion, to control any action or proceeding and to determine whether Company wishes to settle, and if so, on what terms, and you agree to fully cooperate with Company in the defense or settlement of such claim. 74 | 8. ARBITRATION AND CLASS ACTION WAIVER 75 | 8.1 PLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT AND TO HAVE A JURY HEAR YOUR CLAIMS. IT CONTAINS PROCEDURES FOR MANDATORY BINDING ARBITRATION AND A CLASS ACTION WAIVER. 76 | 8.2 Informal Process First. You and the Company agree that in the event of any dispute, either party will first contact the other party and make a good faith sustained effort to resolve the dispute before resorting to more formal means of resolution, including without limitation, any court action, after first allowing the receiving party 30 days in which to respond. Both you and the Company agree that this dispute resolution procedure is a condition precedent which must be satisfied before initiating any arbitration against the other party. 77 | 8.3 Arbitration Agreement and Class Action Waiver. After the informal dispute resolution process, any remaining dispute, controversy, or claim (collectively, “Claim”) relating in any way to the Company’s services and/or products, including the Services and any Output, and any use or access or lack of access thereto, will be resolved by arbitration, including threshold questions of arbitrability of the Claim. You and the Company agree that any Claim will be settled by final and binding arbitration, using the English language, administered by JAMS under its Comprehensive Arbitration Rules and Procedures (the “JAMS Rules”) then in effect (those rules are deemed to be incorporated by reference into this section, and as of the date of these Terms). Because your contract with the Company, these Terms, and this Arbitration Agreement concern interstate commerce, the Federal Arbitration Act (“FAA”) governs the arbitrability of all disputes. However, the arbitrator will apply applicable substantive law consistent with the FAA and the applicable statute of limitations or condition precedent to suit. Arbitration will be handled by a sole arbitrator in accordance with the JAMS Rules. Judgment on the arbitration award may be entered in any court that has jurisdiction. Any arbitration under these Terms will take place on an individual basis – class arbitrations and class actions are not permitted. You understand that by agreeing to these Terms, you and the Company are each waiving the right to trial by jury or to participate in a class action or class arbitration. 78 | 8.4 Exceptions. Notwithstanding the foregoing, you and the Company agree that the following types of disputes will be resolved in a court of proper jurisdiction: 79 | (a) disputes or claims within the jurisdiction of a small claims court consistent with the jurisdictional and dollar limits that may apply, as long as it is brought and maintained as an individual dispute and not as a class, representative, or consolidated action or proceeding; 80 | (b) disputes or claims where the sole form of relief sought is injunctive relief (including public injunctive relief); or 81 | (c) intellectual property disputes. 82 | 8.5 Costs of Arbitration. Payment of all filing, administration, and arbitrator costs and expenses will be governed by the JAMS Rules, except that if you demonstrate that any such costs and expenses owed by you under those rules would be prohibitively more expensive than a court proceeding, the Company will pay the amount of any such costs and expenses that the arbitrator determines are necessary to prevent the arbitration from being prohibitively more expensive than a court proceeding (subject to possible reimbursement as set forth below). 83 | Fees and costs may be awarded as provided pursuant to applicable law. If the arbitrator finds that either the substance of your claim or the relief sought in the demand is frivolous or brought for an improper purpose (as measured by the standards set forth in Federal Rule of Civil Procedure 11(b)), then the payment of all fees will be governed by the JAMS rules. In that case, you agree to reimburse the Company for all monies previously disbursed by it that are otherwise your obligation to pay under the applicable rules. If you prevail in the arbitration and are awarded an amount that is less than the last written settlement amount offered by the Company before the arbitrator was appointed, the Company will pay you the amount it offered in settlement. The arbitrator may make rulings and resolve disputes as to the payment and reimbursement of fees or expenses at any time during the proceeding and upon request from either party made within 14 days of the arbitrator’s ruling on the merits. 84 | 8.6 Opt-Out. You have the right to opt-out and not be bound by the arbitration provisions set forth in these Terms by sending written notice of your decision to opt-out to [insert email address]. The notice must be sent to the Company within thirty (30) days of your first registering to use the Services or agreeing to these Terms; otherwise you shall be bound to arbitrate disputes on a non-class basis in accordance with these Terms. If you opt out of only the arbitration provisions, and not also the class action waiver, the class action waiver still applies. You may not opt out of only the class action waiver and not also the arbitration provisions. If you opt-out of these arbitration provisions, the Company also will not be bound by them. 85 | 8.7 WAIVER OF RIGHT TO BRING CLASS ACTION AND REPRESENTATIVE CLAIMS. To the fullest extent permitted by applicable law, you and the Company each agree that any proceeding to resolve any dispute, claim, or controversy will be brought and conducted ONLY IN THE RESPECTIVE PARTY’S INDIVIDUAL CAPACITY AND NOT AS PART OF ANY CLASS (OR PURPORTED CLASS), CONSOLIDATED, MULTIPLE-PLAINTIFF, OR REPRESENTATIVE ACTION OR PROCEEDING (“CLASS ACTION”). You and the Company AGREE TO WAIVE THE RIGHT TO PARTICIPATE AS A PLAINTIFF OR CLASS MEMBER IN ANY CLASS ACTION. You and the Company EXPRESSLY WAIVE ANY ABILITY TO MAINTAIN A CLASS ACTION IN ANY FORUM. If the dispute is subject to arbitration, THE ARBITRATOR WILL NOT HAVE THE AUTHORITY TO COMBINE OR AGGREGATE CLAIMS, CONDUCT A CLASS ACTION, OR MAKE AN AWARD TO ANY PERSON OR ENTITY NOT A PARTY TO THE ARBITRATION. Further, you and the Company agree that the ARBITRATOR MAY NOT CONSOLIDATE PROCEEDINGS FOR MORE THAN ONE PERSON’S CLAIMS, AND IT MAY NOT OTHERWISE PRESIDE OVER ANY FORM OF A CLASS ACTION. For the avoidance of doubt, however, you can seek public injunctive relief to the extent authorized by law and consistent with the Exceptions clause above. 86 | IF THIS CLASS ACTION WAIVER IS LIMITED, VOIDED, OR FOUND UNENFORCEABLE, THEN, UNLESS THE PARTIES MUTUALLY AGREE OTHERWISE, THE PARTIES’ AGREEMENT TO ARBITRATE SHALL BE NULL AND VOID WITH RESPECT TO SUCH PROCEEDING SO LONG AS THE PROCEEDING IS PERMITTED TO PROCEED AS A CLASS ACTION. If a court decides that the limitations of this paragraph are deemed invalid or unenforceable, any putative class, private attorney general, or consolidated or representative action must be brought in a court of proper jurisdiction and not in arbitration. 87 | 9. ADDITIONAL PROVISIONS 88 | 9.1 [SMS Messaging and Phone Calls. Certain portions of the Services may allow us to contact you via telephone or text messages. You agree that the Company may contact you via telephone or text messages (including by an automatic telephone dialing system) at any of the phone numbers provided by you or on your behalf in connection with your use of the Services, including for marketing purposes. You understand that you are not required to provide this consent as a condition of purchasing any Offerings. You also understand that you may opt out of receiving text messages from us at any time, either by texting the word “STOP” using the mobile device that is receiving the messages, or by contacting [insert email address]. If you do not choose to opt out, we may contact you as outlined in our Privacy Policy.] 89 | 9.2 Updating These Terms. We may modify these Terms from time to time in which case we will update the “Last Revised” date at the top of these Terms. If we make changes that are material, we will use reasonable efforts to attempt to notify you, such as by e-mail and/or by placing a prominent notice on the Website. However, it is your sole responsibility to review these Terms from time to time to view any such changes. The updated Terms will be effective as of the time of posting, or such later date as may be specified in the updated Terms. Your continued access or use of the Services after the modifications have become effective will be deemed your acceptance of the modified Terms. No amendment shall apply to a dispute for which an arbitration has been initiated prior to the change in Terms. 90 | 9.3 Termination of License. If you breach any of the provisions of these Terms, all licenses granted by the Company will terminate automatically. Additionally, the Company may suspend or disable your ability to access or use the Services (or any part of the foregoing) with or without notice, for any or no reason. All sections which by their nature should survive the termination of these Terms shall continue in full force and effect subsequent to and notwithstanding any termination of these Terms by the Company or you. Termination will not limit any of the Company’s other rights or remedies at law or in equity. 91 | 9.4 Modifications to the Services. The Company has the right, at any time and without liability, to modify, alter, update or eliminate the features, navigation, appearance, functionality and other elements of the Services, and any aspect, portion or feature thereof, including to modify the Third Party Software you are able to access via the Services. 92 | 9.5 Injunctive Relief. You agree that a breach of these Terms will cause irreparable injury to the Company for which monetary damages would not be an adequate remedy and the Company shall be entitled to equitable relief in addition to any remedies it may have hereunder or at law without a bond, other security or proof of damages. 93 | 9.6 California Residents. If you are a California resident, in accordance with Cal. Civ. Code § 1789.3, you may report complaints to the Complaint Assistance Unit of the Division of Consumer Services of the California Department of Consumer Affairs by contacting them in writing at 1625 North Market Blvd., Suite N 112 Sacramento, CA 95834, or by telephone at (800) 952-5210. 94 | 9.7 Export Laws. You agree that you will not export or re-export, directly or indirectly, the Services and/or other information or materials provided by the Company hereunder, to any country for which the United States or any other relevant jurisdiction requires any export license or other governmental approval at the time of export without first obtaining such license or approval. In particular, but without limitation, the Services may not be exported or re-exported (a) into any U.S. embargoed countries or any country that has been designated by the U.S. Government as a “terrorist supporting” country, or (b) to anyone listed on any U.S. Government list of prohibited or restricted parties, including the U.S. Treasury Department’s list of Specially Designated Nationals or the U.S. Department of Commerce Denied Person’s List or Entity List. By using the Services, you represent and warrant that you are not located in any such country or on any such list. You are responsible for and hereby agree to comply at your sole expense with all applicable United States export laws and regulations. 95 | 9.8 Miscellaneous. If any provision of these Terms shall be unlawful, void or for any reason unenforceable, then that provision shall be deemed severable from these Terms and shall not affect the validity and enforceability of any remaining provisions. These Terms and the licenses granted hereunder may be assigned by the Company but may not be assigned by you without the prior express written consent of the Company. No waiver by either party of any breach or default hereunder shall be deemed to be a waiver of any preceding or subsequent breach or default. The section headings used herein are for reference only and shall not be read to have any legal effect. The Services are operated by us in the United States. Those who choose to access the Services from locations outside the United States do so at their own initiative and are responsible for compliance with applicable local laws. These Terms are governed by the laws of the State of California, without regard to conflict of laws rules, and the proper venue for any disputes arising out of or relating to any of the same will be the arbitration venue set forth in Section 8, or if arbitration does not apply, then the state and federal courts located in [Santa Clara County, California]. 96 | 9.9 How to Contact Us. You may contact us regarding the Services or these Terms by e-mail at [insert email address]. 97 | 98 | --------------------------------------------------------------------------------