Legal Research API

Legal Research API

There is only one endpoint for the legal research api. The goal of this endpoint is to given a legal question and some optional document attached, return a draft answer and all the documents relevant to answer that question.

It points to an EventSource (opens in a new tab) that will stream multiple json objects. We use the event approach because we compute part of the answer at different times, instead of waiting for everything to be ready this allow to show intermediate results as they come.

Connect to the Endpoint

The endpoint url is: https://api.omnilex.ai/advice/app/combined

To connect to the EventStream, you need to send a POST request to the endpoint and listen to the events of the EventSource, each event has a type ( response, considerationDetails, confidence, error, legalArea ), the last event is of type result.

There are 2 parameters to add in the body of the POST request:

ParameterTypeRequiredDescription
querystringYesThe legal question as a string.
documents{name:string, content:string}[]NoThe files attached to the question.
countrystringYesThe country abbreviation in capital letter, ex: CH

The country field will for the search to be done in a specific jurisdiction. ( Currently only CH and DE is supported,FR,US and BR are in beta state )

Here is an example of an implementation in typescript (in this example we use the sse.js (opens in a new tab) package to force the POST request to create the EventSource):

import { SSE } from "sse.js";
 
const endpoint_url = "https://api.omnilex.ai/advice/app/combined";
const email = "test@test.ch"
 
interface FileContent {
  name: string;
  content: string;
}
 
interface Interaction {
  // General
  id: string; // not used but required ( can be any string )
  date: Date; // not used but required ( can be any Date )
 
  // Input
  question: string;
  documents?: FileContent[];
  reply: string;
  sources: ConsiderationDetails[]
}
 
interface Thread {
  id: string; // if not defined a new thread is created and saved
  country: "CH",
  date: Date;
  ownerUsername: email;
  interactions: Interaction[];
}
 
interface OmnilexResultThread {
  id?: string;
  date: Date;
  ownerUsername: string;
 
  can_read: string[]
  can_write: string[]
 
  status: 'PROCESSING' | 'COMPLETED' | 'ERROR';
  errors?: any[];
 
  type: "thread";
  thread: Thread;
}
interface QueryPayload {
  thread: OmnilexResultThread;
}
 
const source = new SSE(endpoint_url, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer <token>`,
  },
  payload: JSON.stringify({
    thread: {
      
      date: new Date(),
      ownerUsername: email,
      can_read: [],
      can_write: [],
      status: "COMPLETED",
      type: "thread",
 
      thread: {
        date: new Date(),
        country: "CH",
        ownerUsername: email,
        interactions: [
          {
            id: "0",
            question: "I had an accident with a e-scooter in Zug, how can I reply to this letter ?",
            date: new Date(),
            documents: [],
            sources: [],
            reply: ""
          },
        ],
    },
  }
  } as QueryPayload),
});

Event types

Once the POST request is triggered, if the token is valid as well as the payload, a stream of JSON objects will be returned. These objects can have different fields, sometimes just one, sometimes multiple at the same time.

Here is the list of the most important fields (if you see a new field appearing, you can ignore it, as it would be an experimental feature, and we will notify you when the experimental feature is ready for production).

The last event is the thread object that return the entire new state of the thread.

response : Draft Stream

{
  "response" : "string"
} 

You have to concatenate all the response strings to form the final draft. The last response include the string <AILEGIS-END>.

considerationDetails : Document List

{
  "considerationDetails ": {
    "label": "paragraph|court_decision|commentary|document",
    "title": "string",
    "url": "string",
    "article_paragraphs_text": "string[]"
    "article_citations": "string[]",
    "relevant_ids": "number[]",
  }[]
}
  • label : a string representing the type of the Document
  • title : a string representing the title of the Document
  • url : a string representing the url of the Document
  • article_paragraphs_text : an array of string, each item of the array is a paragraph of the Document
  • article_citations : an array of string, each item of the array is the title of the paragraph of article_paragraphs_text at that index
  • relevant_ids : an array of number, each item of the array return a relevance score for the given question of the paragraph of article_paragraphs_text at that index. The number can have any value, it is used to compare paragraph within the same Document.

legalArea : Legal Area

{
  "legalArea" : "string"
} 

agentSteps : A list of all steps the legal research agent is taking

Experimental
{
  "agentSteps" : {
    "message": "string", 
    "level": "INFO" | "DEBUG"
  }[]
} 

The agentSteps are intermediate steps the agent is taking before giving the final answer. It is meant primarly to be used for debugging purposes, but can be extended to UI purposes to show the reasoning of the model while the user is waiting for the answer.

resultId : Result ID

{
  "resultId" : "string"
} 

The resultId is the id of the OmnilexResultThread that will be stored in the history. It is the 1st event in the stream.

result : Result

{
  "result" : "OmnilexResultThread"
} 

The result is the final event in the steam. It contains the OmnilexResultThread with the final answer and the list of documents that were used to answer the question, as well as the dev related metadata.

error : Error

{
  "error" : "string"
} 

The error string is a message, not an error code as it is more meant for debugging. Note that the API call still respect the HTTP error codes standards.

Here are some examples of the type of messages:

  • Failed to parse documents, error: ...
  • Lost connection with database, error: ...

Fully functional example

Do not forget to replace the token , if you do not have one look at the authentication doc

example.ts
import { SSE } from "sse.js";
 
const endpoint_url = "https://api.omnilex.ai/advice/app/combined"
const email = "test@test.ch"
 
const source = new SSE(endpoint_url, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer <token>`,
  },
  payload: JSON.stringify({
    thread: {
      date: new Date(),
      ownerUsername: email,
      can_read: [],
      can_write: [],
      status: "COMPLETED",
      type: "thread",
      thread: {
        date: new Date(),
        country: "CH",
        ownerUsername: email,
        interactions: [
          {
            id: "0",
            question: "I had an accident with a e-scooter in Zug, how can I reply to this letter ?",
            date: new Date(),
            documents: [],
            sources: [],
            reply: ""
          },
        ],
      },
    },
  }),
});
 
source.addEventListener("message", async (e: { data: string }) => {
  let data = JSON.parse(e.data);
  console.log(data)
});

Example of output on the console:

{
  considerationDetails: [ {label: 'paragraph', title: 'VRV, 3. Teil: Verhalten bei Unfällen, Sicherung der Unfallstelle', relevant_ids: Array(1), url: 'https://www.fedlex.admin.ch/eli/cc/1962/1364_1409_1420/de#art_54', article_citations: Array(3), …} , ... ]
}
 
{
  response: 'Given Article '
}
 
{
  response: 'VRV 3, you '
}
 
{
  response: 'are entitled to '
}
 
...
 
{
  response: 'best. <AILEGIS-END>'
}
 
{
  legalArea: "Construction law"
}
 
...
 
{
  thread: { ... }
}
 

Linking considerationDetails to segment in the response.

The answers are forced to follow the naming of the citation of the considerationDetails, if that considerationDetails is relevant for that paragraph. The easiest way to make a link from the text to the source is to use a regex on the considerationDetails to find matching citations.

Chat/Discussion mode with follow up questions

To ask for a follow up, we can resend the current thread and add a new interaction to it. In general the api will always answer the last interaction, using old interaction as context.

What is important to note is the id of the thread, as this is a follow up, we want to update in the history the same thread with the follow up result, for it the id if the thread should be the same. If the id is empty or the email of the owner of the thread is not the one asking the follow up a new thread is stored in the history.

example.ts
import { SSE } from "sse.js";
 
const endpoint_url = "https://api.omnilex.ai/advice/app/combined"
const email = "test@test.ch"
 
const old_thread: OmnilexResultThread = { ... } // this thread is the thread on which we want to ask a follow up question
 
// if old_thread.id is null or old_thread.ownerUsername !== email, then a new thread will be stored in the database.
 
const source = new SSE(endpoint_url, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer <token>`,
  },
  payload: JSON.stringify({
    thread: {
      ...old_thread,
      thread: {
        ...old_thread.thread,
        interactions: [
          ...old_thread.thread.interactions,
          {
            id: "1",
            question: "Can you elaborate a bit more that topic ?",
            date: new Date(),
            documents: [],
            sources: [],
            reply: ""
          },
        ],
      },
    },
  }),
});
 
source.addEventListener("message", async (e: { data: string }) => {
  let data = JSON.parse(e.data);
  console.log(data)
});