Guide 문서의 Start 문서, 참고자료 문서를 먼저 공부하고 볼까 고민했는데, 나는 야생형! 먼저 예제 프로젝트를 뜯어봐야겠다.
선호도와 관심사에 기반하여 개인화된 여행 일정을 작성하도록 도와주는 여행 계획 에이전트이다.
agent와 관련된 중요 요소만 뽑아봤다.
com.embabel/
├── agent/ # 공유 에이전트 기능
│ ├── Brave.kt - Brave 검색 도구
│ └── web/
│ ├── htmx/
│ │ ├── ProcessStatusController.kt - SSE 진행 상황 스트리밍
│ │ └── PlatformController.kt - 플랫폼 정보
│ └── security/
│ ├── LoginController.kt - OAuth2 로그인
│ ├── UserController.kt - 사용자 관리
│ └── SecurityConfig.kt - OAuth2 Google 인증 설정
│
└── tripper/ # Tripper 애플리케이션 (메인)
├── agent/ - 여행 계획 에이전트
│ ├── TripperAgent.kt - 메인 오케스트레이터 (3-4단계 흐름)
│ └── domain.kt - 도메인 모델
│
├── config/ - 구성
│ └── ToolsConfig.kt - MCP 도구 그룹 등록
│
├── web/ - 웹 인터페이스
│ └── JourneyHtmxController.kt - 여행 폼 처리
│
├── util/ - 유틸리티
│ └── ImageChecker.kt - 이미지 검증
│
└── TripperApplication.kt - Spring Boot 진입점
이 프로젝트를 실행하기 위해선 최소 3개의 API KEY(openai, brave api, google maps api)가 필요하다.


(흑흑 내 8200원)

luna@baejunseoui-MacBookPro tripper % docker compose --file compose.yaml up
WARN[0000] The "ANTHROPIC_API_KEY" variable is not set. Defaulting to a blank string.
[+] Running 15/15
✔ zipkin Pulled 0.5s
Attaching to mcp-gateway-1, zipkin-1
mcp-gateway-1 | Note: dynamic-tools disabled when using --servers flag
mcp-gateway-1 | - Reading configuration...
mcp-gateway-1 | - Reading catalog from [https://desktop.docker.com/mcp/catalog/v3/catalog.yaml]
mcp-gateway-1 | - Reading secrets [brave.api_key github.personal_access_token google-maps.api_key]
zipkin-1 |
zipkin-1 | oo
zipkin-1 | oooo
zipkin-1 | oooooo
zipkin-1 | oooooooo
zipkin-1 | oooooooooo
zipkin-1 | oooooooooooo
zipkin-1 | ooooooo ooooooo
zipkin-1 | oooooo ooooooo
zipkin-1 | oooooo ooooooo
zipkin-1 | oooooo o o oooooo
zipkin-1 | oooooo oo oo oooooo
zipkin-1 | ooooooo oooo oooo ooooooo
zipkin-1 | oooooo ooooo ooooo ooooooo
zipkin-1 | oooooo oooooo oooooo ooooooo
zipkin-1 | oooooooo oo oo oooooooo
zipkin-1 | ooooooooooooo oo oo ooooooooooooo
zipkin-1 | oooooooooooo oooooooooooo
zipkin-1 | oooooooo oooooooo
zipkin-1 | oooo oooo
zipkin-1 |
zipkin-1 | ________ ____ _ _____ _ _
zipkin-1 | |__ /_ _| _ \| |/ /_ _| \ | |
zipkin-1 | / / | || |_) | ' / | || \| |
zipkin-1 | / /_ | || __/| . \ | || |\ |
zipkin-1 | |____|___|_| |_|\_\___|_| \_|
zipkin-1 |
zipkin-1 | :: version 3.5.1 :: commit b7e2258 ::
zipkin-1 |
zipkin-1 | 2025-11-23T21:57:49.466Z WARN [/] 1 --- [ main] i.m.p.PrometheusMeterRegistry : A MeterFilter is being configured after a Meter has been registered to this registry. All MeterFilters should be configured before any Meters are registered. If that is not possible or you have a use case where it should be allowed, let the Micrometer maintainers know at https://github.com/micrometer-metrics/micrometer/issues/4920. Enable DEBUG level logging on this logger to see a stack trace of the call configuring this MeterFilter.
zipkin-1 | 2025-11-23T21:57:49.681Z INFO [/] 1 --- [oss-http-*:9411] c.l.a.s.Server : Serving HTTP at /[0:0:0:0:0:0:0:0]:9411 - http://127.0.0.1:9411/
mcp-gateway-1 | couldn't read secret brave.api_key: reading secrets exit status 125: docker: Error response from daemon: no such secret: brave.api_key
mcp-gateway-1 |
mcp-gateway-1 | Run 'docker run --help' for more information
mcp-gateway-1 |
mcp-gateway-1 | couldn't read secret github.personal_access_token: reading secrets exit status 125: docker: Error response from daemon: no such secret: github.personal_access_token
mcp-gateway-1 |
mcp-gateway-1 | Run 'docker run --help' for more information
mcp-gateway-1 |
mcp-gateway-1 | couldn't read secret google-maps.api_key: reading secrets exit status 125: docker: Error response from daemon: no such secret: google-maps.api_key
mcp-gateway-1 |
mcp-gateway-1 | Run 'docker run --help' for more information
mcp-gateway-1 |
mcp-gateway-1 | - Configuration read in 4.281827919s
mcp-gateway-1 | - Using images:
mcp-gateway-1 | - mcp/brave-search@sha256:f45d701a860cade995bbb9c456eb67c0a71cb8232cab82f187f6dffdbf5d150c
mcp-gateway-1 | - mcp/github@sha256:89fd71b6f2dd098de0d7517c1899b7fe90590d3193e253991d61a800b041bfd4
mcp-gateway-1 | - mcp/google-maps@sha256:d8652c8c9c52ac9d82e9cadd1dc0468ade7a4f8c53e102e641eb4248894aa914
mcp-gateway-1 | - mcp/openbnb-airbnb@sha256:352f5716e418f7d94e1bcd5c6846d7940b3250255b78ab1acb7e0adbd8f89331
mcp-gateway-1 | - mcp/puppeteer@sha256:c1e2bda6d92d400e900e497b743552a670a33631799c0a6478e91096e389bd27
mcp-gateway-1 | - mcp/wikipedia-mcp@sha256:047f17273bae96828903cfd12f13c7a64951b54e537530ecb6678fff5204a584
mcp-gateway-1 | > Images pulled in 28.829871931s
mcp-gateway-1 | - Those servers are enabled: brave, wikipedia-mcp, puppeteer, github, openbnb-airbnb, google-maps
mcp-gateway-1 | - Listing MCP tools...
mcp-gateway-1 | Warning: Secret 'brave.api_key' not found for server 'brave', setting BRAVE_API_KEY=<UNKNOWN>
mcp-gateway-1 | - Running mcp/brave-search with [run --rm -i --init --security-opt no-new-privileges --cpus 1 --memory 2Gb --pull never -l docker-mcp=true -l docker-mcp-tool-type=mcp -l docker-mcp-name=brave -l docker-mcp-transport=stdio --network tripper_default -e BRAVE_API_KEY -e BRAVE_MCP_TRANSPORT]
mcp-gateway-1 | - Running mcp/puppeteer with [run --rm -i --init --security-opt no-new-privileges --cpus 1 --memory 2Gb --pull never -l docker-mcp=true -l docker-mcp-tool-type=mcp -l docker-mcp-name=puppeteer -l docker-mcp-transport=stdio --network tripper_default -e DOCKER_CONTAINER]
mcp-gateway-1 | - Running mcp/openbnb-airbnb with [run --rm -i --init --security-opt no-new-privileges --cpus 1 --memory 2Gb --pull never -l docker-mcp=true -l docker-mcp-tool-type=mcp -l docker-mcp-name=openbnb-airbnb -l docker-mcp-transport=stdio --network tripper_default]
mcp-gateway-1 | Warning: Secret 'github.personal_access_token' not found for server 'github', setting GITHUB_PERSONAL_ACCESS_TOKEN=<UNKNOWN>
mcp-gateway-1 | - Running mcp/github with [run --rm -i --init --security-opt no-new-privileges --cpus 1 --memory 2Gb --pull never -l docker-mcp=true -l docker-mcp-tool-type=mcp -l docker-mcp-name=github -l docker-mcp-transport=stdio --network tripper_default -e GITHUB_PERSONAL_ACCESS_TOKEN]
mcp-gateway-1 | - Running mcp/wikipedia-mcp with [run --rm -i --init --security-opt no-new-privileges --cpus 1 --memory 2Gb --pull never -l docker-mcp=true -l docker-mcp-tool-type=mcp -l docker-mcp-name=wikipedia-mcp -l docker-mcp-transport=stdio --network tripper_default]
mcp-gateway-1 | - github: GitHub MCP Server running on stdio
mcp-gateway-1 | > github: (6 tools)
mcp-gateway-1 | Warning: Secret 'google-maps.api_key' not found for server 'google-maps', setting GOOGLE_MAPS_API_KEY=<UNKNOWN>
mcp-gateway-1 | - Running mcp/google-maps with [run --rm -i --init --security-opt no-new-privileges --cpus 1 --memory 2Gb --pull never -l docker-mcp=true -l docker-mcp-tool-type=mcp -l docker-mcp-name=google-maps -l docker-mcp-transport=stdio --network tripper_default -e GOOGLE_MAPS_API_KEY]
mcp-gateway-1 | > brave: (1 tools)
mcp-gateway-1 | - openbnb-airbnb: [2025-11-23T21:58:22.500Z] [INFO] Airbnb MCP Server starting: {
mcp-gateway-1 | - openbnb-airbnb: "version": "0.1.3",
mcp-gateway-1 | - openbnb-airbnb: "ignoreRobotsTxt": false,
mcp-gateway-1 | - openbnb-airbnb: "nodeVersion": "v22.17.1",
mcp-gateway-1 | - openbnb-airbnb: "platform": "linux"
mcp-gateway-1 | - openbnb-airbnb: }
mcp-gateway-1 | - openbnb-airbnb: [2025-11-23T21:58:22.506Z] [INFO] Fetching robots.txt from Airbnb
mcp-gateway-1 | - openbnb-airbnb: [2025-11-23T21:58:22.639Z] [INFO] Successfully fetched robots.txt
mcp-gateway-1 | - openbnb-airbnb: [2025-11-23T21:58:22.639Z] [INFO] Airbnb MCP Server running on stdio: {
mcp-gateway-1 | - openbnb-airbnb: "version": "0.1.3",
mcp-gateway-1 | - openbnb-airbnb: "robotsRespected": true
mcp-gateway-1 | - openbnb-airbnb: }
mcp-gateway-1 | > puppeteer: (7 tools) (1 resources)
mcp-gateway-1 | > openbnb-airbnb: (2 tools)
mcp-gateway-1 | - puppeteer: Puppeteer MCP Server closed
mcp-gateway-1 | - google-maps: Google Maps MCP Server running on stdio
mcp-gateway-1 | - wikipedia-mcp: 2025-11-23 21:58:23,242 - wikipediaapi - INFO - Wikipedia: language=en, user_agent: WikipediaMCPServer/1.6.0 (https://github.com/rudra-ravi/wikipedia-mcp) (Wikipedia-API/0.8.1; https://github.com/martin-majlis/Wikipedia-API/), extract_format=ExtractFormat.WIKI
mcp-gateway-1 | > google-maps: (7 tools)
mcp-gateway-1 | - wikipedia-mcp: 2025-11-23 21:58:23,282 - wikipedia_mcp.__main__ - INFO - Starting Wikipedia MCP server with stdio transport for language: en
mcp-gateway-1 | - wikipedia-mcp: 2025-11-23 21:58:23,282 - wikipedia_mcp.__main__ - INFO - Using stdio transport - suppressing direct stdout messages for MCP communication.
mcp-gateway-1 | - wikipedia-mcp: 2025-11-23 21:58:23,283 - wikipedia_mcp.__main__ - INFO - To use with Claude Desktop, ensure 'wikipedia-mcp' command is in your claude_desktop_config.json.
mcp-gateway-1 | - wikipedia-mcp:
mcp-gateway-1 | - wikipedia-mcp:
mcp-gateway-1 | - wikipedia-mcp: ╭──────────────────────────────────────────────────────────────────────────────╮
mcp-gateway-1 | - wikipedia-mcp: │ │
mcp-gateway-1 | - wikipedia-mcp: │ ▄▀▀ ▄▀█ █▀▀ ▀█▀ █▀▄▀█ █▀▀ █▀█ │
mcp-gateway-1 | - wikipedia-mcp: │ █▀ █▀█ ▄▄█ █ █ ▀ █ █▄▄ █▀▀ │
mcp-gateway-1 | - wikipedia-mcp: │ │
mcp-gateway-1 | - wikipedia-mcp: │ FastMCP 2.13.0.2 │
mcp-gateway-1 | - wikipedia-mcp: │ │
mcp-gateway-1 | - wikipedia-mcp: │ │
mcp-gateway-1 | - wikipedia-mcp: │ 🖥 Server name: Wikipedia │
mcp-gateway-1 | - wikipedia-mcp: │ │
mcp-gateway-1 | - wikipedia-mcp: │ 📦 Transport: STDIO │
mcp-gateway-1 | - wikipedia-mcp: │ │
mcp-gateway-1 | - wikipedia-mcp: │ 📚 Docs: https://gofastmcp.com │
mcp-gateway-1 | - wikipedia-mcp: │ 🚀 Hosting: https://fastmcp.cloud │
mcp-gateway-1 | - wikipedia-mcp: │ │
mcp-gateway-1 | - wikipedia-mcp: ╰──────────────────────────────────────────────────────────────────────────────╯
mcp-gateway-1 | - wikipedia-mcp:
mcp-gateway-1 | - wikipedia-mcp:
mcp-gateway-1 | - wikipedia-mcp: [11/23/25 21:58:23] INFO Starting MCP server 'Wikipedia' with server.py:1966
mcp-gateway-1 | - wikipedia-mcp: transport 'stdio'
mcp-gateway-1 | - wikipedia-mcp: 2025-11-23 21:58:23,296 - mcp.server.lowlevel.server - INFO - Processing request of type ListToolsRequest
mcp-gateway-1 | - wikipedia-mcp: 2025-11-23 21:58:23,301 - mcp.server.lowlevel.server - INFO - Processing request of type ListPromptsRequest
mcp-gateway-1 | - wikipedia-mcp: 2025-11-23 21:58:23,308 - mcp.server.lowlevel.server - INFO - Processing request of type ListResourcesRequest
mcp-gateway-1 | - wikipedia-mcp: 2025-11-23 21:58:23,310 - mcp.server.lowlevel.server - INFO - Processing request of type ListResourceTemplatesRequest
mcp-gateway-1 | > wikipedia-mcp: (11 tools) (9 resourceTemplates)
mcp-gateway-1 | > 34 tools listed in 3.404502376s
mcp-gateway-1 | > Initialized in 36.570998142s
mcp-gateway-1 | > Start sse server on port 9011
mcp-gateway-1 | > Gateway URL: http://localhost:9011/sse
mcp-gateway-1 | > Authentication disabled (running in container)





07:18:36.585 [tomcat-handler-9] INFO MDR - 👔 Kier May my gaze be singularly placed upon the path, may the words of virtue guide me in the daily labor of my great undertaking.
[beautiful_grothendieck] created
07:18:36.586 [tomcat-handler-9] INFO MDR - [beautiful_grothendieck] Perpetuity wing: object added: JourneyTravelBrief
07:18:36.586 [tomcat-handler-9] INFO MDR - [beautiful_grothendieck] Perpetuity wing: object added: Travelers
07:18:36.593 [task-1] INFO MDR - [beautiful_grothendieck] <WIT> ready to plan from hasRun_com.embabel.tripper.agent.TripperAgent.confirmExpensiveOperation: FALSE
hasRun_com.embabel.tripper.agent.TripperAgent.findPlacesToSleep: FALSE
hasRun_com.embabel.tripper.agent.TripperAgent.findPointsOfInterest: FALSE
hasRun_com.embabel.tripper.agent.TripperAgent.postProcessHtml: FALSE
hasRun_com.embabel.tripper.agent.TripperAgent.proposeTravelPlan: FALSE
hasRun_com.embabel.tripper.agent.TripperAgent.researchPointsOfInterest: FALSE
it:com.embabel.tripper.agent.ItineraryIdeas: FALSE
it:com.embabel.tripper.agent.PointOfInterestFindings: FALSE
it:com.embabel.tripper.agent.ProposedTravelPlan: FALSE
it:com.embabel.tripper.agent.TravelPlan: FALSE
it:com.embabel.tripper.agent.TripperAgent$AcceptanceOfCost: FALSE
it:com.embabel.tripper.agent.JourneyTravelBrief: TRUE
it:com.embabel.tripper.agent.Travelers: TRUE
07:18:36.787 [task-1] INFO Planner - Found plan to goal com.embabel.tripper.agent.TripperAgent.postProcessHtml: com.embabel.tripper.agent.TripperAgent.findPointsOfInterest -> com.embabel.tripper.agent.TripperAgent.confirmExpensiveOperation -> com.embabel.tripper.agent.TripperAgent.researchPointsOfInterest -> com.embabel.tripper.agent.TripperAgent.proposeTravelPlan -> com.embabel.tripper.agent.TripperAgent.findPlacesToSleep -> com.embabel.tripper.agent.TripperAgent.postProcessHtml
07:18:36.788 [task-1] INFO MDR - [beautiful_grothendieck] <WILES>: formulated plan com.embabel.tripper.agent.TripperAgent.findPointsOfInterest ->
com.embabel.tripper.agent.TripperAgent.confirmExpensiveOperation ->
com.embabel.tripper.agent.TripperAgent.researchPointsOfInterest ->
com.embabel.tripper.agent.TripperAgent.proposeTravelPlan ->
com.embabel.tripper.agent.TripperAgent.findPlacesToSleep ->
com.embabel.tripper.agent.TripperAgent.postProcessHtml
goal: com.embabel.tripper.agent.TripperAgent.postProcessHtml from {it:com.embabel.tripper.agent.JourneyTravelBrief=TRUE, it:com.embabel.tripper.agent.Travelers=TRUE, it:com.embabel.tripper.agent.ItineraryIdeas=FALSE, hasRun_com.embabel.tripper.agent.TripperAgent.findPointsOfInterest=FALSE, it:com.embabel.tripper.agent.PointOfInterestFindings=FALSE, it:com.embabel.tripper.agent.ProposedTravelPlan=FALSE, hasRun_com.embabel.tripper.agent.TripperAgent.proposeTravelPlan=FALSE, it:com.embabel.tripper.agent.TravelPlan=FALSE, hasRun_com.embabel.tripper.agent.TripperAgent.findPlacesToSleep=FALSE, hasRun_com.embabel.tripper.agent.TripperAgent.postProcessHtml=FALSE, it:com.embabel.tripper.agent.TripperAgent$AcceptanceOfCost=FALSE, hasRun_com.embabel.tripper.agent.TripperAgent.confirmExpensiveOperation=FALSE, hasRun_com.embabel.tripper.agent.TripperAgent.researchPointsOfInterest=FALSE}
07:18:37.175 [task-1] INFO MDR - [beautiful_grothendieck] <VERVE>: executing action com.embabel.tripper.agent.TripperAgent.findPointsOfInterest
07:18:37.240 [task-1] WARN ToolConsumer - No tools found for tool group with role='ToolGroupRequirement(role=web)': null
▗▖ ▗▖ ▗▄▖ ▗▄▄▄▖▗▄▖ ▗▄▖ ▗▖ ▗▄▄▖ ▗▄▄▄▖ ▗▄▖ ▗▖ ▗▖▗▖ ▗▖▗▄▄▄
▐▛▚▖▐▌▐▌ ▐▌ █ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▛▚▖▐▌▐▌ █
▐▌ ▝▜▌▐▌ ▐▌ █ ▐▌ ▐▌▐▌ ▐▌▐▌ ▝▀▚▖ ▐▛▀▀▘▐▌ ▐▌▐▌ ▐▌▐▌ ▝▜▌▐▌ █
▐▌ ▐▌▝▚▄▞▘ █ ▝▚▄▞▘▝▚▄▞▘▐▙▄▄▖▗▄▄▞▘ ▐▌ ▝▚▄▞▘▝▚▄▞▘▐▌ ▐▌▐▙▄▄▀
▗▄▄▖ ▗▄▄▖ ▗▄▖ ▗▄▄▖ ▗▄▖ ▗▄▄▖ ▗▖ ▗▄▄▄▖ ▗▖ ▗▖▗▄▄▄▖ ▗▄▄▖ ▗▄▄▖ ▗▄▖ ▗▖ ▗▖▗▄▄▄▖▗▄▄▄▖ ▗▄▄▖▗▖ ▗▖▗▄▄▖ ▗▄▖▗▄▄▄▖▗▄▄▄▖ ▗▄▖ ▗▖ ▗▖
▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▛▚▞▜▌ █ ▐▌ ▐▌ ▐▌ ▐▌▐▛▚▖▐▌▐▌ █ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ █ █ ▐▌ ▐▌▐▛▚▖▐▌
▐▛▀▘ ▐▛▀▚▖▐▌ ▐▌▐▛▀▚▖▐▛▀▜▌▐▛▀▚▖▐▌ ▐▛▀▀▘ ▐▌ ▐▌ █ ▝▀▚▖▐▌ ▐▌ ▐▌▐▌ ▝▜▌▐▛▀▀▘ █ ▐▌▝▜▌▐▌ ▐▌▐▛▀▚▖▐▛▀▜▌ █ █ ▐▌ ▐▌▐▌ ▝▜▌
▐▌ ▐▌ ▐▌▝▚▄▞▘▐▙▄▞▘▐▌ ▐▌▐▙▄▞▘▐▙▄▄▖▐▙▄▄▖ ▐▌ ▐▌▗▄█▄▖▗▄▄▞▘▝▚▄▄▖▝▚▄▞▘▐▌ ▐▌▐▌ ▗▄█▄▖▝▚▄▞▘▝▚▄▞▘▐▌ ▐▌▐▌ ▐▌ █ ▗▄█▄▖▝▚▄▞▘▐▌ ▐▌
07:18:37.240 [task-1] WARN ToolConsumer - No tools found for tool group with role='ToolGroupRequirement(role=maps)': null
▗▖ ▗▖ ▗▄▖ ▗▄▄▄▖▗▄▖ ▗▄▖ ▗▖ ▗▄▄▖ ▗▄▄▄▖ ▗▄▖ ▗▖ ▗▖▗▖ ▗▖▗▄▄▄
▐▛▚▖▐▌▐▌ ▐▌ █ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▛▚▖▐▌▐▌ █
▐▌ ▝▜▌▐▌ ▐▌ █ ▐▌ ▐▌▐▌ ▐▌▐▌ ▝▀▚▖ ▐▛▀▀▘▐▌ ▐▌▐▌ ▐▌▐▌ ▝▜▌▐▌ █
▐▌ ▐▌▝▚▄▞▘ █ ▝▚▄▞▘▝▚▄▞▘▐▙▄▄▖▗▄▄▞▘ ▐▌ ▝▚▄▞▘▝▚▄▞▘▐▌ ▐▌▐▙▄▄▀
▗▄▄▖ ▗▄▄▖ ▗▄▖ ▗▄▄▖ ▗▄▖ ▗▄▄▖ ▗▖ ▗▄▄▄▖ ▗▖ ▗▖▗▄▄▄▖ ▗▄▄▖ ▗▄▄▖ ▗▄▖ ▗▖ ▗▖▗▄▄▄▖▗▄▄▄▖ ▗▄▄▖▗▖ ▗▖▗▄▄▖ ▗▄▖▗▄▄▄▖▗▄▄▄▖ ▗▄▖ ▗▖ ▗▖
▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▛▚▞▜▌ █ ▐▌ ▐▌ ▐▌ ▐▌▐▛▚▖▐▌▐▌ █ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ █ █ ▐▌ ▐▌▐▛▚▖▐▌
▐▛▀▘ ▐▛▀▚▖▐▌ ▐▌▐▛▀▚▖▐▛▀▜▌▐▛▀▚▖▐▌ ▐▛▀▀▘ ▐▌ ▐▌ █ ▝▀▚▖▐▌ ▐▌ ▐▌▐▌ ▝▜▌▐▛▀▀▘ █ ▐▌▝▜▌▐▌ ▐▌▐▛▀▚▖▐▛▀▜▌ █ █ ▐▌ ▐▌▐▌ ▝▜▌
▐▌ ▐▌ ▐▌▝▚▄▞▘▐▙▄▞▘▐▌ ▐▌▐▙▄▞▘▐▙▄▄▖▐▙▄▄▖ ▐▌ ▐▌▗▄█▄▖▗▄▄▞▘▝▚▄▄▖▝▚▄▞▘▐▌ ▐▌▐▌ ▗▄█▄▖▝▚▄▞▘▝▚▄▞▘▐▌ ▐▌▐▌ ▐▌ █ ▗▄█▄▖▝▚▄▞▘▐▌ ▐▌
07:18:37.252 [task-1] WARN ToolConsumer - No tools found for tool group with role='ToolGroupRequirement(role=weather)': null
▗▖ ▗▖ ▗▄▖ ▗▄▄▄▖▗▄▖ ▗▄▖ ▗▖ ▗▄▄▖ ▗▄▄▄▖ ▗▄▖ ▗▖ ▗▖▗▖ ▗▖▗▄▄▄
▐▛▚▖▐▌▐▌ ▐▌ █ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▛▚▖▐▌▐▌ █
▐▌ ▝▜▌▐▌ ▐▌ █ ▐▌ ▐▌▐▌ ▐▌▐▌ ▝▀▚▖ ▐▛▀▀▘▐▌ ▐▌▐▌ ▐▌▐▌ ▝▜▌▐▌ █
▐▌ ▐▌▝▚▄▞▘ █ ▝▚▄▞▘▝▚▄▞▘▐▙▄▄▖▗▄▄▞▘ ▐▌ ▝▚▄▞▘▝▚▄▞▘▐▌ ▐▌▐▙▄▄▀
▗▄▄▖ ▗▄▄▖ ▗▄▖ ▗▄▄▖ ▗▄▖ ▗▄▄▖ ▗▖ ▗▄▄▄▖ ▗▖ ▗▖▗▄▄▄▖ ▗▄▄▖ ▗▄▄▖ ▗▄▖ ▗▖ ▗▖▗▄▄▄▖▗▄▄▄▖ ▗▄▄▖▗▖ ▗▖▗▄▄▖ ▗▄▖▗▄▄▄▖▗▄▄▄▖ ▗▄▖ ▗▖ ▗▖
▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▛▚▞▜▌ █ ▐▌ ▐▌ ▐▌ ▐▌▐▛▚▖▐▌▐▌ █ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ █ █ ▐▌ ▐▌▐▛▚▖▐▌
▐▛▀▘ ▐▛▀▚▖▐▌ ▐▌▐▛▀▚▖▐▛▀▜▌▐▛▀▚▖▐▌ ▐▛▀▀▘ ▐▌ ▐▌ █ ▝▀▚▖▐▌ ▐▌ ▐▌▐▌ ▝▜▌▐▛▀▀▘ █ ▐▌▝▜▌▐▌ ▐▌▐▛▀▚▖▐▛▀▜▌ █ █ ▐▌ ▐▌▐▌ ▝▜▌
▐▌ ▐▌ ▐▌▝▚▄▞▘▐▙▄▞▘▐▌ ▐▌▐▙▄▞▘▐▙▄▄▖▐▙▄▄▖ ▐▌ ▐▌▗▄█▄▖▗▄▄▞▘▝▚▄▄▖▝▚▄▞▘▐▌ ▐▌▐▌ ▗▄█▄▖▝▚▄▞▘▝▚▄▞▘▐▌ ▐▌▐▌ ▐▌ █ ▗▄█▄▖▝▚▄▞▘▐▌ ▐▌
07:18:37.254 [task-1] INFO MDR - [beautiful_grothendieck] (com.embabel.tripper.agent.TripperAgent.findPointsOfInterest-com.embabel.tripper.agent.ItineraryIdeas) 🖥️ MACRODATA REFINEMENT using LLM gpt-5-mini, creating ItineraryIdeas: LlmOptions(modelSelectionCriteria=null, model=gpt-5-mini, role=null, temperature=null, frequencyPenalty=null, maxTokens=null, presencePenalty=null, topK=null, topP=null, thinking=null, timeout=null) with tools add, ceiling, divide, floor, max, mean, min, multiply, round, subtract
07:18:37.370 [task-1] INFO MDR - beautiful_grothendieck Spring AI ChatModel call:
using gpt-5-mini
Messages ⇩ .......................................................................................
SYSTEM <Current date: 2025-11-24
----
You are Hermes.
Your persona: You are an expert travel planner.
Your objective is Help the user plan an amazing trip, considering their requirements and preferences.
Your voice: friendly and concise.
----
2 travelers:
bebe: Loves history and museums. Fascinated by Joan of Arc.
pobi: Enjoys food and wine. Has a particular interest in cabernet.
----
Knowledge cutoff: 2024-05
>
....................................................................................................
USER < Consider the following travel brief for a journey from seoul to washington.
Journey from seoul to washington
Dates: 2025-12-03 to 2025-12-23
Brief: Relaxed road trip exploring countryside, history, food and wine.
Transport preference: flying
Find points of interest that are relevant to the travel brief and travelers.
Use mapping tools to consider appropriate order and put a rough date
range for each point of interest.
Consider likely weather>
Options ⇩ .........................................................................................
null
prompt id: 'com.embabel.tripper.agent.TripperAgent.findPointsOfInterest-com.embabel.tripper.agent.ItineraryIdeas'
tools: [add, ceiling, divide, floor, max, mean, min, multiply, round, subtract]



A* 알고리즘에 따라 동적으로 계획을 세운다.
+-------------------------------+
| USER |
| TripRequest JSON 등 |
+---------------+---------------+
|
v
+---------+-----------+
| Embabel Agent |
| Platform |
+---------+-----------+
|
v
+---------+-----------+
| GOAP PLANNER | <-- A* 기반 GOAP
| (AStarGoapPlanner) |
+---------+-----------+
|
타입/조건 기반 분석
|
v
+-----------+---------------------------------------------+
| WORLD STATE (타입 기반 상태) |
| |
| { |
| TripRequest, UserPreferences, CandidateStops, |
| StopResearch[], StayOptions, TripPlan ... |
| } |
+-----------+---------------------------------------------+
^
|
| GOAP가 사용할 수 있는 액션들
|
+-----------+----------------------------------------------+
| @Action SET (도메인 액션) |
| |
| A1: enrichRequest(TripRequest) -> UserPreferences |
| A2: chooseStops(UserPreferences) -> CandidateStops |
| A3: researchStops(CandidateStops) -> StopResearch[] |
| A4: pickStays(Stops, Research) -> StayOptions |
| A5: buildTripPlan(Stops, Stays, Research) -> TripPlan |
| (@AchievesGoal) |
+-----------+----------------------------------------------+
GOAP:
- 현재 WORLD STATE와 액션의 입/출력 타입·조건을 보고
- A*로 "TripRequest -> TripPlan" 최적 액션 시퀀스 탐색
예: Plan = [A1 -> A2 -> A3 -> A4 -> A5]
|
v
+---------+-----------+
| PLAN EXECUTOR |
| (A1..A5 순서 실행) |
+---------+-----------+
|
v
+---------+-----------+
| TripPlan 반환 |
+---------------------+
@SpringBootApplication
@ConfigurationPropertiesScan
@EnableAgents(
loggingTheme = LoggingThemes.SEVERANCE,
mcpServers = [McpServers.DOCKER_DESKTOP],
)
class TripperApplication
fun main(args: Array<String>) {
runApplication<TripperApplication>(*args)
}
@EnableAgents
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@AgentPlatform
public @interface EnableAgents {
String loggingTheme() default "";
/** @deprecated */
@Deprecated(
since = "0.1.3",
forRemoval = true
)
String[] localModels() default {};
String[] mcpServers() default {};
}
loggingTheme: 로그의 테마를 선택한다.public static final String *STAR_WARS* = "starwars";public static final String *SEVERANCE* = "severance";public static final String *COLOSSUS* = "colossus”;mcpServers: 에이전트가 연결할 MCP 서버 목록을 설정한다. 로컬 또는 외부 MCP 서버(MCP Tool Server)를 연결할 때 사용한다.public static final String DOCKER = "docker-ce";public static final String DOCKER_DESKTOP = "docker-desktop";Controller 주요 코드
@Controller
class JourneyHtmxController(
private val agentPlatform: AgentPlatform,
) {
// ... 중략
@PostMapping("/plan")
fun planJourney(
@ModelAttribute form: JourneyPlanForm,
model: Model
): String {
val travelBrief = JourneyTravelBrief(
from = form.from,
to = form.to,
transportPreference = form.transportPreference,
brief = form.brief,
departureDate = form.departureDate,
returnDate = form.returnDate,
dailyBudget = form.dailyBudget,
)
// 폼 여행자를 도메인 객체로 변환
val travelersList = form.travelers
.filter { it.name.isNotBlank() }
.map { travelerForm ->
Traveler(name = travelerForm.name, about = travelerForm.about)
}
val travelers = Travelers(travelers = travelersList)
val agent = agentPlatform.agents().singleOrNull { it.name.lowercase().contains("trip") }
?: error("No travel agent found. Please ensure the tripper agent is registered.")
val agentProcess = agentPlatform.createAgentProcessFrom(
agent = agent,
processOptions = ProcessOptions(
verbosity = Verbosity(
showPrompts = true,
showLlmResponses = true,
),
// 비용이 많이 들지만 괜찮습니다
budget = Budget(
tokens = Budget.DEFAULT_TOKEN_LIMIT * 3,
)
),
travelBrief, travelers
)
model.addAttribute("travelBrief", travelBrief)
GenericProcessingValues(
agentProcess = agentProcess,
pageTitle = "Planning your journey",
detail = travelBrief.brief,
resultModelKey = "travelPlan",
successView = "journey-plan",
).addToModel(model)
agentPlatform.start(agentProcess)
return "common/processing"
}
class JourneyHtmxController(
private val agentPlatform: AgentPlatform,
) {
agentPlatform.agents().singleOrNull { it.name.lowercase().contains("trip") }
?: error("No travel agent found. Please ensure the tripper agent is registered.")
val agentProcess = agentPlatform.createAgentProcessFrom(
agent = agent,
processOptions = ProcessOptions(
verbosity = Verbosity(
showPrompts = true,
showLlmResponses = true,
),
// 비용이 많이 들지만 괜찮습니다
budget = Budget(
tokens = Budget.DEFAULT_TOKEN_LIMIT * 3,
)
),
travelBrief, travelers
)