Sužinokite, kaip sukurti AI agentą mokslinių tyrimų dokumentų paieškai, paieškai ir apibendrinimui
Sužinokite, kaip sukurti AI agentą mokslinių tyrimų dokumentų paieškai, paieškai ir apibendrinimui
Mokslininkams atnaujinimas su naujausiais duomenimis yra panašus į adatų rinkinį.Įsivaizduokite AI varomą padėjėją, kuris ne tik surenka aktualiausius straipsnius, bet ir apibendrina pagrindines įžvalgas ir atsako į jūsų konkrečius klausimus, visa tai realiu laiku.
Šiame straipsnyje nagrinėjama, kaip sukurti tokį AI mokslinių tyrimų agentą, naudojant „Superlinked“ sudėtingas dokumentų įterpimo galimybes.Integruojant semantikos ir laiko aktualumą, mes pašaliname sudėtingos pertvarkos poreikį, užtikrinant veiksmingą ir tikslią informacijos paiešką.
Šiame straipsnyje nagrinėjama, kaip sukurti tokį AI mokslinių tyrimų agentą, naudojant „Superlinked“ sudėtingas dokumentų įterpimo galimybes.Integruojant semantikos ir laiko aktualumą, mes pašaliname sudėtingos pertvarkos poreikį, užtikrinant veiksmingą ir tikslią informacijos paiešką.TL;DR:
Sukurkite realaus laiko AI mokslinių tyrimų agentą, naudodami „Superlinked“ vektorių paiešką. „Superlinked“ praleidžia sudėtingus RAG vamzdynus tiesiogiai įterpiant ir užklausant dokumentus, todėl moksliniai tyrimai yra greitesni, paprastesni ir protingesni.
(Nori pereiti tiesiai prie kodo?Patikrinkite atviro kodo GitHub čia. Pasiruošę išbandyti semantinę paiešką savo agento naudojimo atveju?
Peržiūrėkite atviro kodo programą „GitHub“Šiame straipsnyje parodoma, kaip sukurti agentų sistemą, naudojant branduolio agentą užklausoms tvarkyti. Jei norite sekti ir paleisti kodą naršyklėje,here’s the
Kur pradėti kurti mokslinių tyrimų asistento sistemą?
Tradiciškai tokios sistemos kūrimas apima sudėtingumą ir dideles išteklių investicijas. Paieškos sistemos paprastai paima pradinį platų dokumentų rinkinį, pagrįstą aktualumu, o po to taiko antrinį perskirstymo procesą, kad patobulintų ir perskirstytų rezultatus. Nors perskirstymas padidina tikslumą, jis žymiai padidina skaičiavimo sudėtingumą, vėlavimą ir viršijimą dėl iš pradžių reikalaujamo didelio duomenų paieškos. „Superlinked“ sprendžia šį sudėtingumą derindamas struktūrizuotus skaitmeninius ir kategorinius įterpimus su semantiniais teksto įterpimais, suteikdamas išsamius multimodalius vektorius. Šis metodas žymiai pagerina paieškos tikslumą, išlaikydamas
Sukurkite agentinę sistemą su Superlinked
Šis AI agentas gali atlikti tris pagrindinius dalykus:
- Ieškoti straipsnių: Ieškoti mokslinių tyrimų straipsnių pagal temą (pvz., „kvantiniai skaičiavimai“) ir tada juos reitinguoti pagal aktualumą ir naujausią.
- Apibendrinti dokumentus: suspausti surinktus dokumentus į įgūdžių dydžio įžvalgas.
- Atsakymai į klausimus: Išimkite atsakymus tiesiogiai iš konkrečių mokslinių tyrimų dokumentų, pagrįstų tikslinėmis vartotojų užklausomis.
Superlinked pašalina poreikį iš naujo reitinguoti metodus, nes jis pagerina vektoriaus paieškos aktualumą. „Superlinked“ RecencySpace bus naudojamas, kuris specialiai koduoja laiko metaduomenis, pirmenybę teikia naujausiems dokumentams paieškos metu ir pašalina poreikį apskaičiuoti brangų reitinguojimą. Pavyzdžiui, jei du straipsniai turi tą patį aktualumą - tas, kuris yra naujausias, bus aukštesnis.
1 žingsnis: įrankių dėžutės nustatymas
%pip install superlinked
To make things easier and more modular, I created an Abstract Tool class. This will simplify the process of building and adding tools
import pandas as pd
import superlinked.framework as sl
from datetime import timedelta
from sentence_transformers import SentenceTransformer
from openai import OpenAI
import os
from abc import ABC, abstractmethod
from typing import Any, Optional, Dict
from tqdm import tqdm
from google.colab import userdata
# Abstract Tool Class
class Tool(ABC):
@abstractmethod
def name(self) -> str:
pass
@abstractmethod
def description(self) -> str:
pass
@abstractmethod
def use(self, *args, **kwargs) -> Any:
pass
# Get API key from Google Colab secrets
try:
api_key = userdata.get('OPENAI_API_KEY')
except KeyError:
raise ValueError("OPENAI_API_KEY not found in user secrets. Please add it using Tools > User secrets.")
# Initialize OpenAI Client
api_key = os.environ.get("OPENAI_API_KEY", "your-openai-key") # Replace with your OpenAI API key
if not api_key:
raise ValueError("Please set the OPENAI_API_KEY environment variable.")
client = OpenAI(api_key=api_key)
model = "gpt-4"
2 žingsnis: suprasti duomenų rinkinį
Šiame pavyzdyje naudojamas duomenų rinkinys, kuriame yra apie 10 000 AI mokslinių tyrimųKaukėNorėdami tai padaryti lengviau, tiesiog paleiskite žemiau esančią langelį, ir ji automatiškai atsisiųs duomenų rinkinį į savo darbo katalogą. Taip pat galite naudoti savo duomenų šaltinius, pvz., mokslinių tyrimų straipsnius ar kitą akademinį turinį.
import pandas as pd
!wget --no-check-certificate 'https://drive.google.com/uc?export=download&id=1FCR3TW5yLjGhEmm-Uclw0_5PWVEaLk1j' -O arxiv_ai_data.csv
Šiuo metu, norint, kad viskas vyktų šiek tiek greičiau, mes naudosime mažesnį popierių pogrupį, kad pagreitintume dalykus, bet nedvejodami išbandysime pavyzdį naudodami visą duomenų rinkinį. Svarbi techninė detalė čia yra tai, kad laiko žymės iš duomenų rinkinio bus konvertuojamos iš eilutės laiko žymės (pavyzdžiui, '1993-08-01 00:00:00+00:00') į pandos datetime objektus.
df = pd.read_csv('arxiv_ai_data.csv').head(100)
# Convert to datetime but keep it as datetime (more readable and usable)
df['published'] = pd.to_datetime(df['published'])
# Ensure summary is a string
df['summary'] = df['summary'].astype(str)
# Add 'text' column for similarity search
df['text'] = df['title'] + " " + df['summary']
Debug: Columns in original DataFrame: ['authors', 'categories', 'comment', 'doi', 'entry_id', 'journal_ref' 'pdf_url', 'primary_category', 'published', 'summary', 'title', 'updated']
Suprasti duomenų rinkinių stulpelius
Žemiau pateikiama trumpa mūsų duomenų rinkinio pagrindinių stulpelių apžvalga, kuri bus svarbi būsimuose etapuose:
- Paskelbta: mokslinių tyrimų dokumento paskelbimo data.
- santrauka: straipsnio santrauka, pateikianti glaustą apžvalgą.
- entry_id: unikalus identifikatorius kiekvienam arXiv popieriui.
Šioje demonstracijoje ypatingą dėmesį skiriame keturiems stulpeliams:entry_id
,published
,title
irsummary
. To optimize retrieval quality, the title and summary are combined into a single, comprehensive text column, which forms the core of our embedding and search process.
Pastaba apie „Superlinked“ atminties indeksą: „Superlinked“ atminties indeksavimas išsaugo mūsų duomenų rinkinį tiesiai į RAM, todėl paieška yra išskirtinai greita, o tai idealiai tinka realaus laiko paieškai ir greitam prototipų kūrimui.
3 žingsnis: Superlinked schema apibrėžimas
Norėdami judėti į priekį, reikia schemos, kad būtų galima žemėlapį mūsų duomenis.PaperSchema
Su pagrindiniais laukais:
lass PaperSchema(sl.Schema):
text: sl.String
published: sl.Timestamp # This will handle datetime objects properly
entry_id: sl.IdField
title: sl.String
summary: sl.String
paper = PaperSchema()
Apibrėžti superlinked erdves efektyviam atgavimui
Esminis žingsnis organizuojant ir veiksmingai užklausant mūsų duomenų rinkinį yra dviejų specializuotų vektorinių erdvių apibrėžimas: TextSimilaritySpace ir RecencySpace.
- Tekstilės erdvė
TųTextSimilaritySpace
yra skirta koduoti tekstinę informaciją, pvz., mokslinių tyrimų straipsnių pavadinimus ir abstrakcijas į vektorius. Konvertuojant tekstą į įterpimus, ši erdvė žymiai padidina semantinių paieškų paprastumą ir tikslumą.
text_space = sl.TextSimilaritySpace(
text=sl.chunk(paper.text, chunk_size=200, chunk_overlap=50),
model="sentence-transformers/all-mpnet-base-v2"
)
- Recenzijos
TųRecencySpace
užfiksuoja laiko metaduomenis, pabrėžiant mokslinių tyrimų leidinių nesenumą. Koduojant laiko žymes, ši erdvė suteikia didesnę reikšmę naujesniems dokumentams.
recency_space = sl.RecencySpace(
timestamp=paper.published,
period_time_list=[
sl.PeriodTime(timedelta(days=365)), # papers within 1 year
sl.PeriodTime(timedelta(days=2*365)), # papers within 2 years
sl.PeriodTime(timedelta(days=3*365)), # papers within 3 years
],
negative_filter=-0.25
)
Pagalvokite apie „RecencySpace“ kaip apie laiko filtrą, panašų į tai, kaip rūšiuoti savo el. Laiškus pagal datą arba pirmiausia peržiūrėti „Instagram“ įrašus su naujausiais.
- Mažesnės laiko dalys (pavyzdžiui, 365 dienos) leidžia pateikti daugiau granuliuotų, metinių laiko reitingų.
- Didesnės laiko dalelės (pavyzdžiui, 1095 dienos) sukuria platesnius laikotarpius.
Tųnegative_filter
Norėdami tai paaiškinti aiškiau, apsvarstykite toliau pateiktą pavyzdį, kai du straipsniai turi identišką turinio aktualumą, tačiau jų reitingas priklausys nuo jų paskelbimo datos.
Paper A: Published in 1996
Paper B: Published in 1993
Scoring example:
- Text similarity score: Both papers get 0.8
- Recency score:
- Paper A: Receives the full recency boost (1.0)
- Paper B: Gets penalized (-0.25 due to negative_filter)
Final combined scores:
- Paper A: Higher final rank
- Paper B: Lower final rank
Šios erdvės yra raktas į tai, kad duomenų rinkinys būtų prieinamesnis ir efektyvesnis.Jos leidžia tiek turinio, tiek laiko paiešką, ir tikrai padeda suprasti mokslinių tyrimų straipsnių aktualumą ir naujumą.Tai suteikia galingą būdą organizuoti ir ieškoti per duomenų rinkinį, atsižvelgiant tiek į turinį, tiek į paskelbimo laiką.
4 žingsnis: kurti indeksą
Toliau erdvės sujungiamos į indeksą, kuris yra paieškos variklio šerdis:
paper_index = sl.Index([text_space, recency_space])
Tada "DataFrame" yra susietas su schema ir įkeliamas į partijas (10 popierių vienu metu) į atminties saugyklą:
# Parser to map DataFrame columns to schema fields
parser = sl.DataFrameParser(
paper,
mapping={
paper.entry_id: "entry_id",
paper.published: "published",
paper.text: "text",
paper.title: "title",
paper.summary: "summary",
}
)
# Set up in-memory source and executor
source = sl.InMemorySource(paper, parser=parser)
executor = sl.InMemoryExecutor(sources=[source], indices=[paper_index])
app = executor.run()
# Load the DataFrame with a progress bar using batches
batch_size = 10
data_batches = [df[i:i + batch_size] for i in range(0, len(df), batch_size)]
for batch in tqdm(data_batches, total=len(data_batches), desc="Loading Data into Source"):
source.put([batch])
Atminties vykdytojas yra priežastis, kodėl Superlinked šviečia čia - 1000 popierių puikiai tinka RAM, o užklausos skrenda be disko I / O buteliukų.
5 žingsnis: užklausos kūrimas
Toliau yra užklausos kūrimas. Čia sukuriamas užklausų kūrimo šablonas. Norėdami tai valdyti, mums reikia užklausos šablono, kuris gali subalansuoti tiek aktualumą, tiek naujausią.
# Define the query
knowledgebase_query = (
sl.Query(
paper_index,
weights={
text_space: sl.Param("relevance_weight"),
recency_space: sl.Param("recency_weight"),
}
)
.find(paper)
.similar(text_space, sl.Param("search_query"))
.select(paper.entry_id, paper.published, paper.text, paper.title, paper.summary)
.limit(sl.Param("limit"))
)
Tai leidžia mums pasirinkti, ar teikti prioritetą turiniui (relevance_weight) ar neseniai (recency_weight) - labai naudingas derinys mūsų agento poreikiams.
6 žingsnis: įrankių kūrimas
Dabar ateina įrankio dalis.
Mes sukursime tris įrankius ...
- Retrieval Tool : Šis įrankis sukurtas prisijungus prie „Superlinked“ indekso, leidžiant jam ištraukti 5 geriausius dokumentus, pagrįstus užklausa. Jis subalansuoja aktualumą (1.0 svoris) ir naujausią (0,5 svoris) norint pasiekti „rasti dokumentus“ tikslą. Ką mes norime, tai rasti dokumentus, kurie yra svarbūs užklausai. Taigi, jei užklausa yra: „Kokius kvantinių kompiuterių dokumentus buvo paskelbta tarp 1993 ir 1994 metų?“, tada paieškos įrankis surinks tuos dokumentus, apibendrins juos po vieną ir grąžins rezultatus.
class RetrievalTool(Tool):
def __init__(self, df, app, knowledgebase_query, client, model):
self.df = df
self.app = app
self.knowledgebase_query = knowledgebase_query
self.client = client
self.model = model
def name(self) -> str:
return "RetrievalTool"
def description(self) -> str:
return "Retrieves a list of relevant papers based on a query using Superlinked."
def use(self, query: str) -> pd.DataFrame:
result = self.app.query(
self.knowledgebase_query,
relevance_weight=1.0,
recency_weight=0.5,
search_query=query,
limit=5
)
df_result = sl.PandasConverter.to_pandas(result)
# Ensure summary is a string
if 'summary' in df_result.columns:
df_result['summary'] = df_result['summary'].astype(str)
else:
print("Warning: 'summary' column not found in retrieved DataFrame.")
return df_result
Kitas aukščiau yraSummarization Tool
Šis įrankis skirtas tais atvejais, kai reikalinga glausta popieriaus santrauka.paper_id
, kuris yra popieriaus ID, kurį reikia apibendrinti.paper_id
nėra pateikta, įrankis neveiks, nes šie ID yra reikalavimas norint rasti atitinkamus dokumentus duomenų rinkinyje.
class SummarizationTool(Tool):
def __init__(self, df, client, model):
self.df = df
self.client = client
self.model = model
def name(self) -> str:
return "SummarizationTool"
def description(self) -> str:
return "Generates a concise summary of specified papers using an LLM."
def use(self, query: str, paper_ids: list) -> str:
papers = self.df[self.df['entry_id'].isin(paper_ids)]
if papers.empty:
return "No papers found with the given IDs."
summaries = papers['summary'].tolist()
summary_str = "\n\n".join(summaries)
prompt = f"""
Summarize the following paper summaries:\n\n{summary_str}\n\nProvide a concise summary.
"""
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=500
)
return response.choices[0].message.content.strip()
Galiausiai mes turimeQuestionAnsweringTool
Šis įrankis grandinėRetrievalTool
gauti atitinkamus dokumentus ir tada juos naudoti atsakyti į klausimus.Jei nėra atitinkamų dokumentų rasti atsakyti į klausimus, ji pateiks atsakymą, pagrįstą bendromis žiniomis
class QuestionAnsweringTool(Tool):
def __init__(self, retrieval_tool, client, model):
self.retrieval_tool = retrieval_tool
self.client = client
self.model = model
def name(self) -> str:
return "QuestionAnsweringTool"
def description(self) -> str:
return "Answers questions about research topics using retrieved paper summaries or general knowledge if no specific context is available."
def use(self, query: str) -> str:
df_result = self.retrieval_tool.use(query)
if 'summary' not in df_result.columns:
# Tag as a general question if summary is missing
prompt = f"""
You are a knowledgeable research assistant. This is a general question tagged as [GENERAL]. Answer based on your broad knowledge, not limited to specific paper summaries. If you don't know the answer, provide a brief explanation of why.
User's question: {query}
"""
else:
# Use paper summaries for specific context
contexts = df_result['summary'].tolist()
context_str = "\n\n".join(contexts)
prompt = f"""
You are a research assistant. Use the following paper summaries to answer the user's question. If you don't know the answer based on the summaries, say 'I don't know.'
Paper summaries:
{context_str}
User's question: {query}
"""
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=500
)
return response.choices[0].message.content.strip()
7 žingsnis: Kernelio agento kūrimas
Toliau yra branduolinis agentas. Jis veikia kaip centrinis valdiklis, užtikrinantis sklandų ir efektyvų veikimą. Veikdamas kaip pagrindinis sistemos komponentas, branduolinis agentas koordinuoja komunikaciją, nukreipdamas užklausas pagal jų tikslą, kai vienu metu veikia keli agentai. Vieno agento sistemose, tokiose kaip ši, branduolinis agentas tiesiogiai naudoja atitinkamus įrankius veiksmingam užduočių valdymui.
class KernelAgent:
def __init__(self, retrieval_tool: RetrievalTool, summarization_tool: SummarizationTool, question_answering_tool: QuestionAnsweringTool, client, model):
self.retrieval_tool = retrieval_tool
self.summarization_tool = summarization_tool
self.question_answering_tool = question_answering_tool
self.client = client
self.model = model
def classify_query(self, query: str) -> str:
prompt = f"""
Classify the following user prompt into one of the three categories:
- retrieval: The user wants to find a list of papers based on some criteria (e.g., 'Find papers on AI ethics from 2020').
- summarization: The user wants to summarize a list of papers (e.g., 'Summarize papers with entry_id 123, 456, 789').
- question_answering: The user wants to ask a question about research topics and get an answer (e.g., 'What is the latest development in AI ethics?').
User prompt: {query}
Respond with only the category name (retrieval, summarization, question_answering).
If unsure, respond with 'unknown'.
"""
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=10
)
classification = response.choices[0].message.content.strip().lower()
print(f"Query type: {classification}")
return classification
def process_query(self, query: str, params: Optional[Dict] = None) -> str:
query_type = self.classify_query(query)
if query_type == 'retrieval':
df_result = self.retrieval_tool.use(query)
response = "Here are the top papers:\n"
for i, row in df_result.iterrows():
# Ensure summary is a string and handle empty cases
summary = str(row['summary']) if pd.notna(row['summary']) else ""
response += f"{i+1}. {row['title']} \nSummary: {summary[:200]}...\n\n"
return response
elif query_type == 'summarization':
if not params or 'paper_ids' not in params:
return "Error: Summarization query requires a 'paper_ids' parameter with a list of entry_ids."
return self.summarization_tool.use(query, params['paper_ids'])
elif query_type == 'question_answering':
return self.question_answering_tool.use(query)
else:
return "Error: Unable to classify query as 'retrieval', 'summarization', or 'question_answering'."
Šiame etape visi tyrimų agento sistemos komponentai yra sukonfigūruoti.Sistema dabar gali būti inicijuojama, suteikiant branduoliniam agentui atitinkamus įrankius, po kurių tyrimų agento sistema bus visiškai veikianti.
retrieval_tool = RetrievalTool(df, app, knowledgebase_query, client, model)
summarization_tool = SummarizationTool(df, client, model)
question_answering_tool = QuestionAnsweringTool(retrieval_tool, client, model)
# Initialize KernelAgent
kernel_agent = KernelAgent(retrieval_tool, summarization_tool, question_answering_tool, client, model)
Dabar išbandykime sistemą.
# Test query print(kernel_agent.process_query("Find papers on quantum computing in last 10 years"))
Vykdant šį procesą aktyvuojamasRetrievalTool
Jis paims atitinkamus dokumentus, atsižvelgdamas tiek į aktualumą, tiek į nesenumą, ir grąžins atitinkamus stulpelius.Jei grąžintame rezultate yra santraukos stulpelis (kuris rodo, kad dokumentai buvo paimti iš duomenų rinkinio), jis panaudos šias santraukas ir grąžins juos mums.
Query type: retrieval
Here are the top papers:
1. Quantum Computing and Phase Transitions in Combinatorial Search
Summary: We introduce an algorithm for combinatorial search on quantum computers that
is capable of significantly concentrating amplitude into solutions for some NP
search problems, on average. This is done by...
1. The Road to Quantum Artificial Intelligence
Summary: This paper overviews the basic principles and recent advances in the emerging
field of Quantum Computation (QC), highlighting its potential application to
Artificial Intelligence (AI). The paper provi...
1. Solving Highly Constrained Search Problems with Quantum Computers
Summary: A previously developed quantum search algorithm for solving 1-SAT problems in
a single step is generalized to apply to a range of highly constrained k-SAT
problems. We identify a bound on the number o...
1. The model of quantum evolution
Summary: This paper has been withdrawn by the author due to extremely unscientific
errors....
1. Artificial and Biological Intelligence
Summary: This article considers evidence from physical and biological sciences to show
machines are deficient compared to biological systems at incorporating
intelligence. Machines fall short on two counts: fi...
Pabandykime dar vieną užklausą, šį kartą padarykime apibendrinimą.
print(kernel_agent.process_query("Summarize this paper", params={"paper_ids": ["http://arxiv.org/abs/cs/9311101v1"]}))
Query type: summarization
This paper discusses the challenges of learning logic programs that contain the cut predicate (!). Traditional learning methods cannot handle clauses with cut because it has a procedural meaning. The proposed approach is to first generate a candidate base program that covers positive examples, and then make it consistent by inserting cut where needed. Learning programs with cut is difficult due to the need for intensional evaluation, and current induction techniques may need to be limited to purely declarative logic languages.
Tikiuosi, kad šis pavyzdys buvo naudingas kuriant AI agentus ir agentų pagrįstas sistemas.Daugelis čia demonstruojamų paieškos funkcijų tapo įmanoma Superlinked, todėl apsvarstykite galimybę žaistiRepozitorijosuž būsimą nuorodą, kai jūsų AI agentams reikalingos tikslios paieškos galimybės!
Ėmė
- Semantikos ir laiko aktualumo derinimas pašalina sudėtingą perkėlimą, išlaikant mokslinių tyrimų dokumentų paieškos tikslumą.
- Laiku pagrįstos sankcijos (negative_filter=-0.25) teikia pirmenybę naujausiems tyrimams, kai straipsniai turi panašų turinio aktualumą.
- Modulinė įrankiais pagrįsta architektūra leidžia specializuotiems komponentams tvarkyti atskiras užduotis (atkūrimas, apibendrinimas, klausimų atsakymas), išlaikant sistemos sanglaudą.
- Duomenų įkėlimas mažose partijose (batch_size=10) su pažangos stebėjimu pagerina sistemos stabilumą apdorojant didelius mokslinių tyrimų duomenų rinkinius.
- Reguliuojami užklausos svoriai leidžia vartotojams subalansuoti aktualumą (1.0) ir naujausią (0,5) pagal konkrečius mokslinių tyrimų poreikius.
- Klausimų atsakymo komponentas gražiai degraduoja į bendrąsias žinias, kai popieriaus specifinis kontekstas nėra prieinamas, užkertant kelią galutinei vartotojo patirčiai.
Atnaujinimas su daugybe reguliariai skelbiamų mokslinių tyrimų straipsnių gali būti sudėtingas ir daug laiko reikalaujantis.Agentinis AI asistento darbo eiga, galinti efektyviai rasti atitinkamus tyrimus, apibendrinti pagrindines įžvalgas ir atsakyti į konkrečius klausimus iš šių straipsnių, galėtų žymiai supaprastinti šį procesą.
Prisidėjėjai
- Vipul Maheshwari, autorius
- Filip Makraduli, apžvalgininkas