๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Java

๋ฐ•๋ช…์ˆ˜ ๋งŒ๋“ค๊ธฐ

by kriorsen 2026. 3. 13.

๐Ÿ“Œ ์šฐ๋ฆฌ๋Š” ์™œ ๋ฐ•๋ช…์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ• ๊นŒ?

์‹ฌ์‹ฌํ•  ๋•Œ ๋ฐ•๋ช…์ˆ˜๊ฐ€ ๋‚˜์™€ ๋Œ€ํ™”๋ฅผ ํ•ด ์ฃผ๋ฉด ์ฐธ ์ข‹๊ฒ ๋Š”๋ฐ, ๋ฐ•๋ช…์ˆ˜์—๊ฒŒ ์นœ๊ตฌ๋น„๋ฅผ ์ฃผ๊ธฐ์—” ์ฃผ๋จธ๋‹ˆ ์‚ฌ์ •์ด ๋„‰๋„‰ํ•˜์ง€ ์•Š๋‹ค….

ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ์—๊ฒ ์ฝ”๋”ฉ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋งฅ๋ถ๊ณผ ์ฝ”๋”ฉ ์ฒœ์žฌ ์ง€ํ”ผํ‹ฐ๊ฐ€ ์žˆ์œผ๋‹ˆ, ๊ฐ€์งœ ๋ฐ•๋ช…์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ์ž๊ธ‰์ž์กฑํ•˜๋ฉด ๋œ๋‹ค ๐Ÿ˜ˆ

๐Ÿ“Œ Embabel์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž

Embabel์€๋‹จ์ˆœํžˆํ…์ŠคํŠธ๋ฅผ์ƒ์„ฑํ•˜๋Š”๊ฒƒ์„๋„˜์–ด์‚ฌ์šฉ์ž๋ฅผ๋Œ€์‹ ํ•ด์ถ”๋ก ํ•˜๊ณ ๋ชฉํ‘œ์ง€ํ–ฅ์ ์œผ๋กœํ–‰๋™ํ•˜๋ฉฐ๊ณ„ํš์„์„ธ์šฐ๊ณ ๋„๊ตฌ๋ฅผํ˜ธ์ถœํ•˜๋Š”์ง€๋Šฅํ˜•์—์ด์ „ํŠธ๋ฅผ๊ตฌ์ถ•ํ•˜๊ธฐ์œ„ํ•œJVM๊ธฐ๋ฐ˜์˜์—์ด์ „ํŠธํ”„๋ ˆ์ž„์›Œํฌ๋กœ๊ฐœ๋ฐœ์ž๊ฐ€๋ณต์žกํ•œ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ์ผ์ผ์ด์ฝ”๋”ฉํ•˜์ง€์•Š์•„๋„์—์ด์ „ํŠธ๊ฐ€์Šค์Šค๋กœ๋ชฉํ‘œ๋ฅผ๋‹ฌ์„ฑํ•˜๊ธฐ์œ„ํ•œ์ตœ์ ์˜์•ก์…˜์ˆœ์„œ๋ฅผ๊ฒฐ์ •ํ•˜๋„๋ก๋•๋Š”๋‹ค.(์•„์ด๊ณ  ์ˆจ์ฐจ)

์„ธ ์ค„ ์š”์•ฝ์„ ๋” ์š”์•ฝํ•˜์ž๋ฉด, ๋Œ€์ถฉ AI ์—์ด์ „ํŠธ ๊ฐœ๋ฐœ์„ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค.

 

โšก๏ธ 1. GOAP(Goal-Oriented Action Planning)

GOAP๋ผ๋Š” ๋น„-LLM ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ด์„œ ๋ชฉํ‘œ ๋‹ฌ์„ฑ์„ ์œ„ํ•œ ์ตœ์ ์˜ ๊ฒฝ๋กœ๋ฅผ ์Šค์Šค๋กœ ์ฐพ๋Š” ๊ฒƒ์ด ํŠน์ง•์ด๋‹ค. ๋ฐฑ์ค€ ์‹ค๋ฒ„ 3 ์ด์ƒ๋ถ€ํ„ฐ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค.

 

์ดํ•ด๋ฅผ ๋•๊ธฐ ์œ„ํ•ด ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•˜์ž๋ฉด,

์›๋ž˜ ์šฐ๋ฆฌ๋Š” ์–ด๋–ค ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ์‹คํ–‰์„ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค:

1. ์‚ฌ๊ณผ๋ฅผ ์”ป์–ด์„œ ์ค€๋น„ํ•œ๋‹ค.
2. ๋ฐ”๋‚˜๋‚˜๋ฅผ ์”ป์–ด์„œ ์ค€๋น„ํ•œ๋‹ค.
3. ๊ผฌ์น˜๋ฅผ ์ค€๋น„ํ•œ๋‹ค.
4. ๊ผฌ์น˜์— ์‚ฌ๊ณผ์™€ ๋ฐ”๋‚˜๋‚˜๋ฅผ ๊ฝ‚๋Š”๋‹ค.
5. ์„คํƒ•์„ ๋…น์ธ๋‹ค.
6. ๋…น์€ ์„คํƒ•์„ ๊ผฌ์น˜์— ์งœ์ž” ํ•ด์„œ ํƒ•ํ›„๋ฃจ๋ฅผ ํƒ€๋ž€ โœจ

 

๊ทธ๋Ÿฐ๋ฐ ์ด ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์ฝ”๋“œ๋ฅผ ์ด๋ ‡๊ฒŒ ์งœ๊ฒŒ ๋œ๋‹ค:

์ตœ์ข… ๋ชฉํ‘œ: ํƒ•ํ›„๋ฃจ ํƒ€๋ž€ โœจ
ํ—ˆ์šฉ๋œ ํ–‰๋™ ๋ชฉ๋ก
- ์‚ฌ๊ณผ ์”ป์–ด์„œ ์ค€๋น„ํ•˜๊ธฐ (์ค€๋น„๋ฌผ: ์—†์Œ, ๊ฒฐ๊ณผ๋ฌผ: ๋ฝ€๋“๋ฝ€๋“ ์‚ฌ๊ณผ)
- ๋ฐ”๋‚˜๋‚˜ ์”ป์–ด์„œ ์ค€๋น„ํ•˜๊ธฐ (์ค€๋น„๋ฌผ: ์—†์Œ, ๊ฒฐ๊ณผ๋ฌผ: ๋ฝ€๋“๋ฝ€๋“ ๋ฐ”๋‚˜๋‚˜)
- ๊ผฌ์น˜ ์ค€๋น„ํ•˜๊ธฐ (์ค€๋น„๋ฌผ: ์—†์Œ, ๊ฒฐ๊ณผ๋ฌผ: ๊ทธ๋ƒฅ ๊ผฌ์น˜)
- ๊ผฌ์น˜์— ์‚ฌ๊ณผ๋ž‘ ๋ฐ”๋‚˜๋‚˜ ๊ฝ‚๊ธฐ (์ค€๋น„๋ฌผ: ๋ฝ€๋“๋ฝ€๋“ ์‚ฌ๊ณผ, ๋ฝ€๋“๋ฝ€๋“ ๋ฐ”๋‚˜๋‚˜, ๊ทธ๋ƒฅ ๊ผฌ์น˜, ๊ฒฐ๊ณผ๋ฌผ: ์ตœ๊ฐ• ๊ผฌ์น˜)
- ์„คํƒ• ๋…น์—ฌ์„œ ์„คํƒ•๋ฌผ ๋งŒ๋“ค๊ธฐ (์ค€๋น„๋ฌผ: ์—†์Œ, ๊ฒฐ๊ณผ๋ฌผ: ์„คํƒ•๋ฌผ)
- ์ตœ๊ฐ• ๊ผฌ์น˜์— ์„คํƒ•๋ฌผ์„ ์งœ์ž” ํ•ด์„œ ํƒ•ํ›„๋ฃจ๋ฅผ ํƒ€๋ž€ โœจ (์ค€๋น„๋ฌผ: ์ตœ๊ฐ• ๊ผฌ์น˜, ์„คํƒ•๋ฌผ, ๊ฒฐ๊ณผ๋ฌผ: ํƒ€๋ž€ ํƒ•ํ›„๋ฃจ)

 

์œ„์ƒ ์ •๋ ฌ์˜ ๋ƒ„์ƒˆ๊ฐ€ ์†”์†”… (ํ•˜์ง€๋งŒ GOAP๋Š” ๋” ๋ณต์žกํ•œ ๊ฒฝ๋กœ ํƒ์ƒ‰์„ ์œ„ํ•ด ๋‹จ์ˆœํ•œ ์ˆœ์„œ ์ •๋ ฌ์„ ๋„˜์–ด, ํ–‰๋™์˜ ๋น„์šฉ๊ณผ ์„ฑ๊ณต ํ™•๋ฅ ๊นŒ์ง€ ๊ณ„์‚ฐํ•ด ์ตœ์ ์˜ ๊ฒฝ๋กœ๋ฅผ ์ฐพ๋Š”๋‹ค)

์ด๋ ‡๊ฒŒ ์ž‘์„ฑ์„ ํ•˜๋ฉด embabel์ด GOAP ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ด, ์•Œ์•„์„œ ์–ด๋–ค ํ–‰๋™์„ ์‹คํ–‰ํ• ์ง€, ๊ทธ๋ฆฌ๊ณ  ๊ทธ ํ–‰๋™๋“ค์„ ์–ด๋–ค ์ˆœ์„œ๋กœ ์‹คํ–‰ํ•ด์„œ ์ตœ์ข… ๋ชฉํ‘œ์ธ ํƒ•ํ›„๋ฃจ ํƒ€๋ž€์„ ๋„์ถœํ• ์ง€ ๊ฒฝ๋กœ๋ฅผ ์Šค์Šค๋กœ ์ฐพ๋Š” ๊ตฌ์กฐ์ด๋‹ค. ์ด ๊ฒฝ๋กœ ์ถ”๋ก ์˜ ํ•ต์‹ฌ์ด ๋˜๋Š” ๊ฒƒ์€ ๊ฐ ํ–‰๋™์˜ ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ ํƒ€์ž…์ด๋‹ค, ์œ„ ์˜ˆ์‹œ์—์„œ ๋ณธ๋‹ค๋ฉด '๊ผฌ์น˜์— ์‚ฌ๊ณผ๋ž‘ ๋ฐ”๋‚˜๋‚˜ ๊ฝ‚๊ธฐ' ๋ผ๋Š” ํ–‰๋™์—๋Š” ๋ฝ€๋“๋ฝ€๋“ ์‚ฌ๊ณผ, ๋ฝ€๋“๋ฝ€๋“ ๋ฐ”๋‚˜๋‚˜, ๊ทธ๋ƒฅ ๊ผฌ์น˜๊ฐ€ ํ•„์š”ํ•˜๋‹ˆ ์ด ์ค€๋น„๋ฌผ๋“ค์„ ๋งŒ๋“ค์–ด ์ฃผ๋Š” ํ–‰๋™์„ ๋จผ์ € ์ˆ˜ํ–‰ํ•˜๋„๋ก ์ˆœ์„œ๋ฅผ ์ •ํ•˜๋Š” ์‹์ด๋‹ค ๐Ÿ‘€

 

(์ž ๊น ์ฟ ํ‚ค๋Ÿฐ ์˜ค๋ธ๋ธŒ๋ ˆ์ดํฌ ์˜์ž…ํ•˜์ž๋ฉด, ์ด ๊ฒŒ์ž„์—๋Š” ์งฑ๊ท€์—ฌ์šด ๊ฐ๊ทคํƒ•ํ›„๋ฃจ๋ง›์ฟ ํ‚ค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค ๐ŸŠ๐ŸŠ ์ฟ ์˜ค๋ธ ์œ ์ €๋Š” ์‚์‚ ์ณ์ฃผ์„ธ์š” ์นœ๊ตฌ์ฝ”๋“œ ๋“ค๊ณ  ๋‚ ์•„๊ฐˆ๊ฒŒ์š” ๐Ÿคธ‍โ™‚๏ธ)

 

 

๊ทธ๋Ÿฐ๋ฐ ์†”์งํžˆ ์ฝ”๋“œ๋ฅผ ์งœ๋Š” ์ž…์žฅ์—์„œ๋Š” ์ „์ž์˜ ๋ฐฉ์‹์ด ํ›จ์”ฌ ์ง๊ด€์ ์ด๊ณ  ํ๋ฆ„๋„ ํŒŒ์•…ํ•˜๊ธฐ ์‰ฝ๊ณ , ๊ตณ์ด ์ด GOAP ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๋„์ž…ํ•ด ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์ฝ”๋“œ์˜ ์‹คํ–‰์„ ์ œ์–ดํ•˜๋Š” ๊ฒƒ์ด ๋ฌด์Šจ ์žฅ์ ์ด ์žˆ๋‹ค๋Š” ๊ฑด์ง€ ์ฒ˜์Œ์—๋Š” ๋‚ฉ๋“์ด ์•ˆ ๋๋‹ค ๐Ÿ˜’

 

๊ทธ๋Ÿฌ๋‹ค ์•Œ๊ฒŒ ๋œ ์‚ฌ์‹ค์€, ์ž๋™ ๊ณ„ํš(GOAP)์˜ ์ง„๊ฐ€๋Š” ์˜ˆ์™ธ ์ƒํ™ฉ์—์„œ ๋‚˜ํƒ€๋‚œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 

๋งŒ์•ฝ์— ์šฐ๋ฆฌ๊ฐ€ ์–ด๋А ๋‚  ๊ฐ‘์ž๊ธฐ ๋šฑ์ด๊ฐ€ ๋˜์–ด ๋ฒ„๋ ธ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด์ž:

 

์•„๋ฌด๋ž˜๋„ ํ•œ ์†์— ์†๊ฐ€๋ฝ์ด ๋‹ค์„ฏ ๊ฐœ์—์„œ 0๊ฐœ๋กœ ๋ฐ”๋€๋‹ค๋ฉด ๊ผฌ์น˜์— ์‚ฌ๊ณผ๋‚˜ ๋ฐ”๋‚˜๋‚˜๋ฅผ ๊ฝ‚๋‹ค๊ฐ€ ๋ฐ”๋‹ฅ์— ๋–จ์–ดํŠธ๋ฆฌ๋Š” ์ผ์ด ์ž์ฃผ ์ƒ๊ธธ ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ด๋Ÿฐ ํ‚น๋ฐ›๋Š” ์ฝ”๋“œ๊ฐ€ ํƒ„์ƒํ•œ๋‹ค:

public Tanghulu makeTanghulu() {

    Apple apple = null;
    Banana banana = null;
    Skewer skewer = null;
    SkewerWithFruit fruitSkewer = null;
    SugarWater sugarWater = null;

    // ์‚ฌ๊ณผ ์ค€๋น„
    while (apple == null) {
        try {
            apple = washApple();
        } catch (AppleDroppedException e) {
            System.out.println("์‚ฌ๊ณผ ๋–จ์–ด์ง! ๋‹ค์‹œ ์”ป๋Š”๋‹ค.");
        }
    }

    // ๋ฐ”๋‚˜๋‚˜ ์ค€๋น„
    while (banana == null) {
        try {
            banana = washBanana();
        } catch (BananaDroppedException e) {
            System.out.println("๋ฐ”๋‚˜๋‚˜ ๋–จ์–ด์ง! ๋‹ค์‹œ ์”ป๋Š”๋‹ค.");
        }
    }

    // ๊ผฌ์น˜ ์ค€๋น„
    skewer = prepareSkewer();

    // ๊ณผ์ผ ๊ฝ‚๊ธฐ (์—ฌ๊ธฐ์„œ ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์Œ)
    while (fruitSkewer == null) {
        try {
            fruitSkewer = stickFruit(skewer, apple, banana);
        } catch (FruitDroppedException e) {

            System.out.println("๊ณผ์ผ ๋–จ์–ด์ง! ๋‹ค์‹œ ์ค€๋น„");

            // ๋–จ์–ด์ง„ ๊ณผ์ผ ๋‹ค์‹œ ์ค€๋น„
            apple = null;
            banana = null;

            while (apple == null) {
                try {
                    apple = washApple();
                } catch (AppleDroppedException ignore) {}
            }

            while (banana == null) {
                try {
                    banana = washBanana();
                } catch (BananaDroppedException ignore) {}
            }
        }
    }

    // ์„คํƒ• ๋…น์ด๊ธฐ
    sugarWater = meltSugar();

    // ์ตœ์ข… ํƒ•ํ›„๋ฃจ
    return coatSugar(fruitSkewer, sugarWater);
}

 

ํ•ต์‹ฌ ๋ฌธ์ œ๋Š” ์ ˆ์ฐจํ˜• ์ฝ”๋“œ์—์„œ๋Š” ์ƒํƒœ ๋ณต๊ตฌ ๋กœ์ง์ด ๋ณต์žกํ•ด์ง€๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋”์ฐํ•˜๊ฒŒ๋„ ์„คํƒ•์ด ํƒ€๊ฑฐ๋‚˜, ๊ผฌ์น˜๊ฐ€ ๋ถ€๋Ÿฌ์ง€๊ฑฐ๋‚˜, ๋ฐ”๋‚˜๋‚˜ ๊ป์งˆ์„ ๊น ๋”๋‹ˆ ์ฉ์€ ๋ฐ”๋‚˜๋‚˜๊ฐ€ ๋‚˜์˜จ ๊ฒƒ๊ณผ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ์‹คํŒจ ์ผ€์ด์Šค๋“ค์€ ์•„์ง ์ฝ”๋“œ์— ์ ์šฉํ•˜์ง€๋„ ์•Š์€ ์ƒํƒœ์ด๋‹ค.

 

๋ฌด์—‡๋ณด๋‹ค ์šฐ๋ฆฌ๊ฐ€ ํ•  ์—์ด์ „ํŠธ ๊ฐœ๋ฐœ์˜ ํ•ต์‹ฌ์ด ๋˜๋Š” AI๋Š”… ํ•ด๋งˆ ์ด๋ชจ์ง€๋ฅผ ๋ณด์—ฌ ๋‹ฌ๋ผ๊ณ  ํ•˜๋ฉด ๊ณ ์žฅ๋‚˜๋Š” ์น˜๋ช…์ ์ธ ๋ฒ„๊ทธ๊ฐ€ ์žˆ๋‹ค ๐Ÿคฆ‍โ™€๏ธ๐Ÿคฆ‍โ™€๏ธ

 

๊ฐœ๋ณต์น˜ ChatGPT ํ•˜๋‚˜๋งŒ ๋ด๋„ ์•Œ ์ˆ˜ ์žˆ๋“ฏ, ์—์ด์ „ํŠธ ๊ฐœ๋ฐœ์— ์žˆ์–ด ํšŒ๋ณต ํƒ„๋ ฅ์„ฑ์„ ํ•ธ๋“ค๋งํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค.

๐Ÿค” 2. Embabel์˜ ํšŒ๋ณต ํƒ„๋ ฅ์„ฑ์€ ์–ด๋””์—์„œ ์˜ค๋Š”๊ฐ€

Embabel์—๋Š” ์—์ด์ „ํŠธ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋ธ”๋ž™๋ณด๋“œ๋ผ๋Š” ์ค‘์•™ ์ €์žฅ์†Œ ๊ฐ™์€ ๊ณต๊ฐ„์ด ์žˆ๋‹ค. ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋œ ๋ฝ€๋“๋ฝ€๋“ ์‚ฌ๊ณผ๋ž‘ ๋ฝ€๋“๋ฝ€๋“ ๋ฐ”๋‚˜๋‚˜๋ฅผ ์ €์žฅํ•ด ๋†“๊ณ  ์žˆ๋Š” ๋ณด๊ด€ํ•จ ์ •๋„๋กœ ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค! ๋ฐ”๋‚˜๋‚˜๊ฐ€ ์ฉ์—ˆ๊ฑฐ๋‚˜ ๊ผฌ์น˜๋ฅผ ๋–จ์–ดํŠธ๋ฆฌ๋ฉด, ์ด ๋ณด๊ด€ํ•จ์—์„œ ์ค€๋น„๋ฌผ์ด ๋ถ€์กฑํ•ด์ง„ ๊ฒƒ์„ Embabel์ด GOAP๋ฅผ ํ†ตํ•ด ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ์ธ ๊ฒƒ์ด๋‹ค.

 

Embabel์€ ๋ธ”๋ž™๋ณด๋“œ์™€ GOAP๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด  ๋ฃจํ”„๋ฅผ ํ˜•์„ฑํ•œ๋‹ค:

  1. Observe (๊ด€์ฐฐ): ํ˜„์žฌ ๋ธ”๋ž™๋ณด๋“œ์˜ ์ƒํƒœ์™€ ์ด์ „ ์•ก์…˜์˜ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•œ๋‹ค.
  2. Orient (๋ฐฉํ–ฅ ์„ค์ •): ๋งˆ์ง€๋ง‰ ๊ณ„ํš ์ˆ˜๋ฆฝ ์ดํ›„ ๋ฌด์—‡์ด ๋ณ€ํ–ˆ๋Š”์ง€ ํŒŒ์•…ํ•œ๋‹ค.
  3. Decide (๊ฒฐ์ •): GOAP ํ”Œ๋ž˜๋„ˆ๊ฐ€ ์ƒˆ๋กœ์šด ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ณ„ํš์„ ์ˆ˜๋ฆฝํ•˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
  4. Act (์‹คํ–‰): ๊ณ„ํš๋œ ๋‹ค์Œ ์•ก์…˜์„ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ธ”๋ž™๋ณด๋“œ์— ๋ฐ˜์˜ํ•œ๋‹ค.
๐Ÿšจ "์˜ˆ์™ธ ์ƒํ™ฉ์—์„œ ์ง„๊ฐ€๊ฐ€ ๋‚˜ํƒ€๋‚œ๋‹ค"๋Š” ๋ฌธ์žฅ์— ์˜คํ•ด์˜ ์†Œ์ง€๊ฐ€ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„ ๋ง๋ถ™์ด์ž๋ฉด, ์‹ค์ œ๋กœ Embabel์€ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ์„ ๋•Œ๋งŒ ๋‹ค์‹œ ๊ณ„ํš์„ ์„ธ์šฐ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋งค ํ–‰๋™์ด ๋๋‚  ๋•Œ๋งˆ๋‹ค ๋ฃจํ”„๋ฅผ ๋Œ๋ฉฐ ์ƒํƒœ๋ฅผ ์žฌํ‰๊ฐ€ํ•œ๋‹ค.

 

๐Ÿ“Œ Embabel ๊ธฐ์ดˆ ์Œ“๊ธฐ

embabel์„ ํ™œ์šฉํ•ด ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜ ์˜ˆ์‹œ repository๋ฅผ cloneํ•˜๋ฉด ๋œ๋‹ค:

https://github.com/embabel/embabel-agent-examples

 

GitHub - embabel/embabel-agent-examples: Agent Framework Examples for Java and Kotlin Developers

Agent Framework Examples for Java and Kotlin Developers - embabel/embabel-agent-examples

github.com

 

์•„๋ž˜๋Š” Embabel ๋ฌธ์„œ์— ์žˆ๋Š” Agent ํด๋ž˜์Šค์˜ ์˜ˆ์‹œ ์ฝ”๋“œ์ด๋‹ค.

@Agent(description = "Find news based on a person's star sign")  
public class StarNewsFinder {

    private final HoroscopeService horoscopeService;  
    private final int storyCount;

    public StarNewsFinder(
            HoroscopeService horoscopeService,  
            @Value("${star-news-finder.story.count:5}") int storyCount) {
        this.horoscopeService = horoscopeService;
        this.storyCount = storyCount;
    }

    @Action  
    public StarPerson extractStarPerson(UserInput userInput, OperationContext context) {  
        return context.ai()
            .withLlm(OpenAiModels.GPT_41)
            .createObject("""
                Create a person from this user input, extracting their name and star sign:
                %s""".formatted(userInput.getContent()), StarPerson.class);  
    }

    @Action  
    public Horoscope retrieveHoroscope(StarPerson starPerson) {  
        // Uses regular injected Spring service - not LLM
        return new Horoscope(horoscopeService.dailyHoroscope(starPerson.sign()));  
    }

    @Action  
    public RelevantNewsStories findNewsStories(
            StarPerson person, Horoscope horoscope, OperationContext context) {  
        var prompt = """
            %s is an astrology believer with the sign %s.
            Their horoscope for today is: %s
            Given this, use web tools to find %d relevant news stories.
            """.formatted(person.name(), person.sign(), horoscope.summary(), storyCount);

        return context.ai().withDefaultLlm()
            .withToolGroup(CoreToolGroups.WEB)  
            .createObject(prompt, RelevantNewsStories.class);
    }

    @AchievesGoal(description = "Write an amusing writeup based on horoscope and news")  
    @Action
    public Writeup writeup(
            StarPerson person, RelevantNewsStories stories, Horoscope horoscope,
            OperationContext context) {  
        var llm = LlmOptions.fromCriteria(ModelSelectionCriteria.getAuto())
            .withTemperature(0.9);  

        var storiesFormatted = stories.items().stream()
            .map(s -> "- " + s.url() + ": " + s.summary())
            .collect(Collectors.joining("\n"));

        var prompt = """
            Write something amusing for %s based on their horoscope and news stories.
            Format as Markdown with links.
            <horoscope>%s</horoscope>
            <news_stories>
            %s
            </news_stories>
            """.formatted(person.name(), horoscope.summary(), storiesFormatted);  

        return context.ai().withLlm(llm).createObject(prompt, Writeup.class);  
    }
}

 

@Agent

Agent๋Š” ํŠน์ • ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๋„๋ฉ”์ธ ๋กœ์ง, AI ์—ญ๋Ÿ‰, ๋„๊ตฌ ์‚ฌ์šฉ์„ ํ•˜๋‚˜๋กœ ๋ฌถ์€ ๋…๋ฆฝ์ ์ธ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์ •์˜ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. ํด๋ž˜์Šค ๋ ˆ๋ฒจ์— ์ด ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๊ฒฝ์šฐ, ํ•ด๋‹น ํด๋ž˜์Šค๊ฐ€ multi-step flow๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์—์ด์ „ํŠธ์ž„์„ ํ”„๋ ˆ์ž„์›Œํฌ์— ์•Œ๋ฆฌ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. @Agent ์ž‘์„ฑ ์‹œ description ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ํ•„์ˆ˜๋กœ ์ œ๊ณต๋˜์–ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋Š” ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์œ„ํ•œ ์šฉ๋„์ผ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, LLM์ด ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์— ๊ฐ€์žฅ ์ ํ•ฉํ•œ ์—์ด์ „ํŠธ๋ฅผ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ์ˆœ์œ„๋ฅผ ๋งค๊ธธ ๋•Œ ์ค‘์š”ํ•œ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

 

+) Spring Component ์–ด๋…ธํ…Œ์ด์…˜์„ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด์„œ ์•Œ์•„์„œ ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋œ๋‹ค

@Action

์—์ด์ „ํŠธ ๋‚ด๋ถ€์— ์—ฌ๋Ÿฌ public ๋ฉ”์„œ๋“œ์— @Action ์ด ๋ถ™์–ด ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๊ฒŒ ์•ž์„œ ์˜ˆ์‹œ์—์„œ ์–ธ๊ธ‰ํ•œ ํ–‰๋™ ๋ชฉ๋ก๋“ค์ด๋‹ค. @Action์€ ์—์ด์ „ํŠธ๊ฐ€ ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์ทจํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ๋ณ„์ ์ธ ๋‹จ๊ณ„ ๋˜๋Š” ๊ธฐ๋Šฅ์„ ์ •์˜ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ์ด ์•ก์…˜ ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๋ฐ˜ํ™˜๊ฐ’์€ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์‹คํ–‰ ๊ณ„ํš์„ ์„ธ์šฐ๋Š” ํ•ต์‹ฌ ์ง€ํ‘œ๊ฐ€ ๋œ๋‹ค. 

์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๊ต‰์žฅํžˆ ๋‹ค์–‘ํ•œ๋ฐ ์•ผ๋ฌด์ง€๊ฒŒ ์จ๋จน์œผ๋ฉด ๋” ๋ฉ‹์ง„ ์—์ด์ „ํŠธ๊ฐ€ ๋งŒ๋“ค์–ด์งˆ์ง€๋„…?

 

Non-LLM Action

    @Action  
    public Horoscope retrieveHoroscope(StarPerson starPerson) {  
        // Uses regular injected Spring service - not LLM
        return new Horoscope(horoscopeService.dailyHoroscope(starPerson.sign()));  
    }

retrieveHoroscope ๋ฉ”์„œ๋“œ๋ฅผ ์ฝ์–ด๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋“ฏ, ๋ชจ๋“  Action์— ai์— ๋Œ€ํ•œ ํ˜ธ์ถœ์ด ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์‹์œผ๋กœ ์ „ํ†ต์ ์ธ ๋ฐฉ์‹์˜ ๊ฐœ๋ฐœ ๋ฐฉ์‹๊ณผ ai๋ฅผ ์ด์šฉํ•œ ๊ฐœ๋ฐœ์„ ์ ์ ˆํ•˜๊ฒŒ ์ž˜ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜๋ฆ„์˜ ์žฅ์ ์œผ๋กœ ๊ผฝ์„ ์ˆ˜ ์žˆ๋‹ค ๐Ÿ‘€ 

Multi-Input ์˜์กด์„ฑ

    @Action  
    public RelevantNewsStories findNewsStories(
            StarPerson person, Horoscope horoscope, OperationContext context) {  }

    @AchievesGoal(description = "Write an amusing writeup based on horoscope and news")  
    @Action
    public Writeup writeup(
            StarPerson person, RelevantNewsStories stories, Horoscope horoscope,
            OperationContext context) { }

findNewsStories ๋ฉ”์„œ๋“œ๋ฅผ ๋ณด๋ฉด StarPerson๊ณผ Horoscope๋ฅผ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›๋Š”๋ฐ input์ด ๋ช‡ ๊ฐœ๊ฐ€ ๋˜๋“  ๊ฐ€๋Šฅํ•œ ์‹คํ–‰ ๊ฒฝ๋กœ๊ฐ€ ์žˆ๋‹ค๋ฉด Embabel์ด ์ •๊ตํ•˜๊ฒŒ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜์„ ํ•ด ์ค€๋‹ค. ์ž…๋ ฅ ๋ณต์žก๋„ ์ตœ๊ณ ๋ด‰์€ writeup๋ฉ”์„œ๋“œ์ด๋‹ค, ๋ธ”๋ž™๋ณด๋“œ + GOAP๋ฅผ ์ง€๋‹Œ Embabel์€ ๋ฌด์ ์ด์•  ๐Ÿซจ

 

๋”ฐ๋œปํ•œ ์•„์ด์Šค ์•„๋ฉ”๋ฆฌ์นด๋…ธ ํ•œ ์ž” ์ฃผ์„ธ์š”

๋†€๋ž๊ฒŒ๋„ ๊ทธ๋Ÿฐ ์ฃผ๋ฌธ์ด Embabel์—์„œ๋Š” ์ •๋ง๋กœ ๊ฐ€๋Šฅํ•˜๋‹ค๋А๋‡ฝ….

	var llm = LlmOptions.fromCriteria(ModelSelectionCriteria.getAuto())
            .withTemperature(0.9);

 

 

withTemperature๋กœ ai ์‘๋‹ต์— ๋Œ€ํ•œ ์˜ต์…˜์„ ์ค„ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์˜จ๋„๊ฐ€ ๋†’์„์ˆ˜๋ก ์กฐ๊ธˆ ๋” ์ฐฝ์˜์ ์ธ ๋‹ต๋ณ€์„ ์ค€๋‹ค๊ณ  ํ•œ๋‹ค.

์•„๋ž˜ ๋‘˜ ์ค‘ ํ•˜๋‚˜๋Š” ์˜จ๋„๋ฅผ 0.1๋กœ ์„ค์ •ํ•œ ๊ฑฐ๊ณ  ํ•˜๋‚˜๋Š” 1.0์œผ๋กœ ์„ค์ •ํ•˜๊ณ  ๋™์ผํ•œ ์งˆ๋ฌธ์„ ํ•œ ๊ฒƒ์ด๋‹ค. 

์–ด๋А ์ชฝ์ด 1.0์„ ์ž…๋ ฅํ•œ ๊ฑด์ง€๋Š”…์•Œ์•„์„œ ๋งžํ˜€ ๋ณด์„ธ์š”

 

+) ๋ฒˆ์™ธ, AI๋Š” ๋”ฐ๋œปํ•ด๋„ ์ฐจ๊ฐ‘๋‹ค

1+1์„ ๋ฌผ์–ด๋ดค์„ ๋•Œ ๋‹น์—ฐํžˆ ์ฐฝ์˜์ ์ธ ์ชฝ์€ ๊ท€์š”๋ฏธ๋ผ๊ณ  ๋Œ€๋‹ตํ•ด ์ค„ ๊ฒƒ์„ ๊ธฐ๋Œ€ํ–ˆ๋Š”๋ฐ… ๋‘˜์ด ๋˜‘๊ฐ™์ด ๋Œ€๋‹ตํ•ด์„œ ๊ต‰์žฅํžˆ ์‹ค๋ง์Šค๋Ÿฌ์› ๋‹ค (๋‚ด ํ† ํฐ ๋Œ๋ ค์ค˜) ์ธ๊ฐ„์ด ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค๋Š” ๋œ ์ฐฝ์˜์ ์ธ ๊ฒƒ ๊ฐ™๋‹ค.

 

@AchievesGoal

์ด๊ฑด Agent์˜ ์ตœ์ข… ๋ชฉํ‘œ๋ฅผ ์ •์˜ํ•˜๋Š” ํ•ต์‹ฌ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ, ์š”๋…€์„์ด ๋ถ™์€ ๋ฉ”์„œ๋“œ๋Š” ํ•ด๋‹น ์—์ด์ „ํŠธ๊ฐ€ ๋‹ฌ์„ฑํ•ด์•ผ ํ•  ๋ชฉ์ ์ง€์— ๋„๋‹ฌํ–ˆ์Œ์„ ํ”„๋ ˆ์ž„์›Œํฌ์—๊ฒŒ ์•Œ๋ฆฌ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

ํ˜น์‹œ๋‚˜ ๊ฐœ๋ฐœํ•˜๋‹ค ๋ชฉํ‘œ ์ง€์ •์„ ๋นผ๋จน์„๊นŒ๋ด ๊ฑฑ์ •ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค. ์™œ๋ƒํ•˜๋ฉด

์•ˆ ๋„ฃ์œผ๋ฉด Embabel์ด ์—ด์‹ฌํžˆ ํ™”๋‚ด์ค€๋‹ค ๐Ÿ”ฅ

๋„ค๊ฐ€ ๋„๊ตฌ๊นŒ์ง€ ์‚ฌ์šฉํ•˜๋ฉด ๋‚ด ๋ฐฅ๊ทธ๋ฆ‡์€…

์Šฌํ”„๊ฒŒ๋„ ๋ง์น˜๋ฅผ ์ฅ์—ฌ์ค˜๋„ ๋จธ๋ฆฌ๋งŒ ๊ธ์ ๊ฑฐ๋ฆฌ๋Š” ์›์ˆญ์ด๋“ค๊ณผ ๋‹ค๋ฅด๊ฒŒ, AI์—๊ฒŒ๋Š” ๋ง์น˜๋ผ๋Š” ๋„๊ตฌ๋ฅผ ์ฃผ๊ณ  ๊ทธ ์šฉ๋„๋ฅผ ์•Œ๋ ค์ฃผ๋ฉด ๋š๋”ฑ๋š๋”ฑ ์ง‘์„ ์ง“๋Š”๋‹ค.

๊ทธ๋Ÿฌ๋‹ˆ ์ง€๊ธˆ๋ถ€ํ„ฐ ๋ฏธ๋ฆฌ๋ฏธ๋ฆฌ ๊ธฐ๊ฐ•์„ ์žก์•„๋‘๋„๋ก ํ•˜์ž :)

        return context.ai().withDefaultLlm()
            .withToolGroup(CoreToolGroups.WEB)  
            .createObject(prompt, RelevantNewsStories.class);

์•„๋ฌดํŠผ ์ฝ”๋“œ ๋‚ด์—์„œ๋Š” ์ด๋Ÿฐ ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ๋„๊ตฌ๋ฅผ ์ง์ ‘ ์ง€์ •ํ•ด ์ค„ ์ˆ˜๋„ ์žˆ๋‹ค.

 

UserInput

Embabel์—์„œ๋Š” UserInput ํด๋ž˜์Šค๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค๋ช…ํ•œ๋‹ค.

UserInput์€ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ด ๋ฐ์ดํ„ฐ๋Š” ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์˜จ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์›์‹œ ์ž…๋ ฅ์ด๋ผ๋Š” ๊ฒƒ์„ ์ธ์‹ํ•˜๊ฒŒ ํ•˜๋Š” ํ•ต์‹ฌ ํƒ€์ž…์ด๋‹ค.

 

๐Ÿ’ฉ ์ž‘์€ ๊ณ ๋ฏผ ํฌ์ธํŠธ: ์ด๋ฏธ Request DTO๊ฐ€ ์žˆ๋Š”๋ฐ ๊ตณ์ด UserInput ํƒ€์ž…์œผ๋กœ ๋„˜๊ฒจ์ค˜์•ผ ํ• ๊นŒ?

์‚ฌ์‹ค ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋‹น์‹œ์—๋Š” ๋ณ„์ƒ๊ฐ ์—†์ด ์˜ˆ์‹œ์— ์žˆ๋˜ Agent์™€ ๋™์ผํ•˜๊ฒŒ UserInput์„ ๋„ฃ์–ด์ฃผ๋Š” ์‹์œผ๋กœ ๊ตฌํ˜„์„ ํ–ˆ๋Š”๋ฐ, Shell ๋ชจ๋“œ๋กœ ๊ตฌ๋™ํ•˜๋Š” Agent์ธ ๊ฒฝ์šฐ์—๋Š” ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์ด ๊ณ ์ •๋œ ํ…์ŠคํŠธ์ด๊ณ  ์–ด๋–ค Agent๊ฐ€ ํ•„์š”ํ• ์ง€ LLM์ด ์ง์ ‘ ๊ฐœ์ž…ํ•˜์—ฌ ํŒ๋‹จํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— UserInput์œผ๋กœ ๋„ฃ๋Š” ๊ฒƒ์ด ์ •์„์ด์—ˆ์ง€๋งŒ,  ์›น์œผ๋กœ ๊ตฌํ˜„ํ•œ ์„œ๋น„์Šค์— Agent๋ฅผ ๋ถ™์ผ ๋•Œ๋Š” Input์˜ ๊ทœ๊ฒฉ์ด ๋”ฑ ์ •ํ•ด์ ธ ์žˆ์–ด์„œ ๊ตณ์ด UserInput์„ ๋„˜๊ฒจ์ค˜์•ผ ํ•˜๋‚˜? ํ•˜๋Š” ์˜๋ฌธ์ด ๋“ค์—ˆ๋‹ค.

 

UserInput์€ UserContent๋ฅผ ์ƒ์†ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๊ฐ€๋“œ๋ ˆ์ผ์„ ์ ์šฉํ•  ๋•Œ, ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์˜จ ์ž…๋ ฅ์— ๋Œ€ํ•ด์„œ๋งŒ ์œ ํ•ด์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๊ธฐ์ค€์ด ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ UserInput์œผ๋กœ ๋„ฃ์–ด์ฃผ๋ฉด ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐ€๊ณต์„ ํ†ตํ•ด ๋„ฃ์€ ๊ฐ’์ด ์•„๋‹Œ ์ˆœ์ˆ˜ ์™ธ๋ถ€ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์ž„์„ ๋ช…์‹œํ•˜๊ณ  ๊ฒ€์ฆ์ด๋‚˜ ์ •์ œ๋ฅผ ๊ฑฐ์น˜๋„๋ก ์œ ๋„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

 

 

๊ทธ๋Ÿฌ๋‚˜ ๋ณ„๋‹ค๋ฅธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ๋ฉด AgentInvocation.invoke(myRequestDto)์™€ ๊ฐ™์€ ์‹์œผ๋กœ dto๋ฅผ ์ง์ ‘ ์ž…๋ ฅํ•ด๋„ ๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์ถ”์ธก์„ ํ•ด ๋ณด์ž๋ฉด Shell๋ชจ๋“œ์˜ ๊ฒฝ์šฐ ์ผ๋ฐ˜์ ์œผ๋กœ UserInput์ด ์ง„์ž…์ ์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ฒ˜์Œ๋ถ€ํ„ฐ UserInput์„ ๋ฐ›๋„๋ก ์—์ด์ „ํŠธ๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด Shell๊ณผ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋ชจ๋‘ ํ˜ธํ™˜๋˜์–ด ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

๊ตฌ๋ถ„ UserInput requestDto ์ง์ ‘ ์ž…๋ ฅ
์ž…๋ ฅ ํ˜•ํƒœ ์ž์œ ๋กœ์šด ํ…์ŠคํŠธ, ์„ค๋ช…, ๋ฉ”์‹œ์ง€ ํ•„๋“œ๊ฐ€ ์ •ํ•ด์ง„ ํผ ๋ฐ์ดํ„ฐ, API ์š”์ฒญ
์—์ด์ „ํŠธ ์—ญํ•  ํ…์ŠคํŠธ ๋ถ„์„ ๋ฐ ์ •๋ณด ์ถ”์ถœ (DICE) ์ถ”์ถœ๋œ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์—…๋ฌด ์ˆ˜ํ–‰
์ฃผ์š” ์žฅ์  OODA ๋ฃจํ”„์˜ ์ฒซ ๋‹จ๊ณ„๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ์‹œ์ž‘ ๋ถˆํ•„์š”ํ•œ LLM ์ถ”๋ก  ๋น„์šฉ ์ ˆ๊ฐ ๋ฐ ์ •ํ™•๋„ ํ–ฅ์ƒ
๋ณด์•ˆ UserContent ๊ธฐ๋ฐ˜ ๊ฐ€๋“œ๋ ˆ์ผ ์ž‘๋™ ๋‚ด๋ถ€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์œผ๋กœ ์ง์ ‘ ์ฃผ์ž…

 

์š”์•ฝํ•˜์ž๋ฉด ์ด๋ฏธ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ตฌ์กฐํ™”๋˜์–ด ์žˆ๋‹ค๋ฉด ์ง์ ‘ ๋„˜๊ฒจ๋„ ๋˜์ง€๋งŒ, ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ž์—ฐ์–ด๋ฅผ ๋ถ„์„ํ•˜์—ฌ ํŠน์ • ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ ์ถ”์ถœํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด UserInput์œผ๋กœ ๊ฐ์‹ธ์„œ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด Embabel์˜ ํƒ€์ž… ์•ˆ์ „์„ฑ๊ณผ ์ž๋™ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๊ธฐ๋Šฅ์„ ๊ฐ€์žฅ ์ž˜ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ฐœ๋ฐœ์—๋Š” ์ •๋ง ์ •๋‹ต์ด ์—†์–ด์š” ๐Ÿฅฒ

 

๐Ÿ“Œ  ์ดˆ์ดˆ์ดˆ๊ฐ„๋‹จ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋งŒ๋“ค์–ด ๋ณด๊ธฐ

์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค ๊ฒƒ์€ ๋ฐ”๋กœ๋ฐ”๋กœ

์š”์ฆ˜ AI ์ •๋ง ์ข‹๋„ค์š”

๋ฐ•๋ช…์ˆ˜๋‹ค.

 

์˜๊ฐ์„ ์–ป์€ ๊ฒƒ์€ ์Šคํฐ์ง€๋ฐฅ์— ๋‚˜์˜ค๋Š” ๋งˆ๋ฒ•์˜ ์†Œ๋ผ๊ณ ๋™์ด๋‹ค.

 

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์š”๊ตฌ ์‚ฌํ•ญ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์›ํ•˜๋Š” ๋งํˆฌ๋‚˜, ์„ฑ๊ฒฉ, ๋‹ต๋ณ€ ๊ทœ์น™์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  2. ์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ๋ฒ•์˜ ์†Œ๋ผ๊ณ ๋™์ฑ…์ฒ˜๋Ÿผ ๋ฏธ๋ฆฌ ๊ณ ์ •๋œ ๋‹ต๋ณ€์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  3. ๋ฏธ๋ฆฌ ์ง€์ •๋œ ๊ณ ์ • ๋‹ต๋ณ€๋“ค ์ค‘ ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต์ด ๋  ์ˆ˜ ์žˆ๋Š” ์ตœ์ ์˜ ํ›„๋ณด๋ฅผ ์ฐพ๊ณ , ์ตœ๊ณ ์ ์ด 0.7์„ ๋„˜๊ธฐ์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋งํˆฌ, ์„ฑ๊ฒฉ, ๋‹ต๋ณ€ ๊ทœ์น™์— ๋”ฐ๋ผ ์ง์ ‘ ๋‹ต๋ณ€์„ ์ƒ์„ฑํ•œ๋‹ค.

๋Œ€์ถฉ ๋ฐ•๋ช…์ˆ˜๋ฅผ ํ‰๋‚ด๋‚ผ ํŽ˜๋ฅด์†Œ๋‚˜๋ฅผ ์„ค๊ณ„ํ•ด ์ค€๋‹ค:

@Builder
public record PersonaContext(
    String name,
    String description,
    String tone,
    String rules,
    List<String> quotes,
    List<StyleExampleContext> styleExamples
) { }

 

๊ทธ๋ฆฌ๊ณ  ์š”๊ตฌ ์‚ฌํ•ญ์„ ๋งŒ์กฑํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ–‰๋™๋“ค์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค:

- ํŽ˜๋ฅด์†Œ๋‚˜ ์ •๋ณด ์กฐํšŒ (์ž…๋ ฅ: ํŽ˜๋ฅด์†Œ๋‚˜ ์‹๋ณ„์ž, ์ถœ๋ ฅ: ํŽ˜๋ฅด์†Œ๋‚˜ ์ •๋ณด)
- ๊ณ ์ • ๋‹ต๋ณ€ ๊ธฐ๋ฐ˜ ์‘๋‹ต ์ƒ์„ฑ (์ž…๋ ฅ: ํŽ˜๋ฅด์†Œ๋‚˜ ์ •๋ณด, ์‚ฌ์šฉ์ž ์งˆ๋ฌธ, ์ถœ๋ ฅ: ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์‘๋‹ต)
- ํŽ˜๋ฅด์†Œ๋‚˜ ๊ธฐ๋ฐ˜ ์‘๋‹ต ์ƒ์„ฑ (์ž…๋ ฅ: ํŽ˜๋ฅด์†Œ๋‚˜ ์ •๋ณด, ์‚ฌ์šฉ์ž ์งˆ๋ฌธ, ์ถœ๋ ฅ: ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์‘๋‹ต)

 

์šฐ์„  MagicConchAgent๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๊ณ  ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•ด ์ค€๋‹ค.

@Slf4j
@RequiredArgsConstructor
@Agent(description = "Generate answer for user's question")
public class MagicConchAgent {}

 

ํŽ˜๋ฅด์†Œ๋‚˜ ์ •๋ณด ์กฐํšŒ Action์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜ํ•œ๋‹ค ๐Ÿ’จ

    @Transactional
    @Action
    public PersonaContext searchContext(Long personaId) {
        Persona persona = personaRepository.findById(personaId)
                                           .orElseThrow();

        return PersonaContext.fromEntity(persona);
    }

 

๊ทธ๋ฆฌ๊ณ  ๊ณ ์ • ๋‹ต๋ณ€ ๊ธฐ๋ฐ˜ ์‘๋‹ต ์ƒ์„ฑ Action๊ณผ  ํŽ˜๋ฅด์†Œ๋‚˜ ๊ธฐ๋ฐ˜ ์‘๋‹ต ์ƒ์„ฑ Action์„ ๊ฐ๊ฐ ๊ตฌํ˜„ํ•œ๋‹ค.

    @Action
    public QuoteBasedAnswer createQuoteBasedAnswer(
        PersonaContext personaContext,
        String question,
        Ai ai
    ) {
        if (personaContext.quotes().isEmpty()) {
            return new QuoteBasedAnswer(null, 0);
        }

        String quotesText = personaContext.buildQuotesText();
        String prompt = QUOTE_BASED_PROMPT.formatted(question, quotesText);

        QuoteScoreList result = ai
            .withAutoLlm()
            .createObject(prompt, QuoteScoreList.class);

        QuoteScoreResult best = result.scores()
                                      .stream()
                                      .max((a, b) -> Double.compare(a.score(), b.score()))
                                      .orElseThrow();


        if (best.quoteIndex() <= 0 || best.quoteIndex() > personaContext.quotes().size()) {
            return new QuoteBasedAnswer(null, 0);
        }

        String bestQuote = personaContext.quotes().get(best.quoteIndex() - 1);

        return new QuoteBasedAnswer(bestQuote, best.score());
    }

 

    @Action
    public Optional<PersonaBasedAnswer> createPersonBasedAnswer(
        PersonaContext personaContext,
        QuoteBasedAnswer quoteBasedAnswer,
        String question,
        Ai ai
    ) {
        if (quoteBasedAnswer.score >= 0.7) {
            return Optional.empty();
        }

        String examples = personaContext.buildStyleExamplesText();

        String prompt = PERSONA_BASED_PROMPT.formatted(
            personaContext.name(),
            personaContext.description(),
            personaContext.tone(),
            personaContext.rules(),
            examples,
            question
        );

        String answer = ai.withAutoLlm().createObject(prompt, String.class);
        return Optional.of(new PersonaBasedAnswer(answer));
    }

 

๊ทธ๋Ÿฌ๋ฉด Optional<PersonaBasedAnswer>์™€ QuoteBasedAnswer๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š” ์ตœ์ข… Action์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

    @AchievesGoal(description = "answer user's question.")
    @Action
    public ConchAnswerResult conchAnswerResult(
        QuoteBasedAnswer quoteBasedAnswer,
        Optional<PersonaBasedAnswer> personaBasedAnswer
    ) {
        String answer = personaBasedAnswer.map(PersonaBasedAnswer::answer)
                                          .orElse(quoteBasedAnswer.answer());
        return new ConchAnswerResult(answer);
    }

 

์ฝ”๋“œ์—์„œ ๋ƒ„์ƒˆ๊ฐ€ ๋‚œ๋‹ค๊ณ  ๋А๊ผˆ๋‹ค๋ฉด ์•„์ฃผ ์ •ํ™•ํ•˜๋‹ค… ๐Ÿง€๐Ÿง€

๐Ÿ˜’ Embabel ๊ทธ๋ ‡๊ฒŒ ์“ฐ๋Š” ๊ฑฐ ์•„๋‹Œ๋ฐ

์†Œ๊ฐœ์—์„œ ๋งํ–ˆ๋“ฏ ์—์ด์ „ํ‹ฑ AI์˜ ํ•ต์‹ฌ์€ ์˜ˆ์™ธ ์ƒํ™ฉ์ด๋‚˜ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ๋Œ€์‘ ๋Šฅ๋ ฅ์ด๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ์—์„œ quoteBasedAnswer.score >= 0.7 ์กฐ๊ฑด์„ ์ฒดํฌํ•˜์—ฌ ๋‹ต๋ณ€์„ ์ƒ์„ฑํ• ์ง€ ๋ง์ง€ ๊ฒฐ์ •ํ•˜๊ณ  ์žˆ์ง€๋งŒ, ๋งŒ์•ฝ ์ƒ์„ฑ๋œ ๋‹ต๋ณ€๋งˆ์ € ํ’ˆ์งˆ์ด ๋‚ฎ์•„ ๋‹ค์‹œ ์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๋ฉด ์ผ๋ฐ˜์ ์ธ ์•ก์…˜ ๊ตฌ์กฐ๋กœ๋Š” ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋งค์šฐ ๊นŒ๋‹ค๋กญ๋‹ค. 

 

https://docs.embabel.com/embabel-agent/guide/0.3.5-SNAPSHOT/#reference.states

 

Embabel Agent Framework User Guide

Agentic AI is the use of large language (and other multi-modal) models not just to generate text, but to act as reasoning, goal-driven agents that can plan, call tools, and adapt their actions to deliver outcomes. The JVM is a compelling platform for this

docs.embabel.com

 

๊ทธ๋ž˜์„œ ๊ฐ€์ด๋“œ ๋ฌธ์„œ์— ๋ช…์‹œ๋œ๋Œ€๋กœ Embabel์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค์„ ์‚ฌ์šฉํ•˜๋ฉด ์•„์ฃผ ์‰ฝ๊ฒŒ ๋ฃจํ”„๋ฅผ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋Œ€์ถฉ ํ๋ฆ„์„ ๋จผ์ € ๊ทธ๋ ค ๋ณด๋ฉด ์ด๋Ÿฐ ๋А๋‚Œ์ด๋‹ค.

๊ณ ์ • ๋‹ต๋ณ€ ๊ธฐ๋ฐ˜์œผ๋กœ ์ ์ ˆํ•œ ์‘๋‹ต์„ ์ฐพ๊ณ , ์ ์ ˆํ•œ ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ์ ํ•ฉ๋„๊ฐ€ 0.7 ์ด์ƒ์ธ ๋‹ต๋ณ€์ด ๋‚˜์˜ฌ ๋•Œ๊นŒ์ง€ AI ์žก๋„๋ฆฌ๋ฅผ ํ•˜๋Š” ๊ตฌ์กฐ์ด๋‹ค๐Ÿ‘€

 

1. ๊ณตํ†ต ์ธํ„ฐํŽ˜์ด์Šค

@State
public interface MagicConchState {
}

 

2. ๊ณ ์ • ๋‹ต๋ณ€ ๊ธฐ๋ฐ˜ ์‘๋‹ต ์ƒ์„ฑ ๋‹จ๊ณ„

public record SearchingQuote(
    PersonaContext context,
    String question
) implements MagicConchState {


    @Action
    public MagicConchState searchFromQuotes(Ai ai) {

        if (context.quotes().isEmpty()) {
            return new GeneratingAnswer(context, question, "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฟผํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.");
        }

        QuoteScoreList result = ai.withAutoLlm()
                                  .createObject(
                                      QUOTE_BASED_PROMPT.formatted(
                                          question,
                                          context.buildQuotesText()
                                      ),
                                      QuoteScoreList.class
                                  );

        QuoteScoreResult best = result.scores().stream()
                                      .max(Comparator.comparingDouble(QuoteScoreResult::score))
                                      .orElseThrow();

        if (best.score() >= 0.7) {
            String quote = context.quotes().get(best.quoteIndex() - 1);
            return new FinalAnswer(quote);
        }

        return new GeneratingAnswer(context, question,
            "์ ์ ˆํ•œ ์ฟผํŠธ๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค (์ตœ๊ณ ์ : " + best.score() + ")");
    }


    record QuoteScoreResult(
        int quoteIndex,
        double score
    ) {
    }

    record QuoteScoreList(
        List<QuoteScoreResult> scores
    ) {
    }
}

 

3. ํŽ˜๋ฅด์†Œ๋‚˜ ๊ธฐ๋ฐ˜ ์‘๋‹ต ์ง์ ‘ ์ƒ์„ฑ ๋‹จ๊ณ„

public record GeneratingAnswer(
    PersonaContext context,
    String question,
    String reason
) implements MagicConchState {

    @Action
    public EvaluatingAnswer generate(Ai ai) {

        String prompt = PERSONA_BASED_PROMPT.formatted(
            context.name(),
            context.description(),
            context.tone(),
            context.rules(),
            context.buildStyleExamplesText(),
            question
        );

        String generated = ai.withAutoLlm()
                             .withLlm(LlmOptions.withAutoLlm().withTemperature(0.8))
                             .createObject(prompt, String.class);

        return new EvaluatingAnswer(context, question, generated);
    }
}

 

4. ๋‹ต๋ณ€ ํ‰๊ฐ€ ๋‹จ๊ณ„

@Slf4j
public record EvaluatingAnswer(
    PersonaContext context,
    String question,
    String candidateAnswer
) implements MagicConchState {

    @Action(clearBlackboard = true)
    public MagicConchState evaluate(Ai ai) {

        Double qualityScore = ai.withAutoLlm()
                                .createObject(
                                    "๋‹ค์Œ ๋‹ต๋ณ€์ด ํŽ˜๋ฅด์†Œ๋‚˜ ์„ค์ •์— ์–ผ๋งˆ๋‚˜ ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ์ ์ˆ˜(0.0~1.0)๋งŒ ์ถœ๋ ฅํ•ด์ค˜: "
                                        + candidateAnswer,
                                    Double.class
                                );

        if (qualityScore >= 0.7) {
            return new FinalAnswer(candidateAnswer);
        }

        log.info("[MagicConch] ํ’ˆ์งˆ ๋ฏธ๋‹ฌ({}), ๋‹ค์‹œ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.", qualityScore);

        return new GeneratingAnswer(context, question, "ํ’ˆ์งˆ ํ‰๊ฐ€ ์‹คํŒจ");
    }
}

โ—๏ธ์—ฌ๊ธฐ์„œ @Action์— clearBlackboard=true๋ผ๋Š” ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ด๊ฒŒ ์—์ด์ „ํŠธ๊ฐ€ ๋ฃจํ”„๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š” ํ•ต์‹ฌ ์žฅ์น˜์ด๋‹ค.

(๊ทผ๋ฐ ์ง„์งœ blackboard๋ผ๊ณ  ํ•œ ๊ฑฐ ์ง„์งœ ๋ ˆ์ „๋“œ ์ฐฐ๋–ก์ฝฉ๋–ก ๋„ค์ด๋ฐ…)

 

1. ์ค‘๋ณต ํƒ€์ž…์— ์˜ํ•œ ์‹คํ–‰ ์ฐจ๋‹จ ๋ฐฉ์ง€ ์—ญํ• 

Emababel์˜ GOAP ํ”Œ๋ž˜๋„ˆ๋Š” ๋ธ”๋ž™๋ณด๋“œ์— ํŠน์ • ํƒ€์ž…์˜ ๊ฐ์ฒด๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•˜๋ฉด, ํ•ด๋‹น ๋ชฉํ‘œ๊ฐ€ ์ด๋ฏธ ๋‹ฌ์„ฑ๋˜์—ˆ๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ๊ทธ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์•ก์…˜์„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์ง€ ์•Š์œผ๋ ค๊ณ  ํ•œ๋‹ค. 

 

๋งŒ์•ฝ clearBlackboard๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๋‘ ๋ฒˆ์งธ ๋ฃจํ”„์—์„œ ํ”Œ๋ž˜๋„ˆ๋Š” ๋ธ”๋ž™๋ณด๋“œ์— ์ด๋ฏธ ์ด์ „ ๋‹จ๊ณ„์˜ GeneratingAnswer๋‚˜ EvaluatingAnswer ๊ฐ์ฒด๊ฐ€ ๋‚จ์•„ ์žˆ๋Š” ๊ฒƒ์„ ๋ณด๊ณ , ์ด๋ฏธ ํ•ด๋‹น ๋‹จ๊ณ„๋“ค์„ ๊ฑฐ์ณค์œผ๋‹ˆ ๋‹ค์‹œ ์ƒ์ƒํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋ฉˆ์ถ”๊ฑฐ๋‚˜ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

 

clearBlackboard=true๋ฅผ ์„ค์ •ํ•˜๋ฉด ์•ก์…˜์˜ ๊ฒฐ๊ณผ๋ฌผ(์ด ๊ฒฝ์šฐ ์ƒˆ๋กœ์šด GeneratingAnswer ๋˜๋Š” FinalAnswer ๊ฐ์ฒด)๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ๋ชจ๋“  ์ค‘๊ฐ„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ธ”๋ž™๋ณด๋“œ์—์„œ ์ œ๊ฑฐ๋˜๋ฏ€๋กœ, ํ”Œ๋ž˜๋„ˆ๊ฐ€ ์šฐ๋ฆฌ์˜ ์˜๋„๋Œ€๋กœ ๋‹ค์‹œ ๊ณ„ํš์„ ์ˆ˜๋ฆฝํ•˜๋Š” ์œ ์—ฐํ•œ ๊ตฌ์กฐ์ด๋‹ค.

 

2. ์‹คํ–‰ ์ด๋ ฅ(hasRun) ๋ฆฌ์…‹

Embabel์€ ์•ก์…˜์˜ ๋ฌดํ•œ ๋ฐ˜๋ณต์„ ๋ง‰๊ธฐ ์œ„ํ•ด ๊ฐ ์•ก์…˜์˜ ์‹คํ–‰ ์—ฌ๋ถ€๋ฅผ hasRun์ด๋ผ๋Š” ์กฐ๊ฑด์œผ๋กœ ์ถ”์ ํ•œ๋‹ค. ํ‰๊ฐ€ ๊ฒฐ๊ณผ๊ฐ€ ๊ธฐ์ค€ ๋ฏธ๋‹ฌ์ด์–ด์„œ ๋‹ค์‹œ ์ƒ์„ฑ ๋‹จ๊ณ„๋กœ ๋Œ์•„๊ฐ€์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ์ด์ „์— ์‹คํ–‰ํ–ˆ๋˜ generate ์•ก์…˜์„ ๋‹ค์‹œ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋•Œ ๋ธ”๋ž™๋ณด๋“œ๋ฅผ ๋น„์›Œ์ฃผ๋ฉด hasRun ์ถ”์  ์กฐ๊ฑด๋“ค๋„ ํ•จ๊ป˜ ์ œ๊ฑฐ๋˜์–ด์„œ ํ”Œ๋ž˜๋„ˆ๊ฐ€ ์ด์ „์— ์‹คํ–‰ํ–ˆ๋˜ ์•ก์…˜๋“ค์„ ๋‹ค์‹œ ์‹คํ–‰ ๊ฒฝ๋กœ์— ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค ๐Ÿ‘

 

 

5. ์ตœ์ข… ๋‹ต๋ณ€ ํ™•์ • ๋‹จ๊ณ„

public record FinalAnswer(
    String answer
) implements MagicConchState {

    @AchievesGoal(description = "์‚ฌ์šฉ์ž ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์ตœ์ข… ๋‹ต๋ณ€ ํ™•์ •")
    @Action
    public ConchAnswerResult finalizeAnswer() {
        return new ConchAnswerResult(answer);
    }
}

 

๋งˆ์ง€๋ง‰์œผ๋กœ ์ง„์ž…์ ์ด ๋  Agent๋ฅผ ์ •์˜ํ•œ๋‹ค:

@Agent(description = "Generate answer for user's question")
@RequiredArgsConstructor
public class MagicConchAgent {

    private final PersonaRepository personaRepository;

    @Action
    @Transactional
    public SearchingQuote start(Long personaId, String question) {
        Persona persona = personaRepository.findById(personaId).orElseThrow();
        return new SearchingQuote(PersonaContext.fromEntity(persona), question);
    }
}

 

๊ทผ๋ฐ ์—ฌ๊ธฐ์„œ ์ž ๊น

๋ถ„๋ช… AchievesGoal์ด ์—†๋Š” Agent๋Š” ์ด๋นจ ๋น ์ง„ ํ˜ธ๋ž‘์ด, ํŠ€๊น€์˜ท ์—†๋Š” ์น˜ํ‚จ, ๋ฆฌ๋ฐ”์ด ์—†๋Š” ์กฐ์‚ฌ๋ณ‘๋‹จ, ์ฃผ๋”” ์—†๋Š” ๋‹‰์ด๋žฌ๋Š”๋ฐ…

 

 

์ถฉ๊ฒฉ AchievesGoal์ด ์—†๋Š” Agent ์‹ค์กด!

์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:


Agent์˜ ๋ฐ˜ํ™˜๊ฐ’์ธ SearchingQuote๋Š” MagicConchState๋ฅผ ๊ตฌํ˜„ํ•˜๋Š”๋ฐ, MagicConchState๋Š” @State ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— MagicConchState์˜ ๊ตฌํ˜„์ฒด๋“ค ์ค‘ FinalAnswer๊ฐ€ @AchievesGoal์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉด Embabel์€ ์•Œ์•„์„œ ๊ฒฝ๋กœ๋ฅผ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ˜ธ๊ธฐ์‹ฌ์— MagicConchState์—์„œ @State ์–ด๋…ธํ…Œ์ด์…˜์„ ์ œ๊ฑฐํ•˜๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•ด ๋ดค๋Š”๋ฐ

Embabel์ด ๋ฐ”๋กœ ํ™”๋ฅผ ๋ƒˆ๋‹ค ๐Ÿ˜ฅ  

 

๋ฌผ๋ก  ๋ชจ๋“  Action์„ Agent์— ๋•Œ๋ ค๋„ฃ๊ณ  ํƒ€์ž… ๊ธฐ๋ฐ˜ ์ถ”์ ์œผ๋กœ @State ์—†์ด๋„ ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•˜๋‹ค. (์•„๋ž˜๋Š” ์ดˆ๋ฐ˜์— ๋ฌธ์„œ๋ฅผ ๋Œ€์ถฉ ์ฝ๊ณ  ๋ƒ…๋‹ค ๊ตฌํ˜„๋ถ€ํ„ฐ ํ•˜๋Š” ๋ฐ”๋žŒ์— ํƒ„์ƒํ•œ ๋ฆฌํŒฉํ† ๋ง ์ „ ์ฝ”๋“œ์ด๋‹ค. state record๋Š” ๊ฑฐ๋‘˜ ๋ฟ… ๋‚˜๋Š” ๋ชจ๋“  ์ฑ…์ž„์„ MagicConchAgent์—๊ฒŒ ์œ„์ž„ํ•œ๋‹ค)

@Slf4j
@RequiredArgsConstructor
@Agent(description = "Generate answer for user's question")
public class MagicConchAgent {

    private final PersonaRepository personaRepository;

    @Transactional
    @Action
    public SearchingQuote initContext(Long personaId, String question) {
        Persona persona = personaRepository.findById(personaId).orElseThrow();
        return new SearchingQuote(PersonaContext.fromEntity(persona), question);
    }

    @Action
    public MagicConchState searchFromQuotes(SearchingQuote search, Ai ai) {
        PersonaContext ctx = search.context();
        if (ctx.quotes().isEmpty()) {
            return new GeneratingAnswer(ctx, search.question(), "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฟผํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.");
        }

        QuoteScoreList result = ai.withAutoLlm()
                                  .createObject(QUOTE_BASED_PROMPT.formatted(search.question(), ctx.buildQuotesText()), QuoteScoreList.class);

        QuoteScoreResult best = result.scores().stream()
                                      .max(Comparator.comparingDouble(QuoteScoreResult::score))
                                      .orElseThrow();

        if (best.score() >= 0.7) {
            String quote = ctx.quotes().get(best.quoteIndex() - 1);
            return new FinalAnswer(quote); // ๋ฐ”๋กœ ๋ชฉํ‘œ ํƒ€์ž… ๋ฐ˜ํ™˜ ๊ฐ€๋Šฅ
        }

        return new GeneratingAnswer(ctx, search.question(), "์ ์ ˆํ•œ ์ฟผํŠธ๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค (์ตœ๊ณ ์ : " + best.score() + ")");
    }

    @Action
    public EvaluatingAnswer generateByPersona(GeneratingAnswer generating, Ai ai) {
        PersonaContext ctx = generating.context();
        String prompt = PERSONA_BASED_PROMPT.formatted(
            ctx.name(), ctx.description(), ctx.tone(), ctx.rules(), ctx.buildStyleExamplesText(), generating.question()
        );

        String generated = ai.withAutoLlm()
                             .withLlm(LlmOptions.withAutoLlm().withTemperature(0.8)) // ์ƒ์„ฑ ์‹œ ์ฐฝ์˜์„ฑ ๋ถ€์—ฌ
                             .createObject(prompt, String.class);

        return new EvaluatingAnswer(ctx, generating.question(), generated);
    }

    @Action(clearBlackboard = true) // ๋ฃจํ”„๋ฅผ ์œ„ํ•ด ์ด์ „ ์ƒํƒœ๋“ค์„ ๋น„์›€
    public MagicConchState evaluateGeneratedAnswer(EvaluatingAnswer evaluation, Ai ai) {
        Double qualityScore = ai.withAutoLlm().createObject(
            "๋‹ค์Œ ๋‹ต๋ณ€์ด ํŽ˜๋ฅด์†Œ๋‚˜ ์„ค์ •์— ์–ผ๋งˆ๋‚˜ ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ์ ์ˆ˜(0.0~1.0)๋งŒ ์ถœ๋ ฅํ•ด์ค˜: " + evaluation.candidateAnswer(),
            Double.class
        );

        if (qualityScore >= 0.7) {
            return new FinalAnswer(evaluation.candidateAnswer());
        }

        log.info("[MagicConch] ํ’ˆ์งˆ ๋ฏธ๋‹ฌ({}), ๋‹ค์‹œ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.", qualityScore);
        return new GeneratingAnswer(evaluation.context(), evaluation.question(), "์ด์ „ ์ƒ์„ฑ ๋‹ต๋ณ€์˜ ํ’ˆ์งˆ์ด ๋‚ฎ์Œ");
    }


    @AchievesGoal(description = "์‚ฌ์šฉ์ž ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์ตœ์ข… ๋‹ต๋ณ€ ํ™•์ •")
    @Action
    public ConchAnswerResult finalize(FinalAnswer result) {
        return new ConchAnswerResult(result.answer);
    }

    // โ—๏ธโ—๏ธโ—๏ธ @State ์–ด๋…ธํ…Œ์ด์…˜ ์ œ๊ฑฐ โ—๏ธโ—๏ธโ—๏ธ
    public interface MagicConchState {
    }
}

 

๋ฌผ๋ก  ์ด๊ฒŒ ๋ณด๊ธฐ ์ข‹์€ ์ฝ”๋“œ๋„ ์•„๋‹ˆ๊ณ  ์œ ์ง€๋ณด์ˆ˜๋ฅผ ํ•˜๊ธฐ์— ์ข‹์€ ์ฝ”๋“œ๋Š” ๋”๋”์šฑ ์•„๋‹ˆ๊ธด ํ•˜์ง€๋งŒ, ์–ด์จŒ๋“  ์ด๋ ‡๊ฒŒ ๊ฐœ๋–ก ๊ฐ™์ด ์งœ๊ณ  ์‹คํ–‰์‹œ์ผœ๋„ ๋˜‘๋˜‘ํ•œ Embabel์ด ์ž…๋ ฅ ํƒ€์ž…๊ณผ ์ถœ๋ ฅ ํƒ€์ž… ๋งคํ•‘์œผ๋กœ ๊ธฐ๋Šฅ์ด ์ž˜ ๋Œ์•„๊ฐ€์„œ @State๋ฅผ ์™œ ์จ์•ผ ํ•˜๋Š”์ง€? ์— ๋Œ€ํ•œ ์˜๋ฌธ์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ƒ๊ฒผ๋‹ค. (์ฝ”๋“œ๋กœ ๋˜ฅ์„ ์ŒŒ์ง€๋งŒ ๋” ๊นŠ์€ ํ•™์Šต์„ ํ•  ์ˆ˜ ์žˆ๋‹ค๋‹ˆ ์™„์ „ ๋Ÿญํ‚ค๋น„ํ‚ค๋‹ˆ์‹œํ‹ฐ์ž๋‚˜๐Ÿงฝ)

 

1. ์ƒํƒœ ์Šค์ฝ”ํ•‘

@State๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฐ€์žฅ ๊ฒฐ์ •์ ์ธ ์ด์œ ๋Š” ๊ฒฝ๋กœ ๊ฒฉ๋ฆฌ์ด๋‹ค. @State๊ฐ€ ์—†์œผ๋ฉด ํ”Œ๋ž˜๋„ˆ๋Š” ๋งค ๋‹จ๊ณ„๋งˆ๋‹ค ์—์ด์ „ํŠธ์— ์ •์˜๋œ ๋ชจ๋“  ์•ก์…˜์„ ๊ฒ€ํ† ํ•˜์—ฌ ๋‹ค์Œ ๊ฒฝ๋กœ๋ฅผ ์ฐพ๋Š”๋‹ค. ๋ฌธ์ œ๋Š” ์•ก์…˜์ด 10๊ฐœ, 20๊ฐœ๋กœ ๋Š˜์–ด๋‚˜๋ฉด ํ”Œ๋ž˜๋„ˆ๊ฐ€ ์—‰๋šฑํ•œ ์•ก์…˜์„ ์„ ํƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์ƒ๊ธฐ๊ณ , ํŒ๋‹จ์— ๊ฑธ๋ฆฌ๋Š” ๋น„์šฉ ๋ฌธ์ œ๋„ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์•ก์…˜์ด @State ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ํ”Œ๋ž˜๋„ˆ๋Š” ํ•ด๋‹น ์ƒํƒœ ํด๋ž˜์Šค ๋‚ด๋ถ€์— ์ •์˜๋œ ์•ก์…˜๋“ค๋งŒ ์‹คํ–‰ ํ›„๋ณด๋กœ ๊ณ ๋ฅด๋ ค๊ณ  ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒํƒœ ์Šค์ฝ”ํ•‘์ด ์•„์ฃผ ํฐ ์žฅ์ ์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

2. ์ƒํƒœ ์ˆจ๊น€

@State๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ƒํƒœ ์ „์ด ์‹œ ์ด์ „ ์ƒํƒœ ๊ฐ์ฒด๋“ค์„ ์ž๋™์œผ๋กœ ์ˆจ๊น€์ฒ˜๋ฆฌํ•œ๋‹ค. ์ด์ „ ์ƒํƒœ๋Š” ๋ธ”๋ž™๋ณด๋“œ history์—๋Š” ๋‚จ์ง€๋งŒ, ํ”Œ๋ž˜๋„ˆ์˜ ๋ˆˆ์—๋Š” ๋ณด์ด์ง€ ์•Š๊ฒŒ ๋˜์–ด์„œ ๋‹ค์Œ ๋‹จ๊ณ„์˜ ์˜์‚ฌ ๊ฒฐ์ •์„ ๋ฐฉํ•ดํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰, ํ•˜๋‚˜์˜ State ๊ตฌํ˜„์ฒด๋งŒ์ด ํ”Œ๋ž˜๋„ˆ์˜ ์˜์‚ฌ ๊ฒฐ์ •์— ๊ด€์—ฌํ•˜๊ฒŒ ๋œ๋‹ค.

 

3. ์œ ์ง€๋ณด์ˆ˜์™€ ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ

๋ชจ๋“  ๋กœ์ง์„ ์—์ด์ „ํŠธ์— ๋•Œ๋ ค ๋„ฃ์œผ๋ฉด ์ฝ”๋“œ๊ฐ€ ๋น„๋Œ€ํ•ด์ง€๊ณ  ๊ฐ ๋‹จ๊ณ„ ๊ฐ„์˜ ๊ฒฝ๊ณ„๊ฐ€ ๋ชจํ˜ธํ•ด์ง„๋‹ค. ์ž˜ ๋งŒ๋“ค์–ด์ง„ EvaluatingAnswer ์ƒํƒœ ํด๋ž˜์Šค๋Š” ๋‹ค๋ฅธ ์—์ด์ „ํŠธ์—์„œ๋„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ๋‹ค ์“ฐ๊ฑฐ๋‚˜ ์ƒ์†๋ฐ›์•„ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ธฐ ๋งค์šฐ ์šฉ์ดํ•œ ๊ตฌ์กฐ๊ฐ€ ๋œ๋‹ค.

 

โš’๏ธ ๋„๊ตฌ ์ฅ์—ฌ์ฃผ๊ธฐ

๋˜‘๋˜‘ํ•ด์ง€์ง€ ๋งˆ, ์•„๋‹ˆ ๋˜‘๋˜‘ํ•ด์ ธ, ์•„๋‹ˆ ๋˜‘๋˜‘ํ•ด์ง€์ง€ ๋งˆ, ๋‚ด ๋ฐฅ๊ทธ๋ฆ‡ ๋นผ์•—์ง€ ์•Š์„ ๋งŒํผ๋งŒ ๋˜‘๋˜‘ํ•ด์ ธ

 

Embabel ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๋„๋ฉ”์ธ ๊ฐ์ฒด๋“ค์€ ๋‹จ์ˆœํ•œ ๋ฐ์ดํ„ฐ ๋ฉ์–ด๋ฆฌ๊ฐ€ ์•„๋‹ˆ๋‹ค. ์„ ํƒ์ ์œผ๋กœ ๋„๋ฉ”์ธ์ด ํ•  ์ˆ˜ ์žˆ๋Š” ํ–‰๋™์„ LLM์—๊ฒŒ ์•Œ๋ ค์ฃผ์–ด์„œ ์—์ด์ „ํŠธ๊ฐ€ Action์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋™์•ˆ ๋„๋ฉ”์ธ๊ณผ ์ƒํ˜ธ์ž‘์šฉ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ํŠน์ง•์ด ์žˆ๋‹ค.

 

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ธํ„ฐ๋ทฐ ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ ˆ์ฝ”๋“œ๊ฐ€ ์žˆ์„ ๋•Œ, ์ธํ„ฐ๋ทฐ์— ๋Œ€ํ•œ ์–ด๋– ํ•œ ์š”์•ฝ ์ •๋ณด๋‚˜ ๋‹ค์Œ ์งˆ๋ฌธ ์ถ”์ฒœ์„ ๋ฐ›๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ ์–ด๋–ค ์ •๋ณด๋ฅผ ๋„ฃ์–ด์ค˜์•ผ ํ• ๊นŒ?

public record InterviewContext(
    CandidateAnalysisResult candidateAnalysis,
    PositionAnalysisResult positionAnalysis,
    List<EvaluationCategoryResult> evaluationCategories,
    List<String> checklist,
    List<AskedQuestionResult> history
) {
}

 

์ •ํ•ด์ง„ ๋‹ต์€ ์—†๋‹ค. ํ˜„์žฌ ์ธํ„ฐ๋ทฐ์˜ ์ง„ํ–‰ ์ƒํ™ฉ์ด๋‚˜, ์ง€์›์ž์˜ ํŠน์ง•, ๊ณต๊ณ ์˜ ํŠน์ง• ๋“ฑ์— ๋”ฐ๋ผ ์–ด๋–ค ์ •๋ณด๋Š” AI์˜ ์˜์‚ฌ ๊ฒฐ์ •์— ๋„์›€์ด ๋˜๊ณ  ์–ด๋–ค ์ •๋ณด๋Š” ์•„๋‹ ๊ฒƒ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฉด์ ‘ ์งˆ๋ฌธ์„ ์ถ”์ฒœํ•ด ๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ–ˆ์„ ๊ฒฝ์šฐ ๋ฉด์ ‘๊ด€์˜ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์ง€์›์ž์˜ ๋งˆ์ง€๋ง‰ ๋‹ต๋ณ€์ด ์—ฐ์ด์€ ๊ผฌ๋ฆฌ ์งˆ๋ฌธ์„ ์ƒ์„ฑํ•˜๊ธฐ ์–ด๋ ค์šด ๋‹ต๋ณ€์ผ ๊ฒฝ์šฐ (ex. ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.), AI๋Š” ์ถ”๊ฐ€ ์งˆ๋ฌธ ์ƒ์„ฑ์„ ์œ„ํ•ด ์ „์ฒด ๊ณต๊ณ ์™€ ์ง€์›์ž์˜ ์š”์•ฝ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€๋กœ ํ•„์š”๋กœ ํ•  ๊ฒƒ์ด๊ณ , ๋ฐ˜๋Œ€๋กœ ๋งˆ์ง€๋ง‰ ๋ฌธ๋‹ต์ด ๊ผฌ๋ฆฌ ์งˆ๋ฌธ์„ ์ƒ์„ฑํ•˜๊ธฐ์— ์ ํ•ฉํ•œ ๊ฒฝ์šฐ์—๋Š” ๊ตณ์ด ์ „์ฒด ๋ฉด์ ‘ ์ปจํ…์ŠคํŠธ๋ฅผ ํ™•์ธํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. LLM์€ ๋˜‘๋˜‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํŒ๋‹จ ๊ณผ์ •์—์„œ ์ž์‹ ์ด ์–ด๋–ค ์ •๋ณด๊ฐ€ ๋” ๋ถ€๊ฐ€์ ์œผ๋กœ ํ•„์š”ํ•œ์ง€, ํ˜น์€ ํ˜„์žฌ ์ •๋ณด๋กœ๋„ ์ถฉ๋ถ„ํžˆ ๋งŒ์กฑ์Šค๋Ÿฌ์šด ๋‹ต๋ณ€์„ ๋ฑ‰์„ ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๋Š” InterviewContext์— ์žˆ๋Š” ๋ชจ๋“  ๋‚ด์šฉ์„ ๋ชจ์กฐ๋ฆฌ LLM์—๊ฒŒ ์ „๋‹ฌํ•˜๊ณ  ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹ , ๋‹ค์Œ๊ณผ ๊ฐ™์ด InterviewContext์— ๋„๊ตฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

public record InterviewContext(
    CandidateAnalysisResult candidateAnalysis,
    PositionAnalysisResult positionAnalysis,
    List<EvaluationCategoryResult> evaluationCategories,
    List<String> checklist,
    List<AskedQuestionResult> history
) {

    @LlmTool(description = "Get detailed candidate analysis")
    public CandidateAnalysisResult getCandidateAnalysis() {
        return candidateAnalysis;
    }

    @LlmTool(description = "Get detailed position or purpose analysis")
    public PositionAnalysisResult getPositionAnalysis() {
        return positionAnalysis;
    }

    @LlmTool(description = "Get the most recent interview question and answers")
    public AskedQuestionResult getLastInteraction() {
        if (history.isEmpty()) {
            return null;
        }
        return history.getLast();
    }

    @LlmTool(description = "Get previous interaction by index. 0 is the first question.")
    public AskedQuestionResult getInteraction(int index) {
        if (index < 0 || index >= history.size()) {
            return null;
        }
        return history.get(index);
    }

    @LlmTool(description = "Get recent interview interactions")
    public List<AskedQuestionResult> getRecentHistory(int count) {
        int start = Math.max(0, history.size() - count);
        return history.subList(start, history.size());
    }

    @LlmTool(description = "Search interview history by keyword")
    public List<AskedQuestionResult> searchHistory(String keyword) {
        return history.stream()
                      .filter(q -> q.content().toLowerCase().contains(keyword.toLowerCase()))
                      .toList();
    }

    @LlmTool(description = "Get evaluation categories used to score the candidate")
    public List<EvaluationCategoryResult> getEvaluationCategories() {
        return evaluationCategories;
    }

    @LlmTool(description = "Get checklist topics that interview questions should cover")
    public List<String> getChecklist() {
        return checklist;
    }

    public String toSummary() {
        return """
               Candidate: %s
               Position: %s
               Categories: %d
               Checklist topics: %d
               """.formatted(
            candidateAnalysis.summary(),
            positionAnalysis.summary(),
            evaluationCategories.size(),
            checklist.size()
        );
    }
}

 

์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” Summary๋งŒ ํ”„๋กฌํ”„ํŠธ์— ์ž…๋ ฅํ•ด ์ฃผ์–ด๋„, LLM์ด ํ•„์š”์— ์˜ํ•ด ์•Œ์•„์„œ ๋„๊ตฌ๋ฅผ ์ฃผ์„ฌ์ฃผ์„ฌ ์ฐพ์•„์„œ ์“ฐ๊ธฐ ๋•Œ๋ฌธ์— ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ๋ณด๋‹ค ํ›จ์”ฌ ํšจ์œจ์ ์ด๋‹ค.

 

์ด LLM์ด ํŒ๋‹จ ๊ณผ์ •์—์„œ ์ด ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์‹ถ์œผ๋ฉด ๊ทธ๋ƒฅ .withToolObject์— ๋„ฃ์–ด์ฃผ๋ฉด ๋•ก์ด๋‹ค. ์š”๋กœ์ฝ”๋กฌ

            String[] generated = ai.withAutoLlm()
                                   .withToolObject(context)
                                   .createObject(prompt, String[].class);

์ฐธ ์‰ฝ์ฃ ? ^^

๋ฒˆ์™ธ) MCP (Model Context Protocol)

๋„๊ตฌ๋ฅผ ํ˜ธ์ŠคํŒ…ํ•˜๊ณ  ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•œ ํ‘œ์ค€ํ™”๋œ ํ”„๋กœํ† ์ฝœ์ธ๋ฐ, ์•„์ฃผ ์‚ด์ง ๋ณด์—ฌ์ฃผ์ž๋ฉด

@Configuration
public class NotionMcpConfig {

    @Bean
    public McpToolFactory mcpToolFactory(List<McpSyncClient> clients) {
        return new McpToolFactory(clients);
    }

    @Bean(name = "notionTool")
    public Tool notionTool(McpToolFactory mcpToolFactory) {
        return mcpToolFactory.matryoshka(
            "notion",
            "Notion MCP editing tool",
            callback -> true
        );
    }
}


NotionTool์„ Bean์œผ๋กœ ๋“ฑ๋กํ•˜๊ณ  ๊ทธ๊ฑธ ๋„๊ตฌ๋กœ ๋“ฑ๋กํ•ด ์ฃผ๋ฉด

@Agent(description = "Exports evaluation result to Notion")
@RequiredArgsConstructor
public class EvaluationNotionAgent {

    private final Tool notionTool;

    @AchievesGoal(description = "Exported to Notion")
    @Action
    public String export(Long candidateId, EvaluationResult result, Ai ai) {
        String categoryTable = result.categoryScores().stream()
                                     .map(c -> "%s | %.1f | %.1f | %s".formatted(
                                         c.content(),
                                         c.weight(),
                                         c.score(),
                                         c.rationale()
                                     ))
                                     .collect(Collectors.joining("\n"));

        String evaluationData = FORMAT_STRING.formatted(
            result.totalScore(),
            categoryTable,
            result.interviewAssessment(),
            result.strengths(),
            result.weaknesses(),
            result.hiringRecommendation()
        );

        return ai.withAutoLlm()
                 .withTool(notionTool)
                 .createObject(NOTION_PROMPT.formatted(pageId, candidateId, evaluationData), String.class);
    }
}

 

๋Œ€์ถฉ ์ด๋Ÿฐ ๋А๋‚Œ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ API ํ˜ธ์ถœ ์‹œ AI๊ฐ€ ์•Œ์•„์„œ ๋…ธ์…˜์„ ์—…๋ฐ์ดํŠธํ•ด์ฃผ๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค:

๊ทผ๋ฐ ํ† ํฐ์ด ์‚ด์‚ด ๋…น์œผ๋‹ˆ๊นŒ ์ฃผ์˜ํ•˜์„ธ์š”

 

 

๋ฐ•๋ช…์ˆ˜_์ตœ์ข….png

 

 

์ฐธ๊ณ ์ž๋ฃŒ

https://docs.embabel.com/embabel-agent/guide/0.3.5-SNAPSHOT/#superior-extensibility-and-reuse