Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

TypeScript v1 객체 집합 API

객체 집합(Object set)은 한 객체 유형에 속한 순서 없는 객체 모음입니다. 집합 정의에서 필터, 링크 탐색과 집계를 구성한 뒤 필요한 결과만 로드할 수 있습니다. 입력으로 객체 배열 대신 집합을 받으면 필요한 시점까지 로드를 미룰 수 있습니다.

TSv1의 필터, 정렬과 집계에는 온톨로지 속성의 Searchable 렌더링 힌트가 필요합니다. Ontology Manager에서 활성화하여 인덱싱해야 합니다. TSv2와 Python은 생성 OSDK의 API를 사용합니다.

검색 시작

Objects.search()에는 프로젝트에 가져온 유형의 검색 메서드가 있습니다. 아래 가상 Flight는 departureAirportCode와 arrivalAirportCode를 가진다고 가정합니다. 항공편의 출발 코드로 검색하여 중복 없는 도착 코드 집합을 반환합니다.

import { Function, UserFacingError } from "@foundry/functions-api";
import { Objects } from "@foundry/ontology-api";

export class FlightFunctions {
    @Function()
    public async currentFlightDestinations(airportCode: string): Promise<Set<string>> {
        const flights = await Objects.search().flights()
            .filter(flight => flight.departureAirportCode.exactMatch(airportCode)).allAsync();
        const destinations = new Set<string>();
        for (const flight of flights) {
            if (flight.arrivalAirportCode === undefined) {
                throw new UserFacingError("도착 공항 코드가 없는 항공편이 있습니다.");
            }
            destinations.add(flight.arrivalAirportCode);
        }
        return destinations;
    }
}

Objects.search().flights([flight])처럼 객체 목록을 넣어 집합을 만들 수도 있습니다. 객체 RID 목록이나 객체 집합 RID도 인수로 사용할 수 있습니다.

속성별 필터

filter는 조건 정의를 받아 검색 집합을 좁힙니다.

속성 또는 관계필터
모든 속성 타입exactMatch()로 정확한 값 일치
값 존재 여부hasProperty()로 null 또는 undefined 여부에 대응하는 존재 조건
숫자, 날짜, Timestamprange().lt(), lte(), gt(), gte()
BooleanisTrue(), isFalse()
GeopointwithinDistanceOf(), withinPolygon(), withinBoundingBox()
GeoShapewithinBoundingBox(), intersectsBoundingBox(), doesNotIntersectBoundingBox(), withinPolygon(), intersectsPolygon(), doesNotIntersectPolygon()
링크isPresent()로 연결된 객체 존재 여부
배열contains()로 지정한 값 중 하나라도 포함하는지 검사

여러 정확한 값을 전달할 때는 exactMatch(...values)를 사용합니다. 빈 배열을 펼치면 필터를 무시하므로, 빈 입력에서 결과도 없어야 하는 함수는 검색 전에 빈 배열을 처리해야 합니다.

숫자 조건은 예를 들어 flight.passengerCount.range().gt(100)처럼 range를 거쳐 만듭니다.

문자열 토큰 검색

필터의미
phrase()모든 토큰이 같은 순서로, 사이에 다른 토큰 없이 연속해서 나타나야 합니다.
phrasePrefix()phrase와 같지만 마지막 토큰은 접두사 일치를 허용합니다.
prefixOnLastToken()모든 토큰을 포함하고 마지막 토큰은 접두사 일치를 허용합니다. 토큰 순서는 제한하지 않습니다.
matchAnyToken(), fuzzyMatchAnyToken()토큰 하나 이상을 포함합니다. fuzzy는 근사 일치를 허용합니다.
matchAllTokens(), fuzzyMatchAllTokens()모든 토큰을 포함합니다. fuzzy는 근사 일치를 허용합니다.

밑줄과 마침표는 토큰을 나누지 않습니다. banana를 phrase로 찾으면 banana_pudding이나 banana.pudding과 일치하지 않습니다. fresh banana의 phrasePrefix는 fresh banana_pudding에 일치하지만 banana_pudding fresh에는 일치하지 않습니다. pudding만으로 banana_pudding을 찾을 수 없습니다.

prefixOnLastToken의 big app는 big apples와 apples from the big tree에 일치하지만 apples from the biggest tree에는 일치하지 않습니다. 마지막 토큰 이외의 big은 정확한 토큰으로 남기 때문입니다.

문자열 배열도 원소 타입의 필터를 사용하되 메서드 이름이 조금 다를 수 있습니다. 배열 원소 중 하나가 조건을 만족하면 객체를 반환합니다.

조건 결합

Filters.and는 모든 조건, Filters.or는 하나 이상, Filters.not은 부정을 뜻합니다. filter를 여러 번 호출하면 and 조건이 됩니다. JavaScript의 &&와 ||로 필터 정의를 결합하면 안 됩니다.

가상 Flight.destination과 passengerCount로 두 공항의 승객 수 조건을 결합하는 예제입니다.

import { Filters, Function } from "@foundry/functions-api";
import { Flight, Objects, ObjectSet } from "@foundry/ontology-api";

export class FilterFunctions {
    @Function()
    public busyFlights(): ObjectSet<Flight> {
        return Objects.search().flights().filter(flight => Filters.or(
            Filters.and(flight.destination.exactMatch("SFO"), flight.passengerCount.range().gt(100)),
            Filters.and(flight.destination.exactMatch("LAX"), flight.passengerCount.range().gt(300))
        ));
    }
}

Fuzziness

fuzzy 필터에 아무 설정도 주지 않으면 검색 토큰 길이에 따라 자동 편집 거리를 허용합니다. @foundry/functions-api의 Fuzziness로 거리를 지정할 수 있습니다.

import { Fuzziness, Function } from "@foundry/functions-api";
import { Employee, Objects, ObjectSet } from "@foundry/ontology-api";

export class FuzzyFunctions {
    @Function()
    public similarFirstNames(): ObjectSet<Employee> {
        return Objects.search().employee().filter(employee =>
            employee.firstName.fuzzyMatchAnyToken("Michael", { fuzziness: Fuzziness.LEVENSHTEIN_TWO })
        );
    }

    @Function()
    public similarFullNames(): ObjectSet<Employee> {
        return Objects.search().employee().filter(employee =>
            employee.fullName.fuzzyMatchAllTokens("Michael Smith", { fuzziness: Fuzziness.LEVENSHTEIN_ONE })
        );
    }
}

첫 조건은 두 번 이내 편집으로 Michael이 되는 Michael, Micheal, Mikhael, Michel, Mikhail, Mihail 등을 허용하고 Miguel은 허용하지 않는 예입니다. 두 번째는 Michael과 Smith 각각 한 번 이내의 편집을 허용하므로 Mikhael Smitt가 일치하며 토큰 순서는 상관없습니다. 이 Fuzziness 조절에는 TSv2 대응 기능이 없습니다.

링크 탐색, Search Around

가져온 링크마다 탐색 메서드를 생성합니다. 다음은 출발 공항을 선택하고 항공편에서 승객 집합으로 이동하는 조건입니다.

const passengersDepartingFromAirport = Objects.search().flights()
    .filter(flight => flight.departureAirportCode.exactMatch(airportCode))
    .searchAroundPassengers();

airportCode는 검색할 코드 입력이고 searchAroundPassengers는 가져온 Flight-Passenger 링크의 생성 메서드입니다. 결과는 승객 집합이므로 추가 필터나 탐색을 구성할 수 있습니다.

TSv1 검색은 Search Around 깊이를 최대 3으로 제한하며 초과하면 런타임 오류가 발생합니다. A에서 B로 탐색할 때 결과 B는 OSv2에서 최대 1,000만 객체, OSv1에서 최대 10만 객체로 제한합니다. 실제 객체를 메모리에 로드하는 한도와는 별개입니다.

KNN 벡터 검색

K-nearest neighbors는 OSv2에 인덱싱한 벡터 속성에서 가까운 객체를 찾습니다. TSv1의 kValue는 1부터 100이며 검색 벡터는 인덱싱한 벡터와 차원이 같아야 하고 최대 2,048차원입니다. 조건을 벗어나면 오류가 발생합니다.

functions.json에서 enableVectorProperties를 true로 설정합니다. 다음은 3차원 vectorProperty를 인덱싱한 가상 Movies 유형의 예입니다. 실제 임베딩 모델의 출력 차원과 혼용하지 않습니다.

import { Double } from "@foundry/functions-api";
import { Objects } from "@foundry/ontology-api";

const kValue = 2;
const vector: Double[] = [0.7, 0.1, 0.3];
const movies = Objects.search().movies()
    .nearestNeighbors(movie => movie.vectorProperty.near(vector, { kValue }))
    .orderByRelevance()
    .take(kValue);

위 검색 코드는 함수 본문에서 사용합니다. orderByRelevance로 관련도를 정렬한 뒤 take로 필요한 수를 읽습니다. TSv2도 nearestNeighbors라는 이름을 쓰지만 인수가 다르고 최대 이웃 수가 500입니다. TSv1의 100 한도를 두 API에 공통으로 적용하면 안 됩니다.

임베딩을 만드는 과정은 의미 기반 검색을 따릅니다. 문서와 질의의 모델 및 출력 차원을 맞춰야 합니다.

집합 연산과 로드

동일한 객체 유형의 집합끼리 union, intersect와 subtract를 사용할 수 있습니다. 각각 합집합, 교집합 및 주어진 집합의 객체 제외입니다.

all과 allAsync는 전체 객체를 로드하며 TSv1 한도는 10만 객체입니다. 1만 객체를 넘으면 시간 초과가 생길 수 있지만 1만이 강제 개수 한도라는 의미는 아닙니다. allAsync는 Promise를 반환하므로 서로 독립적인 집합을 병렬로 읽을 수 있습니다.

정렬하고 일부만 읽기

orderBy는 Searchable인 숫자, 날짜와 문자열 속성에 asc 또는 desc를 지정합니다. 여러 번 호출하면 여러 속성으로 정렬합니다. 예를 들어 시작 날짜가 가장 이른 직원 열 명은 employees().orderBy(employee => employee.startDate.asc()).take(10)으로 가져옵니다.

orderByRelevance는 검색 조건과의 관련도를 높은 순으로 정렬합니다. 속성 안의 용어 빈도와 전체 객체의 빈도 등이 영향을 미치며, 정확한 값만 비교하거나 비문자열만 필터링하는 경우에는 덜 적합합니다.

한 검색에서 orderBy와 orderByRelevance를 함께 사용할 수 없습니다. take와 takeAsync는 정렬한 뒤에만 사용할 수 있으며 지정한 수만 로드합니다.

가상의 사고 기록에서 red car collision with deer를 matchAnyToken으로 검색하고 orderByRelevance().take(10)을 적용하면 임의의 일치 객체 열 개 대신 해당 검색과 관련도가 높은 결과를 먼저 읽습니다.

그룹과 구간

groupBy로 첫 속성을 나누고, 필요하면 segmentBy로 두 번째 Searchable 속성을 나눕니다. 결과 전체 버킷 수는 최대 10,000개이며 초과하면 오류가 발생합니다.

속성버킷 방식
BooleantopValues(). true와 false의 두 버킷
StringtopValues()는 상위 1,000값을 빠르게 집계합니다. 그보다 고유 값이 많으면 근사 결과이며 상위 목록 자체도 부정확할 수 있습니다.
StringexactValues()는 최대 10,000버킷을 고려합니다. exactValues({ maxBuckets: n })의 n은 0부터 10,000 사이 정수입니다. 더 많은 후보를 고려하므로 느릴 수 있고 한도를 넘는 고유 값에서는 완전한 집계로 간주하면 안 됩니다.
숫자byRanges로 명시 구간, byFixedWidth로 고정 너비를 사용합니다.
LocalDatebyYear(), byQuarter(), byMonth(), byWeek(), byDays(). byDays에 일 수를 전달할 수 있습니다.
TimestampLocalDate의 방식과 byHours(), byMinutes(), bySeconds(). 각 방식에 단위 수를 전달할 수 있습니다.
Array원소 타입이 지원하는 같은 버킷 방식을 사용합니다.

byRanges({ min: 0, max: 50 }, { min: 50, max: 100 })의 구간은 [0, 50), [50, 100)입니다. min은 포함하고 max는 제외합니다. 한쪽 끝을 생략하면 음의 무한대 또는 양의 무한대까지 열린 구간입니다. byFixedWidth(50)는 너비가 50인 버킷을 만듭니다.

배열도 객체 단위로 셉니다. Alice.pastCountries가 [US, UK], Bob.pastCountries가 [US]이면 해당 배열의 exactValues().count() 집계는 US 2와 UK 1입니다.

집계 지표와 반환

메서드결과
count()객체 수
average()지정 숫자, Timestamp 또는 Date의 평균
max(), min()지정 숫자, Timestamp 또는 Date의 최댓값과 최솟값
sum()숫자 속성의 합
cardinality()속성의 고유 값 개수 근사치

속성을 받는 지표에는 Searchable 속성을 사용해야 합니다. groupBy만 있으면 TwoDimensionalAggregation, segmentBy도 있으면 ThreeDimensionalAggregation을 Promise로 반환합니다.

import { Double, Function, ThreeDimensionalAggregation } from "@foundry/functions-api";
import { Objects } from "@foundry/ontology-api";

export class AggregationFunctions {
    @Function()
    public async employeesByRoleAndOffice(): Promise<ThreeDimensionalAggregation<string, string, Double>> {
        return Objects.search().employee()
            .groupBy(employee => employee.title.topValues())
            .segmentBy(employee => employee.office.topValues())
            .count();
    }

    @Function()
    public async employeeCount(): Promise<Double> {
        const count = await Objects.search().employee().count();
        return count ?? 0;
    }
}

집계는 원격 서비스에서 실행하므로 결과를 await한 뒤 undefined 처리 등을 합니다. count의 0 대체는 정상 응답 값이 undefined인 경우에만 적용하며 요청 오류를 삼키지 않습니다.

groupBy 없이 average(employee => employee.tenure), max, min, sum(employee => employee.salary), cardinality(employee => employee.office)로 전체 집합의 단일 지표도 계산할 수 있습니다.

TSv1 그룹 결과는 중첩 버킷 구조를 바로 반환하지만 TSv2 aggregate는 평평한 행 목록이므로 소비 함수가 기대하는 구조로 조립해야 합니다. 사용자 지정 집계는 버킷을 가공하는 예제입니다.