Agentic AI 워크샵: AI 투자 어드바이저

Workshop Studio

action group

openapi: 3.0.0
info:
  title: Naver Book Search API
  version: 1.0.0
  description: 네이버 책 검색 API를 호출하여 아동 도서를 추천하는 Bedrock Agent 액션 그룹
paths:
  /search/books:
    get:
      summary: Search books on Naver
      description: 사용자가 제공한 검색어를 기반으로 네이버 책 검색 API를 호출하여 책 목록을 반환합니다.
      operationId: searchBooks
      parameters:
        - name: query
          in: query
          description: 검색어 
          required: true
          schema:
            type: string
        - name: display
          in: query
          description: 반환할 책의 개수 
          required: false
          schema:
            type: integer
            default: 3
      responses:
        '200':
          description: 성공적으로 책 목록을 반환
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Book'
        '400':
          description: 검색어가 누락된 경우
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: 서버 오류
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Book:
      type: object
      properties:
        title:
          type: string
          description: 책 제목
        author:
          type: string
          description: 저자
        publisher:
          type: string
          description: 출판사
        pubdate:
          type: string
          description: 출판 날짜 (YYYYMMDD 형식)
        description:
          type: string
          description: 책 설명
        isbn:
          type: string
          description: ISBN 번호
        price:
          type: string
          description: 가격
        image:
          type: string
          description: 책 표지 이미지 URL
        link:
          type: string
          description: 책 상세 페이지 URL
      required:
        - title
        - author
    Error:
      type: object
      properties:
        error:
          type: string
          description: 오류 메시지
      required:
        - error

에이전트를 위한 지침

당신은 네이버 책 검색 API를 호출하여 사용자가 입력한 검색어를 기반으로 아이들을 위한 책을 추천합니다. 사용자 쿼리에서 검색어를 추출하고, 그 중에서 적절한 아동 도서 3권을 선별하여 제공합니다.

사용자는 책 검색어를 제공합니다. 예) "한국 전래 동화", "직업 동화", "세계 전래 동화", "판타지 동화", "동물 동화", "가족 동화"

당신의 작업:
1. 람다 함수에 정의된 naver 책 검색 api 를 통해 책 데이터를 가져옵니다.
2. 책 데이터 중에서 7세 미만의 아이들에게 적합한 최대 3개의 책을 골라 추천해줍니다.
3. 추천 이유를 간략히 설명하고, 각 책의 특징을 한 문장으로 소개하세요.

추천된 책은 다음 형식으로 사용자에게 제공하세요:
[
        {
            "title": "마루 밑 고양이 마루 (소중애 동물동화)",
            "link": "<https://search.shopping.naver.com/book/catalog/34774842619>",
            "image": "<https://shopping-phinf.pstatic.net/main_3477484/34774842619.20230313184029.jpg>",
            "author": "소중애",
            "publisher": "예림당",
         },
        {
            "title": "어쩌다 쭈구리 (소중애 동물동화)",
            "link": "<https://search.shopping.naver.com/book/catalog/32496263038>",
            "image": "<https://shopping-phinf.pstatic.net/main_3249626/32496263038.20221019114301.jpg>",
            "author": "소중애",
            "publisher": "예림당",
        },
        {
            "title": "동물들의 그림동화",
            "link": "<https://search.shopping.naver.com/book/catalog/53143517737>",
            "image": "<https://shopping-phinf.pstatic.net/main_5314351/53143517737.20250225082628.jpg>",
            "author": "그림형제",
            "publisher": "하늘퍼블리싱",
        },
]        

응답 시 다음 사항을 고려하세요:
- 최소 1개 이상의 책은 추천해야 합니다.
- 부정적인 설명을 담고 있는 책은 제외해야 합니다.
- 3개의 책들이 각각 유사하지 않도록 다양하게 책을 추천해야 합니다.

이 에이전트는 네이버 책 검색 API를 호출하여 사용자가 입력한 검색어를 기반으로 아이들을 위한 책을 추천합니다. 사용자 쿼리에서 검색어를 추출하고, 그 중에서 적절한 아동 도서 3권을 선별하여 제공합니다.
에이전트는 사용자의 질문을 이해하고, JSON 형식으로 응답하는 역할을 수행합니다. 제공된 규칙을 엄격히 준수해야 합니다.
사용자는 책 검색어를 제공하거나 특정 주제나 연령대에 맞는 책을 요청할 수 있습니다. 예) "한국 전래 동화", "직업 동화"

에이전트는 다음과 같은 요청을 처리해야 합니다:

일반 정보 질문: 사용자가 특정 책에 대한 추천을 요청하면, 관련 정보를 제공해야 합니다.

일반 대화 응답: AI가 제공할 수 없는 질문이 들어올 경우, 가이드 메시지를 출력해야 합니다.

추천된 책은 다음 형식으로 사용자에게 제공하세요:
{
      "title": "판타지 동화 (33일동안 들려주는 안데르센의)",
      "author": "계림 닷컴 편집부",
      "publisher": "계림",
      "pubdate": "20060220",
      "description": "『판타지동화』는 1839년에 출판된 책입니다. 안데르센은 어린 시절의 친구 헨리에터 한크에게 보내는 편지에서 아라비안나이트를 쓰기 시작했다고 밝히고 있는데 그때 쓴 책이 바로 『판타지 동화』이지요. 안데르센이 들려주는 33가지 이야기는 안데르센이 직접 보고 들은 이야기라고 합니다. 신비하고 환상적인 이야기는 화려하고 화사한 그림을 보는 것 같은 느낌을 줍니다....",
      "isbn": "9788953308947",
      "price": "",
      "image": "<https://shopping-phinf.pstatic.net/main_3250898/32508989761.20221019140216.jpg>",
      "link": "<https://search.shopping.naver.com/book/catalog/32508989761>"
    },

추천 이유를 간략히 설명하고, 각 책의 특징을 한 문장으로 소개하세요.

Lambda

Naver 책 검색 api 호출하는 람다

응답 형식 잘 맞춰줘야 함. 안맞으면 응답 안옴. → 제일 삽질한 부분.

import json
import urllib.request
import boto3
from botocore.exceptions import ClientError
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
    try:
        # 네이버 API 키
        client_id = "571YeEEAPpAkp_lE_ZBM"
        client_secret = "w5UbpSzB8G"
        logger.info(f"Received event: {event}")
        # Bedrock 에이전트 요청 구조 처리
        action_group = event.get('actionGroup', '')
        api_path = event.get('apiPath', '')
        http_method = event.get('httpMethod', 'GET')
        message_version = event.get('messageVersion', '1.0')
        # 파라미터 리스트에서 값 추출
        parameters = event.get('parameters', [])
        query = ""
        display = 3
        # 리스트 형태의 파라미터 처리
        for param in parameters:
            if param.get('name') == 'query':
                query = param.get('value', '')
            elif param.get('name') == 'display':
                try:
                    display = int(param.get('value', 3))
                except ValueError:
                    display = 3
        logger.info(f"Extracted parameters - query: {query}, display: {display}")
        if not query:
            return {
                'messageVersion': message_version,
                'response': {
                    'actionGroup': action_group,
                    'apiPath': api_path,
                    'httpMethod': http_method,
                    'httpStatusCode': 400,
                    'responseBody': {
                        'application/json': {
                            'body': json.dumps({'error': '검색어가 필요합니다.'}, ensure_ascii=False)
                        }
                    }
                }
            }
        # 네이버 API 호출
        encQuery = urllib.parse.quote(query)
        url = f"<https://openapi.naver.com/v1/search/book.json?query={encQuery}&display={display}>"
        request = urllib.request.Request(url)
        request.add_header("X-Naver-Client-Id", client_id)
        request.add_header("X-Naver-Client-Secret", client_secret)
        response = urllib.request.urlopen(request)
        response_body = response.read()
        result = json.loads(response_body.decode('utf-8'))
        # 결과 포맷팅
        books = []
        for item in result.get('items', []):
            book = {
                'title': item.get('title', '').replace('<b>', '').replace('</b>', ''),
                'author': item.get('author', '').replace('<b>', '').replace('</b>', ''),
                'publisher': item.get('publisher', ''),
                'pubdate': item.get('pubdate', ''),
                'description': item.get('description', ''),
                'isbn': item.get('isbn', ''),
                'price': item.get('price', ''),
                'image': item.get('image', ''),
                'link': item.get('link', '')
            }
            books.append(book)
        logger.info(f"Found {len(books)} books")
        response_json = {
            'messageVersion': message_version,
            'response': {
                'actionGroup': action_group,
                'apiPath': api_path,
                'httpMethod': http_method,
                'httpStatusCode': 200,
                'responseBody': {
                    'application/json': {
                        'body': json.dumps(books, ensure_ascii=False)
                    }
                }
            }
        }
        logger.info(f"Returning response structure: {json.dumps(response_json, ensure_ascii=False)[:200]}...")
        return response_json
    except Exception as e:
        logger.error(f"Unhandled error: {str(e)}")
        # 오류 발생 시에도 응답 형식을 맞춤
        return {
            'messageVersion': event.get('messageVersion', '1.0'),
            'response': {
                'actionGroup': event.get('actionGroup', ''),
                'apiPath': event.get('apiPath', ''),
                'httpMethod': event.get('httpMethod', 'GET'),
                'httpStatusCode': 500,
                'responseBody': {
                    'application/json': {
                        'body': json.dumps({'error': f"오류가 발생했습니다: {str(e)}"}, ensure_ascii=False)
                    }
                }
            }
        }