# Project export: hamilton

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: Empowering Civic Engagement --- Transform Bills with a Simulated Multi-Agent Senate
- Devpost: https://devpost.com/software/hamilton-h09ikr
- GitHub: https://github.com/tejasprabhune/hamilton
- Video: https://player.vimeo.com/video/1021472373?byline=0&portrait=0&title=0#t=
- Team: 3 GitHub contributor(s) — AdityaGhai18 (18 commits), Tejas Prabhune (15 commits), Ayushi Batwara (13 commits)

## Devpost submission (written by the team)

### Overview

💡 inspiration With the elections just around the corner, it is imperative for the average voter to be educated on the public policies and bills that our politicians put forth. Poor literacy in American politics is a well-documented issue, with many citizens lacking a fundamental understanding of government structures, key political concepts, and how policies affect their lives. This lack of civic knowledge contributes to a disengaged electorate, misinformed opinions, and lower participation rates in democratic processes such as voting or local decision-making. As students seeking new, easy, creative ways to engage with our politicians, we built hamilton. ⚙️ what it does hamilton is a multi-agent AI platform that simulates the dynamics of an actual senate committee hearing to lower the barrier to entry to the political sphere in an approachable manner. With real-time audio conversations between our senator agents, you can follow along with what bills and clauses senators are actively discussing and visualize the active changes they are making as they debate the outcomes and impacts of these bills. It enables you to learn about the process to go from a bill proposal to the final majority-approved outcome. ⛏️ how we built it: our process Given the inherent complexity of our project, we spent a significant amount of our early time building a rock solid system infrastructure plan before touching any code. We narrowed down the sponsors that would best enable us to fulfill our vision, and laid out the data access objects, the communication channels between processes, and our tech stack. Below is a higher-level overview of our system design: We then split up our project into sections that could be tested individually to then eventually integrate into the full system. In particular, we broke up the frontend UI and event handling, the REST and WebSocket-based backends for delivering bill outputs to the frontend, and an independent multi-agent system based on AutoGen. The latter required significant data collection for personalized Senator agents through various media sources. To parse and retrieve this data during runtime, we were able to set up a ChromaDB database for vectorized search quickly. This allowed us to provide the correct personality and historical context for each senator agent in relation to the user-submitted bill. For the Next.js frontend, we really loved using Cartesia’s fast and easy API for speech generation. Just using brief voice recordings of each senator, we were able to create expressive, emotional voice clones that we had a blast experimenting with. We envision adding more senators to our platform, which is easily possible with Cartesia’s voice cloning capabilities. To effectively track the evolving bills within Hamilton, we utilize Git version control. This approach enables us to maintain a comprehensive record of all changes, ensuring that each senator’s contributions are clearly documented. The version control system enhances transparency and provides deeper insights into this political process. This way, we can easily review the modifications made during discussions, allowing for a clearer understanding of the dynamics at play in the simulated senate committee hearings. 🛑 challenges we ran into System Design: dealing with multiple asynchronous agents that we have to manage the communication between each other, the backend and the user interface. This was a very difficult task given both shared memory and concurrent communication we had to handle. What helped was breaking it down into manageable and well defined functions, piecing together the broader system design from the ground up. 🏆 accomplishments that we're proud of Conversational Multi-Agent Environment: Leveraging in-context learning with Autogen, we created a dynamic environment where multiple AI agents embody the unique views, behaviors, and mannerisms of real Senate members, bringing political debates to life. Voice Simulation: With Cartesia’s cutting-edge, real-time inference technology, each senator is given a distinct voice, modeled after real-life voice data. This adds a new dimension of realism, enhancing user engagement and immersion. Contextual Intelligence with Vector RAG: Using ChromaDB for advanced context retrieval, we ensured that each senator’s responses were hyper-contextualized to the bill at hand, delivering a more authentic and nuanced political dialogue. Interactive and Gamified Frontend: Our intuitive, game-like interface replicates the Senate hearing process, making civic engagement accessible, engaging, and educational for the public. code snippet 🏫 what we learned We learned a lot about web socket communication between the front end and back end. In addition, we learned about multi-agent frameworks and how to emulate personas to facilitate meaningful discussions about educational and political topics. ⏩ whats next for hamilton! We envision hamilton as the next large platform for educational and political content. Because of the unique crossroads we find ourselves at —- with ML models able to engage in meaningful conversations—hamilton stands poised to revolutionize civic engagement and political literacy. Our platform will serve as an interactive space where users can immerse themselves in the legislative process without the intimidation often associated with politics. By simulating the dynamics of an actual Senate committee hearing, hamilton allows users to witness and participate in the decision-making process.

## README (from the GitHub repository)

# hamilton

1. Create a Python virtual environment `python3 -m venv venv`
2. Run `pip install -r requirements.txt`
3. Run the backend by cd-ing into the backend directory and run `python3 server.py`

Before you push your code, just run `pip freeze > requirements.txt`


## Detected evidence (automated analysis)

Indexed codebase: 22 recognized source files, 184 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (57 of 57)

```
.DS_Store
.gitignore
backend/.cache/41/cache.db
backend/.cache/41/cache.db-shm
backend/.cache/41/cache.db-wal
backend/.cache/42/cache.db
backend/.cache/42/cache.db-shm
backend/.cache/42/cache.db-wal
backend/agriculture_bill.txt
backend/autogen_groq_test.py
backend/autogen_groupchat_test.py
backend/old_bill.txt
backend/senator_debate.py
backend/senator.py
backend/server.py
backend/simulation.py
frontend/.eslintrc.json
frontend/.gitignore
frontend/next.config.mjs
frontend/package.json
frontend/postcss.config.mjs
frontend/README.md
frontend/src/app/bill.css
frontend/src/app/bill.tsx
frontend/src/app/globals.css
frontend/src/app/layout.tsx
frontend/src/app/page.tsx
frontend/src/components/CartesiaTTS.tsx
frontend/src/components/SenateRoom.css
frontend/src/components/SenateRoom.tsx
frontend/src/components/Senator.css
frontend/src/components/Senator.tsx
frontend/src/components/TestCartesia.tsx
frontend/tailwind.config.ts
frontend/tsconfig.json
README.md
requirements.txt
senator_data/.DS_Store
senator_data/tweet_data/booker.txt
senator_data/tweet_data/boozman.txt
senator_data/tweet_data/braun.txt
senator_data/tweet_data/klobuchar.txt
senator_data/tweet_data/mcconnell.txt
senator_data/tweet_data/stabenow.txt
senator_data/voting_data/booker.json
senator_data/voting_data/boozman.json
senator_data/voting_data/braun.json
senator_data/voting_data/klobuchar.json
senator_data/voting_data/mcconnell.json
senator_data/voting_data/stabenow.json
senator_data/website_data/booker.txt
senator_data/website_data/boozman.txt
senator_data/website_data/braun.txt
senator_data/website_data/klobuchar.txt
senator_data/website_data/mcconnell.txt
senator_data/website_data/stabenow.txt
test.py
```

### Dependencies

- frontend/package.json: @cartesia/cartesia-js@^1.1.0, @types/node@^20, @types/react@^18, @types/react-dom@^18, buffer@^6.0.3, difflib@^0.2.4, dotenv@^16.4.5, eslint@^8, eslint-config-next@14.2.15, fast-base64@^0.1.8, next@14.2.15, postcss@^8, react@^18, react-dom@^18, tailwindcss@^3.4.1, typescript@^5
- requirements.txt: aiohappyeyeballs@==2.4.3, aiohttp@==3.10.10, aiosignal@==1.3.1, annotated-types@==0.7.0, apispec@==6.6.1, attrs@==24.2.0, bech32@==1.2.0, black@==24.10.0, certifi@==2024.8.30, charset-normalizer@==3.4.0, click@==8.1.7, cosmpy@==0.9.2, ecdsa@==0.19.0, frozenlist@==1.4.1, googleapis-common-protos@==1.65.0, grpcio@==1.67.0, h11@==0.14.0, idna@==3.10, jsonschema@==4.23.0, jsonschema-specifications@==2024.10.1, msgpack@==1.1.0, multidict@==6.1.0, mypy-extensions@==1.0.0, packaging@==24.1, pathspec@==0.12.1, platformdirs@==4.3.6, propcache@==0.2.0, protobuf@==4.25.5, pycryptodome@==3.21.0, pydantic@==2.8.2, pydantic_core@==2.20.1, python-dateutil@==2.9.0.post0, referencing@==0.35.1, requests@==2.32.3, rpds-py@==0.20.0, six@==1.16.0, typing_extensions@==4.12.2, uagents@==0.16.2, urllib3@==2.2.3, uvicorn@==0.30.6, websockets@==10.4, yarl@==1.15.5

### Recent commits (newest first)

- wip
- senate members added
- Working speech version with one iteration of asyncio
- Working backend version of async multi-agent
- Add class version of agents
- Add simulation
- more wip
- created class
- Rename kobuchar.txt to klobuchar.txt
- Add files via upload
- Add files via upload
- Rename BraunT.txt to braun.txt
- Add files via upload
- Rename braunw.txt to braun.txt
- Add files via upload
- Add files via upload
- Update booker.txt
- wip
- Rename bookerW.txt to booker.txt
- Add files via upload

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
aiohappyeyeballs==2.4.3
aiohttp==3.10.10
aiosignal==1.3.1
annotated-types==0.7.0
apispec==6.6.1
attrs==24.2.0
bech32==1.2.0
black==24.10.0
certifi==2024.8.30
charset-normalizer==3.4.0
click==8.1.7
cosmpy==0.9.2
ecdsa==0.19.0
frozenlist==1.4.1
googleapis-common-protos==1.65.0
grpcio==1.67.0
h11==0.14.0
idna==3.10
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
msgpack==1.1.0
multidict==6.1.0
mypy-extensions==1.0.0
packaging==24.1
pathspec==0.12.1
platformdirs==4.3.6
propcache==0.2.0
protobuf==4.25.5
pycryptodome==3.21.0
pydantic==2.8.2
pydantic_core==2.20.1
python-dateutil==2.9.0.post0
referencing==0.35.1
requests==2.32.3
rpds-py==0.20.0
six==1.16.0
typing_extensions==4.12.2
uagents==0.16.2
urllib3==2.2.3
uvicorn==0.30.6
websockets==10.4
yarl==1.15.5

```

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@cartesia/cartesia-js": "^1.1.0",
    "buffer": "^6.0.3",
    "difflib": "^0.2.4",
    "dotenv": "^16.4.5",
    "fast-base64": "^0.1.8",
    "next": "14.2.15",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.15",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### backend/server.py

```python
from flask import Flask
from flask_cors import CORS
from simulation import create_websocket
from simulation import open_bill
import asyncio

app = Flask(__name__)
CORS(app)

# asyncio.run(create_websocket())




@app.route('/')
def home():
    return "Hello, Flask!"

@app.route('/start_sim', methods=['GET'])
def start_sim():
    """
    Starts the simulation. Processes a bill into a list of clauses and returns them.
    """
    clauses = {"clauses": open_bill()[0]}
    return clauses

@app.route('/initial-agreement-level')
def initial_agreement_level():
    """
    Input: takes in a clause in plaintext
    Output: ids of the min and max agreement senators
    """
    pass

@app.route('/debate')
def debate():
    """
    Input: senator id, current clause
    Output: updated clause 
    """
    pass

@app.route('/vote')
def vote():
    """
    Input: updated clause
    Output: whether or not majority have agreed 
    """

# Run the application
if __name__ == '__main__':
    app.run(debug=True, port=8080)

```

### frontend/src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";

const geistSans = localFont({
  src: "./fonts/GeistVF.woff",
  variable: "--font-geist-sans",
  weight: "100 900",
});
const geistMono = localFont({
  src: "./fonts/GeistMonoVF.woff",
  variable: "--font-geist-mono",
  weight: "100 900",
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <link rel="stylesheet" href="https://use.typekit.net/ugi7azv.css" />
      </head>
      <body
        style={{ maxWidth: "100%" }}
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

```

### frontend/src/app/page.tsx

```typescript
"use client";
import { useTTS } from "@cartesia/cartesia-js/react";
import SenateRoom from "@/components/SenateRoom";
import { useEffect, useState } from "react";

import { Clauses, generateDiffHtml, StartSim } from "./bill";

let clauseIdToQueue = new Array<Array<any>>();

function StartWebSocket(setClauses: (clauses: string[]) => void) {
	const ws = new WebSocket("ws://localhost:8765");

	ws.onopen = () => {
		console.log("connected");
	};

	ws.onmessage = (message) => {
		console.log(message);
		const payload = JSON.parse(message.data);

		const event = payload["event"];

		const clauseId = payload["clause_id"];
		const dialogue = payload["dialogue"];
		const speaker = payload["senator"];
		const clause = payload["clause"];

		if (clauseIdToQueue[clauseId] === undefined) {
			clauseIdToQueue[clauseId] = [];
		}

		const htmlDiff = generateDiffHtml(
			clauseIdToQueue[clauseId][clauseIdToQueue[clauseId].length - 1],
			clause
		);

		console.log("HTML DIFF ===========================");
		console.log(htmlDiff);
		let current_clauses = new Array<string>();
		for (let i = 0; i < current_clauses.length; i++) {
			current_clauses.push(clauseIdToQueue[i][clauseIdToQueue[i].length - 1]);
		}
		setClauses(current_clauses);
		clauseIdToQueue[clauseId].push(payload);
	};

	ws.onclose = () => {
		console.log("disconnected");
	};

	return ws;
}

export default function Home() {
	const [clauses, setClauses] = useState<string[]>([]);
	const [activeClause, setActiveClause] = useState(-1);
	const [speaker, setActiveSpeaker] = useState("Boozman");
	const [dialogue, setActiveDialogue] = useState("Hi there");
	const [hasStarted, setHasStarted] = useState(false);

	useEffect(() => {
		const ws = StartWebSocket(setClauses);
	}, []);

	return (
		<div style={{ display: "flex", maxWidth: "100%" }}>
			<div style={{ width: "70%" }}>
				<SenateRoom activeSpeaker={speaker} />
			</div>
			<div
				style={{
					backgroundColor: "#384F8B",
					minHeight: "100vh",
					height: "100%",
					width: "30%",
				}}
			>
				{hasStarted ? (
					<Clauses
						clauses={clauses}
						setActiveClause={setActiveClause}
						speaker={speaker}
						dialogue={dialogue}
						clauseIdToQueue={clauseIdToQueue}
						setClauses={setClauses}
					/>
				) : (
					<StartSim
						setHasStarted={setHasStarted}
						setClauses={setClauses}
						clauseIdToQueue={clauseIdToQueue}
					/>
				)}
			</div>
		</div>
	);
}

```

### test.py

```python
from openai import OpenAI
client = OpenAI()

completion = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {
            "role": "user",
            "content": "Write a haiku about recursion in programming."
        }
    ]
)

print(completion.choices[0].message)
```

### frontend/tailwind.config.ts

```typescript
import type { Config } from "tailwindcss";

const config: Config = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      colors: {
        background: "var(--background)",
        foreground: "var(--foreground)",
      },
    },
  },
  plugins: [],
};
export default config;

```

### backend/autogen_groupchat_test.py

```python
import os
os.environ["AUTOGEN_USE_DOCKER"] = "0"
import autogen
from autogen import AssistantAgent, UserProxyAgent, Agent, GroupChat
from autogen.coding import LocalCommandLineCodeExecutor
from pathlib import Path
from typing import Union, Literal

# Put your api key in the environment variable 
config_list = [{
        # Let's choose the llama 3.1 70b model
        "model": "llama3-70b-8192",
        # Put your Groq API key here or put it into the GROQ_API_KEY environment variable.
        "api_key": "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",
        # We specify the API Type as 'groq' so it uses the Groq client class
        "api_type": "groq",
    }]

llm_config = {
    "config_list": config_list,
    "cache_seed": 42  # Optional: for reproducibility
}


def custom_speaker_selection_func(
    last_speaker: Agent, 
    groupchat: GroupChat
) -> Union[Agent, Literal['auto', 'manual', 'random' 'round_robin'], None]:

    """Define a customized speaker selection function.
    A recommended way is to define a transition for each speaker in the groupchat.

    Parameters:
        - last_speaker: Agent
            The last speaker in the group chat.
        - groupchat: GroupChat
            The GroupChat object
    Return:
        Return one of the following:
        1. an `Agent` class, it must be one of the agents in the group chat.
        2. a string from ['auto', 'manual', 'random', 'round_robin'] to select a default method to use.
        3. None, which indicates the chat should be terminated.

    """
    return 'auto'


initializer = autogen.UserProxyAgent(
    name="Init",
)

boozman = autogen.AssistantAgent(
    name="boozman",
    llm_config=llm_config,
    system_message="""you are boozman, a stupid 10 year old school student, talk about how you went to school today""",
)
# executor = autogen.UserProxyAgent(
#     name="Retrieve_Action_2",
#     system_message="Executor. Execute the code written by the boozman and report the result.",
#     human_input_mode="NEVER",
#     code_execution_config={
#         "last_n_messages": 3,
#         "work_dir": "paper",
#         "use_docker": False,
#     },  # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.
# )
stabenow = autogen.AssistantAgent(
    name="stabenow",
    llm_config=llm_config,
    system_message="""You are the stabenow. Please describe your day as a software engineer and respond to how boozmans day went, go through specifics of what boozman did today""",
)

def state_transition(last_speaker, groupchat):
    if last_speaker is initializer:
        return boozman
    elif last_speaker is boozman:
        return stabenow
    elif last_speaker is stabenow:
        return None
    else:
        return 'auto'

groupchat = autogen.GroupChat(
    agents=[initializer, boozman, stabenow],
    messages=[],
    max_round=20,
    speaker_selection_method=state_transition,
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)
initializer.initiate_chat(
    manager, message="discuss how your days went today"
)
```

### backend/autogen_groq_test.py

```python
import os
import autogen
from autogen import AssistantAgent, UserProxyAgent
from autogen.coding import LocalCommandLineCodeExecutor
from pathlib import Path


config_list = [
    {
        # Let's choose the llama 3.1 70b model
        "model": "llama3-70b-8192",
        # Put your Groq API key here or put it into the GROQ_API_KEY environment variable.
        "api_key": "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",
        # We specify the API Type as 'groq' so it uses the Groq client class
        "api_type": "groq",
    }
]


# groq_config = {
#         "model": "llama3-70b-8192",
#         "api_key": "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",
#         "api_type": "groq",
#         "frequency_penalty": 0.5,
#         "max_tokens": 2048,
#         "presence_penalty": 0.2,
#         "seed": 42,
#         "temperature": 0.5,
#         "top_p": 0.2
#     }


# Setting up the code executor
workdir = Path("coding")
workdir.mkdir(exist_ok=True)
code_executor = LocalCommandLineCodeExecutor(work_dir=workdir)

# Setting up the agents

# The UserProxyAgent will execute the code that the AssistantAgent provides
user_proxy_agent = UserProxyAgent(
    name="User",
    code_execution_config={"executor": code_executor},
    is_termination_msg=lambda msg: "FINISH" in msg.get("content"),
)

system_message = """You are a helpful AI assistant who writes code and the user executes it.
Solve tasks using your coding and language skills.
In the following cases, suggest python code (in a python coding block) for the user to execute.
Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill.
When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user.
Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user.
If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try.
When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible.
IMPORTANT: Wait for the user to execute your code and then you can reply with the word "FINISH". DO NOT OUTPUT "FINISH" after your code block."""

# The AssistantAgent, using Groq's model, will take the coding request and return code
assistant_agent = AssistantAgent(
    name="Groq Assistant",
    system_message=system_message,
    llm_config={"config_list": config_list},
)

# Start the chat, with the UserProxyAgent asking the AssistantAgent the message
chat_result = user_proxy_agent.initiate_chat(
    assistant_agent,
    message="Provide code to count the number of prime numbers from 1 to 10000.",
)

```

### backend/senator.py

```python
from pathlib import Path
import json

from chromadb.utils.embedding_functions.onnx_mini_lm_l6_v2 import ONNXMiniLM_L6_V2

import chromadb


class Senator:
    def __init__(self, id, name, init_db=True):
        self.ef = ONNXMiniLM_L6_V2(preferred_providers=["CPUExecutionProvider"])

        self.id = id
        self.name = name

        self.data_root = Path("../senator_data/")

        self.data_labels = ["tweet", "website", "voting"]
        self.data = [[], [], []]

        for i, label in enumerate(self.data_labels[:2]):
            with open(self.data_root / f"{label}_data" / f"{self.name}.txt", "r") as f:
                for line in f:
                    if line != "\n":
                        self.data[i].append(line)

        with open(self.data_root / "voting_data" / f"{self.name}.json", "r") as f:
            self.data[2] = json.load(f)

        self.tweets, self.websites, self.votes = self.data

        if init_db:
            self.create_db()

    def __str__(self):
        return f"Senator {self.name} (ID: {self.id})"

    def create_db(self, skip_existing=True):
        if skip_existing and Path(f"db/{self.name}").exists():
            client = chromadb.PersistentClient(path=f"db/{self.name}")
            self.collections = [
                client.get_collection(label) for label in self.data_labels
            ]
            self.tweet_collection, self.website_collection, self.vote_collection = (
                self.collections
            )

            print(f"Database for {self.name} already exists. Skipping creation.")
            return

        client = chromadb.PersistentClient(path=f"db/{self.name}")

        for i, label in enumerate(self.data_labels[:2]):
            print(f"Creating collection for {label} data...")
            collection = client.get_or_create_collection(
                label, embedding_function=self.ef
            )
            collection.add(
                documents=self.data[i], ids=[str(i) for i in range(len(self.data[i]))]
            )

            self.collections.append(collection)

        print("Creating collection for voting data...")
        voting_collection = client.get_or_create_collection(
            "voting", embedding_function=self.ef
        )
        voting_collection.add(
            documents=list(self.votes.keys()),
            metadatas=[{"key": vote} for vote in list(self.votes.values())],
            ids=[str(i) for i in range(len(self.votes))],
        )
        self.collections.append(voting_collection)

        self.tweet_collection, self.website_collection, self.vote_collection = (
            self.collections
        )

        print(f"Database for {self.name} created.")

    def query(self, query_texts, n_results=10, label="tweet"):
        results = []

        print(f"Querying {label} data...")
        i = self.data_labels.index(label)
        results.extend(
            self.collections[i].query(query_texts=query_texts, n_results=n_results)[
                "documents"
            ]
        )

        return results


if __name__ == "__main__":

    root = Path("../senator_data/tweet_data/")

    senator_names = [path.stem for path in root.iterdir()]
    print(senator_names)

    senators = []

    for i, senator_name in enumerate(senator_names):
        senator = Senator(i, senator_name)
        senators.append(senator)
        senator.create_db()

    print('Testing query "Israel":')
    print(senators[2].query(["Israel"], n_results=3))

```

[11 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]