KINTOテクノロジーズのブログ - TECH PLAY

TECH PLAY

KINTOテクノロジーズ

KINTOテクノロジーズ の技術ブログ

全1132件

This article is the entry for day 5 in the KINTO Technologies Advent Calendar 2024 🎅🎄 I'm Onuma, an Android engineer in the Mobile Development Group at KINTO Technologies. I primarily work on developing the My Route , a mobility service app. In this article, I'll guide you through the process of building Android Automotive OS and developing in-vehicle apps, including both Android Automotive and Android Auto applications. Running Android Automotive OS on a Raspberry Pi What is Android Automotive OS? Android Automotive is an in-vehicle platform built on Android and integrated into the AOSP framework. It supports pre-installed Android apps for IVI systems, as well as second- and third-party Android apps. For more details, refer to the official documentation →  https://developer.android.com/training/cars?hl=ja#automotive-os What is AOSP? AOSP stands for Android Open Source Project, and all the elements that make up the Android OS are open source and available to the public. Android Open Source Project The latest OS developed by Google is released as open source after a certain non-disclosure period. Device developers customize the released OS by adding features or making modifications to suit their specific needs before installing it on smartphones, tablets, and other devices. What to prepare to build Android Automotive OS PC *It is necessary to meet the hardware requirements mentioned later. Display *A touch monitor is better. RaspberryPi 4B MicroSD 16GB should be enough. MicroHDMI-HDMI cable Hardware Requirements for Building OS: Ubuntu 22.04 Intel Gold 6226R (16 cores, 32 threads) At least 16 GB of RAM HD: 1TB *Note: Building on Windows or Mac OS is not supported. I tried to create an environment on AWS EC2 to build, but I gave up since the Free Tier couldn't meet the required specifications. Set Up the Build Environment Install the necessary tools for building. sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig Add Repo and Local_Manifest Android OS consists of a large collection of source code. Repo is used to check out Android source code. The components are loosely coupled, with each one being managed and developed in an independent Git repository. Repo is a tool that manages these numerous Git repositories based on a management file called a Manifest file. ## Install the Repo launcher repo init -u [https://android.googlesource.​com/platform/manifest](https://android.googlesource.com/platform/manifest) -b android-13.0.0\_r35 --depth=1 ## Add the local_manifest git clone [https://github.com/grapeup/​aaos_local_manifest.git](https://github.com/grapeup/aaos_local_manifest.git) .repo/local\_manifests Add dav1d under line 46 <!-- FFmpeg --> of .repo/local_manifests/manifest_brcm_rpi4.xml Added missing dav1d library in the local manifest by jijith700 · Pull Request #5 · grapeup/aaos_local_manifest · GitHub ## Add missing dav1d library to local_manifest <!-- FFmpeg --> <project path="external/dav1d" name="raspberry-vanilla/android_external_dav1d" remote="github" revision="android-13.0" /> Compile . build/envsetup.sh lunch aosp_rpi4-userdebug make bootimage systemimage vendorimage -j$(nproc) Flashing and Deploying Images Clean the MicroSD card. sudo umount /dev/sdb* sudo wipefs -a /dev/sdb* sudo wipefs -a /dev/sdb Then create four partition tables and flash the images to the MicroSD card. There are three images to flash onto the microSD card: boot.img , system.img , and vendor.img . As I thought I could flash the images using the command sudo dd if=boot.img of=/dev/sdb1 bs=1M , I gave it a try, but the steps were too complicated. So, I used a partition editing tool called GParted . Boot Android Automotive OS Insert the microSD card into the Raspberry Pi to boot. It's convenient if you connect a touch monitor to the Raspberry Pi, you can operate it without a mouse. But I don't have a touch monitor, so I'm connecting a PC monitor instead ;-; Develop In-Vehicle Apps that Run on Android Auto and Android Automotive OS Next, I'll walk you through the basics of implementing and debugging in-vehicle apps on Android. While Android Auto works by connecting your smartphone to display apps on the car's screen, Android Automotive OS has Android built directly into the vehicle system, allowing apps to be installed directly. This time, I implemented a navigation app as a trial. The following development environment is a Mac. Supported app categories and corresponding Android APIs Category Description Corresponding Android API Media Apps for music, podcasts and audiobooks Use the MediaBrowserService to browse content and control playback. Use the MediaSession to notify the system of playback status and metadata. Navigation Turn-by-turn navigation with audio and visual guidance Use the NavigationManager from the CarAppLibrary to control navigation start, end, destination setting, and turn-by-turn guidance. Point of Interest (POI) Apps to find locations such as parking lots, EV charging spots, gas stations, etc. Use the PlaceClient to implement features such as finding locations, getting more information, and Place Autocomplete. Use the PlaceListMapTemplate from the CarAppLibrary to view POIs on a map. Messaging ( Android Auto only) Hands-free message replies with voice input Use the MessagingManager from the CarAppLibrary to control sending and receiving messages, voice input, and sending template messages. Game Apps for entertainment while parked Use the ScreenManager from the CarAppLibrary to view the game screen while parked. Use the InputManager to receive control inputs for your game. Browser & Video Browser integration and video playback features (specific to AAOS, often used while parked) Use the WebTemplate from the CarAppLibrary to display web content. Use the VideoTemplate to play video content. These templates are recommended for use only while parked. Supplement I've summarized the key points from the table in the Official Documentation . Since new categories are added every year, even if you can't widely release your app yet, there is a possibility it could be released in the future. CarAppLibrary is a Jetpack library for Android Auto and Android Automotive OS app development. PlaceClient is a client that uses the Google Places API. Desktop Head Unit (DHU) What is a DHU? DHU is a tool for emulating the Android Auto environment on a desktop. It allows you to simulate the in-vehicle experience without using an actual in-vehicle device. Why use DHU? You can test how the app operates and displays in an in-vehicle environment. It lets you debug and verify that your UI/UX complies with guidelines to avoid distracting drivers. Run the DHU The following are required to run the DHU: MacBook Android device Install the Android Auto Desktop Head Unit Emulator in the SDK Manager. Make sure there is a desktop-head-unit in Library/Android/sdk/extras/google/auto. Grant permissions to the desktop-head-unit. chmod +x ./desktop-head-unit Forward the socket connection to the same port number on the Android device. adb forward tcp:5277 tcp:5277 Open the Auto settings on your Android device. Tap [See all apps] > [Android Auto] > [Advanced] > [Additional settings in the app]. Tap the version and permission information about 10 times to enable developer mode. Run the DHU. ./desktop-head-unit --usb About Host When running an app created for Android Auto or Android Automotive in a compatible car, the app doesn't interact directly with the car. At this stage, the connection destination is the Android Auto app on the Android device. During the DHU installation process, connecting to the actual device via USB is necessary since it needs to communicate with the Android Auto app, which serves as the host. The Android Auto app is referred to as the host, and all Auto-compatible apps interact with this host. In the case of an Android Automotive-compatible car, the OS is built into the vehicle system itself, so Android Automotive acts as the host. Libraries CarAppLibrary is a Jetpack library for Android Auto and Android Automotive OS app development. Apps built using the CarAppLibrary run through the host app rather than running directly on Auto or Automotive. Declare the version of CarAppLibrary in the project-level build.gradle. buildscript { ext { car_app_library_version = '1.4.0' } } dependencies { ... implementation "androidx.car.app:app:$car_app_library_version" ... } Add services and sessions Add a class that inherits from CarAppService. You need to extend the CarAppService bound by the host. In the intent filter, you need to declare androidx.car.app.category.POI as the category for the car app. <service android:name="com.example.places.carappservice.PlacesCarAppService" android:exported="true"> <intent-filter> <action android:name="androidx.car.app.CarAppService" /> <category android:name="androidx.car.app.category.POI" /> </intent-filter> </service> The CarAppService abstract class cannot be overridden, such as onBind or onUnbind . The library takes care of proper interaction with the host app for you. You only need to implement createHostValidator and onCreateSession . The HostValidator returned by createHostValidator is referenced when the CarAppService is bound to verify that the host is trusted, and that the bind fails if it does not match the parameters defined by the host. ALLOW_ALL_HOSTS_VALIDATOR is a HostValidator that can only be used for validation. class PlacesCarAppService : CarAppService() { override fun createHostValidator(): HostValidator { return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR } override fun onCreateSession(): Session { return PlacesSession() } } Add the PlacesSession class. class PlacesSession : Session() { override fun onCreateScreen(intent: Intent): Screen { return MainScreen(carContext) } } Template You need to choose from predefined templates and implement them according to the guidelines. The UI and UX of car apps are restricted because the UI needs to be optimal for drivers. Reference: Official template documentation In addition, add the necessary permissions to access templates for displaying maps. <uses-permission android:name="androidx.car.app.MAP_TEMPLATES" /> List Location Information Once launched, locations will be listed. The UI can be implemented using Composable. Add a MainScreen that inherits Screen from CarAppLibrary. To display the list of locations and a map, return a PlaceListMapTemplate using onGetTemplate . Templates are implemented using the Builder Design Pattern. Pass the items to be listed using setItemList and build the template to return. Use ItemListBuilder to build the items to be listed. class MainScreen( carContext: CarContext, ) : Screen(carContext) { override fun onGetTemplate(): Template { val placesRepository = PlacesRepository() val itemListBuilder = ItemList.Builder() .setNoItemsMessage("No data") placesRepository.getPlaces() .forEach { itemListBuilder.addItem( Row.Builder() .setTitle(it.name) // Each item in the list must have a DistanceSpan added to the title or text line. .addText( SpannableString(" ").apply { setSpan( DistanceSpan.create( Distance.create(Math.random() * 100, Distance.UNIT_KILOMETERS), ), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE, ) }, ) .setOnClickListener { screenManager.push(DetailScreen(carContext = carContext, placeId = it.id)) } .setMetadata( Metadata.Builder() .setPlace( Place.Builder(CarLocation.create(it.latitude, it.longitude)) .setMarker(PlaceMarker.Builder().build()) .build(), ) .build(), ).build(), ) } return PlaceListMapTemplate.Builder() .setTitle("Places") .setItemList(itemListBuilder.build()) .build() } } View detailed location information Use PaneTemplate to implement the detail screen. class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) { private var isFavorite = false override fun onGetTemplate(): Template { val place = PlacesRepository().getPlace(placeId) ?: return MessageTemplate.Builder("Place not found") .setHeaderAction(Action.BACK) .build() val navigateAction = Action.Builder() .setTitle("Navigate") .setIcon( CarIcon.Builder( IconCompat.createWithResource( carContext, R.drawable.baseline_navigation_24 ) ).build() ) .setOnClickListener { carContext.startCarApp(place.toIntent(CarContext.ACTION_NAVIGATE)) } .build() val actionStrip = ActionStrip.Builder() .addAction( Action.Builder() .setIcon( CarIcon.Builder( IconCompat.createWithResource( carContext, R.drawable.baseline_favorite_24 ) ).setTint( if (isFavorite) CarColor.RED else CarColor.createCustom( Color.LTGRAY, Color.DKGRAY ) ).build() ) .setOnClickListener { isFavorite = !isFavorite // To capture updates to the screen state, call invalidate() to call `onGetTemplate` again. invalidate() }.build() ) .build() return PaneTemplate.Builder( Pane.Builder() .addAction(navigateAction) .addRow( Row.Builder() .setTitle("Coordinates") .addText("${place.latitude}, ${place.longitude}") .build() ).addRow( Row.Builder() .setTitle("Description") .addText(place.description) .build() ).build() ) .setTitle(place.name) .setHeaderAction(Action.BACK) .setActionStrip(actionStrip) .build() } } Launch the app Possible Errors When Trying to Launch Other Apps Caused by: androidx.car.app.HostException: Remote startCarApp call failed An error may occur when attempting to start navigation (where startCarApp is called). This is likely because a navigation app is not installed. You can easily find a navigation app by searching in the Play Store on the emulator. Vehicle Properties Available in the App Although not yet verified, the following properties should be available. The setting values may be adjustable in the emulator. Reference Vehicle Speed The current speed of the vehicle can be obtained. It is typically provided in km/h and is used for actions based on speed limits and driver assistance features. Fuel Level For gasoline vehicles, you can obtain the remaining fuel level in the tank. This can be used for features like "low fuel" warnings or suggestions for the nearest fuel station. Battery Level For EVs and Hybrids, you can monitor the state of the vehicle's battery. It is used to display charging status or remaining battery levels. Door Status The open/closed status of each door (front, rear, trunk, and hood) can be obtained. You can set up notifications when a door is left open or alerts to prevent forgetting to close it. Light Status The on/off status of the vehicle lights (headlights, high beams, fog lights, etc.) can be obtained. This allows for night mode switching and providing feedback to the driver. Engine Status The on/off/idling status of the engine can be obtained. The application can restrict certain actions when the engine is off. Parking Brake Status The status of whether the parking brake is applied or released can be obtained. This can be used to control app functionality and interactions while parked. Gear Position The position of the shift lever (Park, Reverse, Neutral, Drive, etc.) can be obtained. This allows for automatic activation of the back camera and interface changes based on the gear selection. Tire Pressure The tire information such as tire pressure can be obtained. This allows notification of low pressure warnings and maintenance alerts. External Temperature The external temperature can be obtained, allowing it to be used for weather-based interfaces or driver notifications based on driving conditions. Seat Occupancy Status The presence of passengers in each seat and seatbelt usage can be obtained. This is used to display warnings when seat belts are not fastened for safety reasons. Window Status The open/closed state of each window can be obtained. For example, a notification can be sent if a window is left open when the vehicle is turned off. HVAC Status The settings and status of the vehicle's air-conditioning system (heating, cooling, fan speed, and airflow direction) can be obtained. This allows the app to manage a comfortable in-car environment. GPS Location The vehicle's current GPS location can be obtained. This enables navigation apps and location-based services. Wiper Status The operational state of the wipers can be obtained. This helps adjust the UI based on weather and visibility conditions. Conclusion Thank you for reading to the end. Android Automotive OS The quality of the Android open source is well-maintained, making it easy enough for a beginner to clone, build, and boot it. However, the required PC specs are quite high. One of our engineers pointed out that boards with Android Automotive OS pre-installed are available worldwide, and I couldn't help but think, "Why didn’t you tell me sooner?" Nonetheless, getting the OS up and running was a highly rewarding experience. Auto and Automotive App Development This article ended up providing a broad overview of in-vehicle app development, but we discovered that the implementation process involves just a few steps. That said, the concepts of host app and emulator setup can be somewhat challenging to grasp. Since automotive app development doesn't allow for much UI customization, the real challenge and fun lie in refining what the app can do. In the future, as autonomous driving becomes mainstream, more categories may emerge, allowing drivers to enjoy gaming and other experiences. Bonus Reflecting on my childhood memories of driving, I was inspired by Hyuga-san's article to create AI-generated music. Here's what I came up with. It turned out pretty good and atmospheric. I suppose only my colleagues would stick around this long. Looking forward to hearing your thoughts! https://soundcloud.com/numami-775711983/5qclozsqk1mz
Continuous Delivery of Kubernetes Applications Using Only GitHub Actions Hello. My name is Narazaki, and I work in the Toyota Woven City Payment Solution Development Group. Our team is responsible for developing the payment infrastructure application for Woven by Toyota at Toyota Woven City . We build cross-functional payment solutions, covering everything from the backend to the web front end and mobile applications. The payment backend runs on Kubernetes and is developed using various cloud-native tools. This time, while following GitOps—an approach where infrastructure configuration files are managed and modified using Git, key to building and maintaining stable Kubernetes applications—we aim to implement the continuous delivery (CD) process using only GitHub Actions, instead of the commonly used cloud-native CD tools. The CD process in this setup is limited to: Applying changes to Kubernetes configuration files Updating the container image While there are more advanced CD strategies like Blue/Green and Canary deployments, this approach starts small. This setup is designed for teams that already have a DevOps workflow and want to continuously and efficiently deliver Kubernetes applications with minimal developers and no additional tools—using only GitHub Actions, which they already use daily. The repository assumes that both the application code and Kubernetes configuration management files are maintained in the same repository. (Technically, it might be possible to run this across repositories depending on permission settings, but let’s not get into that here.) For GitLab users, there’s an excellent tool called Auto DevOps , so this isn’t a 'GitHub and GitHub Actions are the best!' kind of post. But don’t worry, I’m not making that claim! Cloud-Native CI/CD Tools for Kubernetes What tools come to mind when you think of CI/CD for Kubernetes applications? Argo CD Flux CD PipeCD Tekton And so on. Both tools are powerful and highly useful for leveraging Kubernetes to its full potential. They also allow for flexible and secure updates to Kubernetes configuration files and application images, enabling GitOps practices. On the other hand, they require tool-specific knowledge and expertise. For smaller teams without dedicated DevOps specialists, maintaining them continuously can be a challenge—wouldn’t you agree? Running a CD tool itself requires Kubernetes, and the tool also needs Kubernetes configuration files to manage those same configuration files. In this article, we‘ll explore how to set up the pipeline shown in the figure below using only GitHub Actions. Kubernetes runs on a generic cluster, not tied to any specific cloud provider. This setup requires a container registry. The configuration management file uses Kustomize as an example, but it can be applied to other tools like Helm, Terraform, and more. flowchart TD A[Change Code] -->| Run Build Pipeline | B[Build and Push Container Image] B -->| Trigger pipeline to update container images | C[Create pull request with updated container image references] C -->| Review pull request | D[Deploy new container image to Kubernetes] linkStyle default stroke-width:2px,color:blue,stroke-dasharray:0 Demo Consider a repository that includes folders for both Kubernetes configuration files and applications. The folder structure is as follows: This section omits specific code, Dockerfile contents, and application source code. ├── .github │   ├── actions │   │   └── image-tag-update │   │   └── action.yaml │   └── workflows │   ├── build-go.yaml │   ├── build-java.yaml │   ├── build-node.yaml │   └── kubectl.yaml ├── go-app │   ├── src/ │   └── Dockerfile ├── java-app │   ├── src/ │   └── Dockerfile ├── k8s │   ├── service-go.yaml │   ├── service-java.yaml │   ├── service-node.yaml │   └── kustomization.yaml └── node-app    ├── src/ └── Dockerfile Each application follows the structure below: apiVersion: apps/v1 kind: Deployment metadata: name: app spec: ... template: ... spec: containers: - name: some-server image: Go-placeholder # put the same string as in kcustomization as a placeholder All placeholders are centrally managed in kustomization.yaml: apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: techblog resources: - service-go.yaml - service-java.yaml - service-node.yaml images: - name: go-placeholder newName: go-app newTag: v1.1.1 - name: java-placeholder newName: java-app newTag: v2.7.9alpha - name: node-placeholder newName: node-app newTag: latest First, to apply the Kubernetes configuration file, configure the following GitHub Actions workflow. name: kubectl on: pull_request: branches: - "**" paths: - "K8s/**" the location of the #Kubernetes manifest file push: branches: - main paths: - "k8s/**" jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: azure/setup-kubectl@v4 - env: KUBECONFIG_CONTENTS: ${ secrets.KUBECONFIG_contents }} # Put kubeconfig in GitHub secrets beforehand run: | echo "${KUBECONFIG_CONTENTS}" > $HOME/.kube/config chmod 600 $HOME/.kube/config - run: kubectl apply --dry-run=server -k ./k8s >> $GITHUB_STEP_SUMMARY - if: Github.ref == 'refs/heads/main # Changes are actually applied only on the main branch run: kubectl apply -k k8s/ This pipeline applies a standard Kubernetes configuration when using a kubeconfig with administrator privileges. Adjust the method of retrieving the kubeconfig based on the cluster’s configuration approach, such as for different cloud environments. Next, automatically create a pull request when pushing an application, or set up a composite action to update the container’s image tags. name: image-tag-update description: 'Task to update image tags in kustomization when container images are updated. inputs: target_app: description: 'Target applications, required: true tag_value: description: 'New Container Image Tag ' required: true token: description: 'Tokens with PR and content update privileges. required: true runs: using: 'composite' steps: - uses: actions/checkout@v4 id: check-branch-exists continue-on-error: true with: ref: "Image-tag-update" # Default branch name for tag updates - uses: Actions/checkout@v4 # Checkout cannot fall back to the default branch if the specified branch is missing if: steps.check-branch-exists.outcome == 'failure' with: ref: main - uses: Mikefarah/yq@master # Replace the value of the target placeholder tag with yq with: cmd: yq eval '(.images[] | select(.name == "'"${{ inputs.target_app }}-placeholder"'")).newTag = "'"${{ inputs.tag_value }}"'"' -i k8s/kustomization.yaml - uses: peter-evans/create-pull-request@v6 if: steps.check-branch-exists.outcome == 'failure' # Create a new pull request if no pull requests exist with: title: 'Update Container Image. body: | Update `${{ inputs.target_app }}' branch: "image-tag-update" - uses: stefanzweifel/git-auto-commit-action@v5 if: steps.check-branch-exists.outcome == 'success' # Add a commit to the existing branch if checkout succeeds with: commit_message: "Image update for ${{ inputs.target_app }}" The composite runs during image creation for each application. For multiple applications, it’s a good idea to add them after each image is created. ... - uses: docker/setup-buildx-action@v3 - uses: docker/build-push-action@v6 with: file: ./Dockerfile push: true tags: ${ env.tag }} # some tag - uses: ./.github/actions/image_update if: github.ref == 'refs/heads/main' with: target_app: go tag_value: ${{ env.tag }} token: ${ secrets.GITHUB_token }} # A github token with content and Pull Request editing privileges When the application runs,the container image updates automatically, letting you deploy a new one with a Pull Request! (Tag derivation is handled by your workflow.) The example below shows the minor version increment.) - name: go-placeholder newName: go-app - newTag: v1.1.1 + newTag: v1.1.2 Operational considerations Timing of deployment Image update Pull Request deploy immediately upon merging. If you want to release updates along with infrastructure changes, you can either add the fixes to the same branch or merge them when the timing is right. Add a new container application For example, if you add a Python application in the above setup while an image update Pull Request is still open, updating the Python image tag won’t take effect unless the Pull Request includes the latest changes. Cut back It’s easy to undo—just revert the commit. Timing of Reconcile While many GitOps Tools offer near real-time reconciliation to minimize drift, this method only works when the CD pipeline is running. It’s important to choose the right tool based on the number of teammates and their permissions to update the Kubernetes cluster. You’re interacting with the Container Registry indirectly. While some retrieve the latest container image directly from the container registry, this approach operates differently. It's advisable to include a verification step for each container registry to ensure the container exists. About permission settings for GitHub Actions You’ll need update permissions for contents and pull-requests . Set permissions in Actions settings, GitHub Apps, and more. Learn more here . Overwritten by a container image that was executed later The CD tool determines the newer version by checking the container image tag, following conventions like Semantic Versioning. The workflow above will overwrite the image tag in the later-executed pipeline, regardless of the tag’s value. If this behavior is an issue, consider checking the value before deciding whether to overwrite it. Summary With this approach, GitOps can be fully managed on GitHub, enabling a simple and efficient continuous delivery process for Kubernetes applications. Since CD tool errors can also be consolidated in GitHub Actions, it’s convenient to check execution results and errors just like in the usual CI process. Kubernetes offers a wide range of tools, making selection challenging. However, by choosing the right tools for my workflow, I aim to improve the productivity of Kubernetes application development.
KINTO ID Platformチームの Xu Huang です。数年前から複数の国にユーザー認証認可システム(略称UserPool)を提供し、Brokerモデルを採用して複数地域のUserpoolを繋ぎ、お互いに認証認可情報を共有できるアーキテクチャを構築して運用していました。昨年からコスト削減活動の一環としてアーキテクチャの設計を見直し、移行を行いましたので、その変更内容について紹介したいと思います。 最初はGlobal展開の戦略でAWS Aurora Global Database(略称Global DB)採用し、アクセス負荷とレイテンシーを減らすためにSlave DBをUserpoolに近いところに配置してBrokerサーバーもSlave DBと同じリージョンに配置した運用にしていました。 (Global DB制約上MasterDB一つだけ、Slave DB最大五つまで許容) 上記の図に青枠に囲んだ地域に順次Userpoolサービスを提供し、ユーザーを一元化管理するために必要なユニークIDは集約したところから発行し、各リージョンのサブDBに同期して管理する方針で設計しました Phase 1 Global DB → Normal DBに変更し、書込み専用アプリ廃棄 アクセス負荷を重要視した設計で複数のリージョンにサーバー配置しましたが実際運用上はまだスケールアップが必要なレベルまで至っておらずコストが余計に掛かっている状態でした。 適切な構成にする為に、検証評価したところGlobal DBの必要性がないと判断した為、直接Brokerから共通DBにR&W可能の設計に変更しました。 変更後のアーキテクチャの以下の図のイメージになります Phase 2 Broker一本化 Phase 1 の対応でかなりコスト削減できましたがさらにコスト下げる検討し続け、Brokerが一つに集約できないかを検討し始めました。但し集約するには一つ課題あって、IDプロバイダーとしては外部サードパーティにもリダイレクトURL提示していてそれらが変更してしまうとサードパーティ側も合わせて変更作業発生するのでドメインを変更しない前提で移行できないか考えてみました。インフラチームにも協力してもらい、Route53にDNS設定を変更して向き先を新しい統合サーバーと繋ぐCloudFrontにスイッチしておけばドメインを変えなくてもいいと考えていました。 上記の図のような設計に変更すると、物理的にサーバー間通信の距離が離れているためUserpoolから集約したBrokerの通信にはレイテンシーどれぐらい影響あるかも気になって測ってみました。 結果はUserPoolからBrokerの通信は約10%遅くなりましたが、BrokerがDBと同じリージョンに配置したため速くなり、アーキテクチャ変更前後の全体から見るとあんまり変わらないため、Phase2の移行計画も立て進めてきました。 成果: 上記2段階でビジネスの実態に即して構成の最適化を行いました 今後は機能的なところも見直し作業継続して定期的にコスト削減の活動を行なって参ります。
This article is part of day 6 of KINTO Technologies Advent Calendar 2024 . 🎅🎄 Introduction Merry Christmas 🔔. I am Romie ( @Romie_ktc ) from Osaka Tech Lab, where I work on Android-side development for the my route app in the Mobile App Development Group. In the my route Android team, we are currently switching the UI implementation from XML over to Jetpack Compose (hereinafter, Compose). However, since we cannot refactor everything all in one go, there will inevitably be situations where parts converted to Compose will be layered on top of XML. In this article, how we implemented it by overlaying Compose on top of the BottomSheet XML. What the finished result looks like :::message alert This article is about implementing things while in the process of refactoring. Implementation everything entirely in either XML or Compose is recommended. ::: Basics The implementation is done with the following classes, which inherit BottomSheetDialogFragment. class MixComposedBottomSheetDialog : BottomSheetDialogFragment() Set the Basic Behavior of the BottomSheet. Here, we set the behavior of the BottomSheet. The following code is put in onCreateView. dialog?.setOnShowListener { dialogInterface -> val bottomSheetDialog = dialogInterface as BottomSheetDialog val bottomSheet = bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) // If you want to set rounded corners and a background color, follow the steps below context?.let { bottomSheet?.background = ContextCompat.getDrawable(it, R.drawable.background_map_bottom_sheet) } val bottomSheetBehavior = bottomSheet?.let { BottomSheetBehavior.from(it) } bottomSheetBehavior?.let { behavior -> // Set maxHeight and peekHeight to whatever heights you want. behavior.maxHeight = EXPANDED_HEIGHT // Set the height for when the BottomSheet is expanded as far as it will go behavior.peekHeight = COLLAPSED_HEIGHT // Set the height for when the BottomSheet is displayed in its initial state behavior.isHideable = false behavior.isDraggable = true } } Compose By returning ComposeView via onCreateView, you can put Compose on top of BottomSheetDialogFragment. return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { BottomSheetContents() } } If this is all you want to do, then it is easy enough to understand. However, if you want to add a button that will always be at the bottom of the bottom sheet, things get trickier. Developing Things Further The button itself is implemented with Compose. However, adding a button in this way means it will not be displayed unless you scroll through the contents. return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { BottomSheetContents() ButtonOnBottomSheet() } } To ensure that the button will always be anchored to the bottom of the bottom sheet and will not get pulled about even by scrolling through the contents, you need to implement something like the following: val button = ComposeView(context ?: return@setOnShowListener).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { ButtonOnBottomSheet() } } To anchor the button to the bottom of the bottom sheet, use the following code. Using this code enables you to directly retrieve the layout implemented with BottomSheetDialogFragment. Consequently, it enables you to manipulate views more flexibly. val containerLayout = dialogInterface.findViewById<FrameLayout>(com.google.android.material.R.id.container) val coordinatorLayout = dialogInterface.findViewById<CoordinatorLayout>(com.google.android.material.R.id.coordinator) clipChildren is a property of ViewGroup that specifies whether to clip the drawing of a child view if it will be drawn outside the boundary of the parent view. It will be used when something overlaps other elements of the bottom sheet. // The default value is true, and setting it to false lets you display child views as is even if they go outside the boundary of the parent. button.clipChildren = false button.layoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, ).apply { gravity = Gravity.BOTTOM } containerLayout?.addView(button) button.post { val layoutParams = coordinatorLayout?.layoutParams as? ViewGroup.MarginLayoutParams layoutParams?.apply { button.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), ) this.bottomMargin = button.measuredHeight containerLayout?.requestLayout() } } Summary Summarizing the implementation so far, we have the following: override fun onCreateView(): View { dialog?.setOnShowListener { dialogInterface -> val bottomSheetDialog = dialogInterface as BottomSheetDialog val containerLayout = dialogInterface.findViewById<FrameLayout>(com.google.android.material.R.id.container) val coordinatorLayout = dialogInterface.findViewById<CoordinatorLayout>(com.google.android.material.R.id.coordinator) val bottomSheet = bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) context?.let { bottomSheet?.background = ContextCompat.getDrawable(it, R.drawable.background_map_bottom_sheet) } val button = ComposeView(context ?: return@setOnShowListener).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { ButtonOnBottomSheet() } } button.clipChildren = false button.layoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, ).apply { gravity = Gravity.BOTTOM } containerLayout?.addView(button) button.post { val layoutParams = coordinatorLayout?.layoutParams as? ViewGroup.MarginLayoutParams layoutParams?.apply { button.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), ) this.bottomMargin = button.measuredHeight containerLayout?.requestLayout() val bottomSheetBehavior = bottomSheet?.let { BottomSheetBehavior.from(it) } bottomSheetBehavior?.let { behavior -> // Set maxHeight and peekHeight to whatever heights you want. behavior.maxHeight = EXPANDED_HEIGHT // Set the height for when the BottomSheet is expanded as far as it will go behavior.peekHeight = COLLAPSED_HEIGHT // Set the height for when the bottom sheet is displayed in its initial state behavior.isHideable = false behavior.isDraggable = true } } } } return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { BottomSheetContents() } } } In other words, here's how we implemented it: Create a BottomSheet in xml. Adjust the layout (i.e., the heights and so on). In order to attach the button to the bottom of the BottomSheet, prepare some more layout details and combine everything together. It means that we overlaid the Compose content onto the BottomSheet. Thank you for reading all the way to the end!
こんにちは! KINTOテクノロジーズ(以下、KTC)の生成AI活用PJTで生成AIエバンジェリストをしている和田( @cognac_n )です。 Azure AI Foundryを用いてDeepSeek R1を試してみた 1. はじめに 最近、大規模言語モデル(LLM)の進化がめざましく、さまざまな企業が独自のLLMを発表しています。その中でも注目を集めているのが、中国のAI企業DeepSeekが開発した DeepSeek R1 です。 2025年1月30日、MicrosoftはDeepSeek R1モデルを Azure AI Foundry とGitHubで提供開始しました。そこで本記事では、Azure AI Foundryを使ってDeepSeek R1を実際に試してみた際の流れや感想をまとめてみます。 2. Azure AI Foundryとは Azure AI Foundry は、Microsoftが提供する 包括的なAI開発プラットフォーム です。開発者が生成AIソリューションやカスタムコパイロットを 構築・評価・展開 するためのツールとサービスを一元的に提供してくれます。 主な特徴 多様なAIモデルの活用: MicrosoftやOpenAI、Hugging Face、Meta、Mistralなど、パートナー企業から提供される幅広い最先端モデル・オープンソースモデルをサポート。今回のDeepSeek R1もこの一環として提供されました。 統合されたAIツールチェーン: 開発ライフサイクル全体を加速させるためのSDKやAPI、ポータルが用意されており、データ前処理からモデル推論、継続的モニタリングまで一貫した体験が得られます。 責任あるAIの実践: 評価機能や安全フィルター、セキュリティ制御を組み込み、AIの信頼性と透明性を高める仕組みを提供します。 エンタープライズ向けのスケーラビリティ: Azureのマネージドインフラ上で高い可用性と拡張性が確保されており、企業レベルのモニタリング・ガバナンスをサポートします。 @ card Azure AI Foundry ポータルでモデル カタログを通じてモデルを使用する場合、プロンプトや出力がMicrosoftやモデル プロバイダーと共有されることはないと記載されており、様々なモデルをセキュアに利用することが可能です。 Microsoft では、ホスティング インフラストラクチャと API エンドポイントを提供および管理します。 この "サービスとしてのモデル" (MaaS) シナリオでホストされるモデルは、Azure のデータ、プライバシー、セキュリティのコミットメントの対象です。 Azure AI Foundry に適用できる Azure コンプライアンス認証の詳細を確認してください。 Microsoft は、従量課金制推論用にデプロイされたモデル (MaaS) により送信および生成されるプロンプトと出力のデータ プロセッサとして機能します。 Microsoft では、これらのプロンプトと出力をモデル プロバイダーと共有しません。 また、Microsoft は、Microsoft、モデル プロバイダー、またはサード パーティのモデルをトレーニングまたは改善するために、これらのプロンプトと出力を使用しません。 @ card 3. Azure AI Foundry上でのDeepSeek R1環境構築 ここからは、実際にDeepSeek R1をAzure AI Foundry上で利用するための手順を紹介します。なお、細かい画面遷移や設定項目については現時点(2025/1/31)の仕様となります。 UIや操作の変更が頻繁に行われるため、可能な限り公式ドキュメントを引用する形とします。 前提準備・アカウント設定 Azureアカウントの用意 Azure AI Foundryへのアクセス @ card DeepSeek R1の導入手順 プロジェクトの作成: Azure AI Foundryポータル上で新規プロジェクトを作成します。DeepSeek-R1が使用可能なリージョンは[eastus2, westus3, northcentralus, eastus, southcentralus, westus]です。(2025/1/31時点) @ card DeepSeek R1モデルの選択: 「モデルカタログ」からDeepSeek R1を探し、モデルページに遷移します。 デプロイ名の設定と価格の確認: 任意のデプロイ名を設定します コンテンツフィルターのON/OFFを設定します 2025/1/31時点で、DeepSeek-R1は無料提供されています。料金は今後変更されることが予想されるため、最新の情報を逐次チェックしましょう。 DeepSeek R1 use is currently priced at $0, and use is subject to rate limits which may change at any time. Pricing may change, and your continued use will be subject to the new price. The model is in preview; a new deployment may be required for continued use. 4. デプロイとエンドポイント構築: Azure AI Foundryのワークスペース内で自動的にエンドポイントが生成され、モデルを呼び出すURIやAPIキーが発行されます。 4. DeepSeek R1を使った簡単なデモ ここでは、実際にDeepSeek R1の推論を試すためのコード例を紹介します。 必要なライブラリをインストールします。 pip install azure-ai-inference APIを利用するコードの例を以下に記載します。 Azure AI Foundryではコードサンプルも提供されるため、トライが非常に簡単です。 from azure.ai.inference import ChatCompletionsClient from azure.ai.inference.models import SystemMessage, UserMessage from azure.core.credentials import AzureKeyCredential client = ChatCompletionsClient( endpoint="<YOUR_TARGET_URI>", credential=AzureKeyCredential("<YOUR_KEY>"), ) deploy_name = "<YOUR_DEPLOY_NAME>" response = client.complete( messages=[ SystemMessage(content="You are a helpful assistant."), UserMessage( content="企業における生成AI利活用を推進する方法を考えてください。日本語で回答してください。" ), ], max_tokens=2048, model=deploy_name, ) print(response.choices[0].message.content) 実際の応答例 <think> まず、問題は何かを理解する必要があるね。企業が生成AIを使う推進方法について考えなきゃいけないんだ。 生成AIといってもいろんな用途があるから、まずは企業内での現状を把握しなきゃ。どの部門がAIを活用できそうか、どのような課題があるのかを調べる必要があるだろう。例えば、マーケティングや顧客サポート、業務自動化などが考えられるね。 次に、教育や研修が大事だと思う。従業員がAIの使い方を学べる機会を提供しないと、実際に活用するのは難しい。特に、AIに対する不安や理解不足を解消するため、ワークショップやトレーニングプログラムを実施した方がいいかも。 また、適切なツールの選定も必要。自社のニーズに合った生成AIのプラットフォームやツールを選ぶことが重要。場合によっては、カスタマイズが必要かもしれないし、セキュリティやデータプライバシーの問題にも対応しなきゃいけない。 管理層のサポートも不可欠だよね。経営陣が推進しないと予算やリソースが確保できない。AI導入のメリットを明確に伝えて、経営層の理解を得る必要がある。 さらに、成功事例を作ることが効果的。一部の部門で試験的に導入して成果を出せば、他の部門にも広がりやすい。結果を共有して、社内全体に広げていく戦略がいいかもしれない。 あと、倫理的なガイドラインやポリシーを整備することも重要。AIの誤用を防ぎ、責任ある活用を促すため、明確なルールを作る必要があるだろう。 継続的な評価と改善も忘れちゃいけない。導入後も効果を測定し、フィードバックを元に改善を重ねることで、持続的な推進が可能になる。 そういったポイントを整理して、具体的なステップを考えていく必要がありそうだな。 </think> 企業における生成AIの利活用を推進するためには、以下のステップを体系的に進めることが効果的です。 --- ### **1. 現状分析と目標設定** - **ニーズの特定**: 各部門(企画、営業、開発、サポートなど)の業務課題を洗い出し、生成AIが解決できる領域(ドキュメント作成、データ分析、顧客対応等)を明確化します。 - **優先順位付け**: 効果が高く実現可能性の高いユースケース(例:問い合わせ自動返信、報告書自動作成)から優先的に導入します。 --- ### **2. 体制構築と意識改革** - **推進チーム発足**: 経営層・IT部門・各部門の代表者が連携し、予算・リソースを確保します。 - **教育プログラムの実施**: - **基礎理解**: 生成AIの仕組み・メリット・倫理リスク(情報漏洩、バイアス等)を研修で共有。 - **実践トレーニング**: プロンプト設計や出力検証の方法をハンズオン形式で習得させます。 --- ### **3. ツール選定とテスト運用** - **自社に適したAI選び**: - オープンソース(Llama 2等)/クラウドサービス(ChatGPT Enterprise、Google Vertex AI等)を比較。 - セキュリティ基準(データ暗号化、学習済みモデルのカスタマイズ可否)を軸に選定します。 - **PoC(概念実証)**: 限定範囲でテスト運用し、精度・業務効率化効果を検証します。 --- ### **4. ガバナンス整備** - **利用ガイドライン策定**: - 機密情報の入力禁止ルールや出力内容のダブルチェック体制を確立。 - 著作権・プライバシーに関する法的リスクに対応します。 - **モニタリング体制**: AIの出力品質や倫理面を定期的に監視し、改善を継続します。 --- ### **5. 社内文化の醸成** - **成功事例の共有**: 効果が出た部門のケースを社内発表やレポートで周知し、モチベーションを向上させます。 - **インセンティブ設計**: AI活用で業務効率化を達成したチームを表彰・報酬化します。 --- ### **6. 外部連携と進化対応** - **ベンダー・専門家との連携**: 最新技術動向の情報収集やカスタマイズ支援を受けます。 - **AIの継続的アップデート**: モデルの精度向上や規制変化に応じて活用方法を見直します。 --- ### **ポイント** - **経営層のコミットメント**: 投資対効果(ROI)の試算を示し、長期的な展望で支持を得ることが不可欠です。 - **現場主導の改善**: ユーザーである従業員のフィードバックを活用し、ツール改善やプロセス最適化を重ねます。 生成AIは「ツール」であり、活用の成否は「人間の使い方」に依存します。技術導入だけでなく、組織全体のデジタルリテラシー向上と心理的安全性の確保が鍵となります。 5. 実際に使ってみた感想・所感 レスポンス: 今回の回答には4分程度かかりました。LLM自体の推論にかかる時間と、利用が集中しているが故の処理時間の長さ、両方の影響がありそうです 出力精度: Thinkingを行う推論モデル故に抽象度が高かったり複雑度の高いプロンプトに対しても深い洞察を感じられる回答が返ってきます API利用では思考過程が タグに囲まれた形式で出力されます。これは使いやすいですね タグの中身を見ていくと、まるで複数人でディスカッションが行われたような思考過程を辿っています まとめ: Azure AI Foundryを用いると、最新のサードパーティ製モデルを手軽に試すことが可能です 「まず試す」ことが重要な生成AI領域において、この手軽さは非常にありがたいですね We Are Hiring! KINTOテクノロジーズでは、事業における生成AIの活用を推進する仲間を探しています。まずは気軽にカジュアル面談からの対応も可能です。少しでも興味のある方は以下のリンク、または XのDM などからご連絡ください。お待ちしております!! @ card ここまでお読みいただき、ありがとうございました!
こんにちは! KINTOテクノロジーズ(以下、KTC)の生成AI活用PJTで生成AIエバンジェリストをしている和田( @cognac_n )です。 Azure AI Foundryを用いてDeepSeek R1を試してみた 1. はじめに 最近、大規模言語モデル(LLM)の進化がめざましく、さまざまな企業が独自のLLMを発表しています。その中でも注目を集めているのが、中国のAI企業DeepSeekが開発した DeepSeek R1 です。 2025年1月30日、MicrosoftはDeepSeek R1モデルを Azure AI Foundry とGitHubで提供開始しました。そこで本記事では、Azure AI Foundryを使ってDeepSeek R1を実際に試してみた際の流れや感想をまとめてみます。 2. Azure AI Foundryとは Azure AI Foundry は、Microsoftが提供する 包括的なAI開発プラットフォーム です。開発者が生成AIソリューションやカスタムコパイロットを 構築・評価・展開 するためのツールとサービスを一元的に提供してくれます。 主な特徴 多様なAIモデルの活用: MicrosoftやOpenAI、Hugging Face、Meta、Mistralなど、パートナー企業から提供される幅広い最先端モデル・オープンソースモデルをサポート。今回のDeepSeek R1もこの一環として提供されました。 統合されたAIツールチェーン: 開発ライフサイクル全体を加速させるためのSDKやAPI、ポータルが用意されており、データ前処理からモデル推論、継続的モニタリングまで一貫した体験が得られます。 責任あるAIの実践: 評価機能や安全フィルター、セキュリティ制御を組み込み、AIの信頼性と透明性を高める仕組みを提供します。 エンタープライズ向けのスケーラビリティ: Azureのマネージドインフラ上で高い可用性と拡張性が確保されており、企業レベルのモニタリング・ガバナンスをサポートします。 @ card Azure AI Foundry ポータルでモデル カタログを通じてモデルを使用する場合、プロンプトや出力がMicrosoftやモデル プロバイダーと共有されることはないと記載されており、様々なモデルをセキュアに利用することが可能です。 Microsoft では、ホスティング インフラストラクチャと API エンドポイントを提供および管理します。 この "サービスとしてのモデル" (MaaS) シナリオでホストされるモデルは、Azure のデータ、プライバシー、セキュリティのコミットメントの対象です。 Azure AI Foundry に適用できる Azure コンプライアンス認証の詳細を確認してください。 Microsoft は、従量課金制推論用にデプロイされたモデル (MaaS) により送信および生成されるプロンプトと出力のデータ プロセッサとして機能します。 Microsoft では、これらのプロンプトと出力をモデル プロバイダーと共有しません。 また、Microsoft は、Microsoft、モデル プロバイダー、またはサード パーティのモデルをトレーニングまたは改善するために、これらのプロンプトと出力を使用しません。 @ card 3. Azure AI Foundry上でのDeepSeek R1環境構築 ここからは、実際にDeepSeek R1をAzure AI Foundry上で利用するための手順を紹介します。なお、細かい画面遷移や設定項目については現時点(2025/1/31)の仕様となります。 UIや操作の変更が頻繁に行われるため、可能な限り公式ドキュメントを引用する形とします。 前提準備・アカウント設定 Azureアカウントの用意 Azure AI Foundryへのアクセス @ card DeepSeek R1の導入手順 プロジェクトの作成: Azure AI Foundryポータル上で新規プロジェクトを作成します。DeepSeek-R1が使用可能なリージョンは[eastus2, westus3, northcentralus, eastus, southcentralus, westus]です。(2025/1/31時点) @ card DeepSeek R1モデルの選択: 「モデルカタログ」からDeepSeek R1を探し、モデルページに遷移します。 デプロイ名の設定と価格の確認: 任意のデプロイ名を設定します コンテンツフィルターのON/OFFを設定します 2025/1/31時点で、DeepSeek-R1は無料提供されています。料金は今後変更されることが予想されるため、最新の情報を逐次チェックしましょう。 DeepSeek R1 use is currently priced at $0, and use is subject to rate limits which may change at any time. Pricing may change, and your continued use will be subject to the new price. The model is in preview; a new deployment may be required for continued use. 4. デプロイとエンドポイント構築: Azure AI Foundryのワークスペース内で自動的にエンドポイントが生成され、モデルを呼び出すURIやAPIキーが発行されます。 4. DeepSeek R1を使った簡単なデモ ここでは、実際にDeepSeek R1の推論を試すためのコード例を紹介します。 必要なライブラリをインストールします。 pip install azure-ai-inference APIを利用するコードの例を以下に記載します。 Azure AI Foundryではコードサンプルも提供されるため、トライが非常に簡単です。 from azure.ai.inference import ChatCompletionsClient from azure.ai.inference.models import SystemMessage, UserMessage from azure.core.credentials import AzureKeyCredential client = ChatCompletionsClient( endpoint="<YOUR_TARGET_URI>", credential=AzureKeyCredential("<YOUR_KEY>"), ) deploy_name = "<YOUR_DEPLOY_NAME>" response = client.complete( messages=[ SystemMessage(content="You are a helpful assistant."), UserMessage( content="企業における生成AI利活用を推進する方法を考えてください。日本語で回答してください。" ), ], max_tokens=2048, model=deploy_name, ) print(response.choices[0].message.content) 実際の応答例 <think> まず、問題は何かを理解する必要があるね。企業が生成AIを使う推進方法について考えなきゃいけないんだ。 生成AIといってもいろんな用途があるから、まずは企業内での現状を把握しなきゃ。どの部門がAIを活用できそうか、どのような課題があるのかを調べる必要があるだろう。例えば、マーケティングや顧客サポート、業務自動化などが考えられるね。 次に、教育や研修が大事だと思う。従業員がAIの使い方を学べる機会を提供しないと、実際に活用するのは難しい。特に、AIに対する不安や理解不足を解消するため、ワークショップやトレーニングプログラムを実施した方がいいかも。 また、適切なツールの選定も必要。自社のニーズに合った生成AIのプラットフォームやツールを選ぶことが重要。場合によっては、カスタマイズが必要かもしれないし、セキュリティやデータプライバシーの問題にも対応しなきゃいけない。 管理層のサポートも不可欠だよね。経営陣が推進しないと予算やリソースが確保できない。AI導入のメリットを明確に伝えて、経営層の理解を得る必要がある。 さらに、成功事例を作ることが効果的。一部の部門で試験的に導入して成果を出せば、他の部門にも広がりやすい。結果を共有して、社内全体に広げていく戦略がいいかもしれない。 あと、倫理的なガイドラインやポリシーを整備することも重要。AIの誤用を防ぎ、責任ある活用を促すため、明確なルールを作る必要があるだろう。 継続的な評価と改善も忘れちゃいけない。導入後も効果を測定し、フィードバックを元に改善を重ねることで、持続的な推進が可能になる。 そういったポイントを整理して、具体的なステップを考えていく必要がありそうだな。 </think> 企業における生成AIの利活用を推進するためには、以下のステップを体系的に進めることが効果的です。 --- ### **1. 現状分析と目標設定** - **ニーズの特定**: 各部門(企画、営業、開発、サポートなど)の業務課題を洗い出し、生成AIが解決できる領域(ドキュメント作成、データ分析、顧客対応等)を明確化します。 - **優先順位付け**: 効果が高く実現可能性の高いユースケース(例:問い合わせ自動返信、報告書自動作成)から優先的に導入します。 --- ### **2. 体制構築と意識改革** - **推進チーム発足**: 経営層・IT部門・各部門の代表者が連携し、予算・リソースを確保します。 - **教育プログラムの実施**: - **基礎理解**: 生成AIの仕組み・メリット・倫理リスク(情報漏洩、バイアス等)を研修で共有。 - **実践トレーニング**: プロンプト設計や出力検証の方法をハンズオン形式で習得させます。 --- ### **3. ツール選定とテスト運用** - **自社に適したAI選び**: - オープンソース(Llama 2等)/クラウドサービス(ChatGPT Enterprise、Google Vertex AI等)を比較。 - セキュリティ基準(データ暗号化、学習済みモデルのカスタマイズ可否)を軸に選定します。 - **PoC(概念実証)**: 限定範囲でテスト運用し、精度・業務効率化効果を検証します。 --- ### **4. ガバナンス整備** - **利用ガイドライン策定**: - 機密情報の入力禁止ルールや出力内容のダブルチェック体制を確立。 - 著作権・プライバシーに関する法的リスクに対応します。 - **モニタリング体制**: AIの出力品質や倫理面を定期的に監視し、改善を継続します。 --- ### **5. 社内文化の醸成** - **成功事例の共有**: 効果が出た部門のケースを社内発表やレポートで周知し、モチベーションを向上させます。 - **インセンティブ設計**: AI活用で業務効率化を達成したチームを表彰・報酬化します。 --- ### **6. 外部連携と進化対応** - **ベンダー・専門家との連携**: 最新技術動向の情報収集やカスタマイズ支援を受けます。 - **AIの継続的アップデート**: モデルの精度向上や規制変化に応じて活用方法を見直します。 --- ### **ポイント** - **経営層のコミットメント**: 投資対効果(ROI)の試算を示し、長期的な展望で支持を得ることが不可欠です。 - **現場主導の改善**: ユーザーである従業員のフィードバックを活用し、ツール改善やプロセス最適化を重ねます。 生成AIは「ツール」であり、活用の成否は「人間の使い方」に依存します。技術導入だけでなく、組織全体のデジタルリテラシー向上と心理的安全性の確保が鍵となります。 5. 実際に使ってみた感想・所感 レスポンス: 今回の回答には4分程度かかりました。LLM自体の推論にかかる時間と、利用が集中しているが故の処理時間の長さ、両方の影響がありそうです 出力精度: Thinkingを行う推論モデル故に抽象度が高かったり複雑度の高いプロンプトに対しても深い洞察を感じられる回答が返ってきます API利用では思考過程が タグに囲まれた形式で出力されます。これは使いやすいですね タグの中身を見ていくと、まるで複数人でディスカッションが行われたような思考過程を辿っています まとめ: Azure AI Foundryを用いると、最新のサードパーティ製モデルを手軽に試すことが可能です 「まず試す」ことが重要な生成AI領域において、この手軽さは非常にありがたいですね We Are Hiring! KINTOテクノロジーズでは、事業における生成AIの活用を推進する仲間を探しています。まずは気軽にカジュアル面談からの対応も可能です。少しでも興味のある方は以下のリンク、または XのDM などからご連絡ください。お待ちしております!! @ card ここまでお読みいただき、ありがとうございました!
Introduction Hello, I’m Koji Nishiguchi, Manager of Analysis Group at KTC Data Analysis Division. Exploring how future-focused research and technology development can address real-world challenges is a key driver of innovation. However, a disconnect often arises between researchers and businesses regarding "research aimed at future needs" and "technologies ready for immediate on-site application." This gap makes it difficult for both parties to collaborate effectively. In this article, we will highlight our efforts to bridge this "matching gap" and share our vision for future initiatives. (Generated by Microsoft Copilot) Activity so far Toyota Motor Corporation operates a division called the Future Creation Center (FRC), dedicated to conducting future-focused research. Through a fortunate connection with the FRC, they joined us at the Data Analysis Division of KINTO Technologies (KTC) as collaborators, assisting with research challenges. This marked the beginning of our initiative. Initially, we addressed specific data science challenges and were able to advance the project smoothly as a novel research endeavor. In traditional data science issues, as described in textbooks, the focus is primarily on the predictions themselves as the main metric of evaluation. However, from a business perspective, the reliability and variability of predictions also play a crucial role. In other words, it is crucial to understand the confidence level of predictions. Over time, several challenges emerged, prompting us to expand our efforts further. At the start, these challenges were relatively manageable, and we were able to turn them into research projects that made meaningful contributions to business outcomes, with the valuable support of FRC. The challenges of matching However, a problem arose. It became clear that the constraints and priorities of the projects were different for each side. While both teams aimed to improve user experience, their goals were not the same: KTC focused on product commercialization, while FRC aimed to conduct research for the future, such as securing patents or publishing papers. These differences created challenges, such as mismatched timelines and varying expectations for the level of completion. To work within these constraints, we often had to focus on “technologies that were nice to have but not essential.” As a result, the developed technologies were deprioritized in practice and remained unused. This experience highlighted the challenges of establishing effective collaboration between the "research team" and the "business field." (Generated by Microsoft Copilot) Conducting the ideathon 1. Expectations for the ideathon As one of the solutions, we organized an ideathon. Ideathon is a workshop-style event designed to generate and develop new ideas for tackling challenges in a short period of time. This event brought together participants from both FRC and KTC, aiming to explore ways to leverage each other's technologies and research through open, creative discussions. In the medium and long term, we hoped this collaboration would help both parties identify opportunities for cooperation. For KTC, this meant gaining fresh perspective on how to apply new technologies, while FRC could discover potential "seeds" for future research projects. Purpose of the initiative Expectations for the ideathon Research side Research needed for the future Discovery of seeds for future projects Business side Technologies that can be used immediately Gaining new perspectives 2. Implementation process To prepare for the ideathon, FRC created a list of technologies that could be introduced. KTC members then provided feedback through a survey, and based on the results, two technologies were selected for the event. On the day of the ideathon, we began with an introduction of the two technologies, followed by a short Q&A session, and then moved into the main ideathon activity. 3. Event summary Date: September 2024 Schedule: 16:00-17:30 Study session (Introduction of two technologies) Technology A: Recommendation-related technology Technology B: Customer psychology measurement technology 17:40-19:00 Ideathon (25 minutes per table) Participants: ・FRC: 7 members (1 online) ・KTC: 11 members (3 online) During the ideathon, KTC participants were divided into three groups, rotating through three discussion tables: Technology A, Technology B, and a free discussion table. Each table consisted of 2-3 FRC members and 3-4 KTC members. Most participants were meeting for the first time, so it began with self-introductions and overviews of each participant’s work. This was followed by discussions focused on the table’s theme. The 25 minute sessions went by in no time, and in many cases, discussions were just starting to gain momentum when time ran out. Even so, a project with potential for matching emerged, and FRC researchers and KTC engineers quickly began aligning on the details. While we cannot share specifics, we felt that if KTC's pursuit of "customer understanding" and the research department’s technologies were well-aligned, meaningful progress could be made. 4. Feedback from participants and suggestions for improvement After the event, we conducted a survey among KTC participants. The satisfaction score was 4.11 out of 5. With the condition of "If the content is interesting," all participants said they would like to join the next event. This shows that the event was meaningful for everyone. However, the following suggestions for improvement were made: The explanations during the study session were too long. The audio quality in the online presentations was poor. The study session should include examples using actual KTC data. More concrete examples are needed. The ideathon sessions should be longer. The self-introductions and work overviews at the beginning of each ideathon session took too much time, leaving less time for discussion. We will take these suggestions into account to make future events smoother and more effective. Future prospects In addition to the FRC, the Toyota Group has many other research units. When opportunities arise, we aim to exchange ideas with other research teams and create spaces for effective matching. Specifically, we plan to clearly present the technologies and solutions required by the business field, while offering researchers opportunities to apply their work in real-world business contexts. At the same time, by communicating researchers’ interests, such as themes and challenges, to companies, we believe we can bridge the gap between research and business, fostering mutual understanding. Through these efforts, we aim to build a win-win relationship by increasing the number of cases where technologies are effectively applied in practical settings. To achieve this, we must continue to accumulate experience through ideathons and matching events, making collaboration between technology and research smoother. We believe this will speed up the process of bringing future-focused research into practical use and help create a better society. Conclusion To connect future-focused research with technologies needed today, it is important to create spaces where the needs and expectations of both sides can be understood. Through study sessions, ideathons, and workshops like those mentioned, we believe that fostering mutual understanding between researchers and the business side can pave the way for more effective and practical collaboration. By fostering such collaboration, we believe that integrating various new technologies into KTC’s vision of an "in-house development organization with a customer-centric perspective" , will enable us to deliver inspiring new experiences to our customers at the earliest. KTC will continue to work with FRC to develop new technologies and explore the practical applications. This is essential for us, as a leading mobility platform provider, to bring not just “mobility” but also “inspiration” to every journey. (Generated by Microsoft Copilot) <Unauthorized reproduction or use of the content, text, images, etc., on this site is strictly prohibited.>
Introduction Hello, I am Osanai, the leader of the SRE Team in the Platform Group at KINTO Technologies (hereinafter, KTC). In this article, I will discuss how we developed the SRE Team’s mission and vision. If you’re only interested in the finalized mission, feel free to skip directly to here . Why Did We Decide on a Mission and Vision? Three key factors influenced our decision to establish a mission and vision this time. 1. A suggestion from a team member The SRE Team was established in January 2021, but after various ups and downs, it became a one-person team by the end of March. In April, a new team member suggested creating a mission and vision for the team, which sparked the initiative. He mentioned that, in his previous job, the company’s mission and vision were well-integrated into daily operations and functioned effectively. However, at the time, I didn’t see an immediate need for it and largely dismissed the idea as something we could address later if time allowed. (Apologies!) 2. The need to present a team roadmap to senior leadership. At the time, the SRE team consisted of just two members with limited resources, so we were actively working on initiatives to enhance recruitment efforts. As part of this effort, it became essential to clearly communicate to senior leadership what the SRE Team aims to accomplish, the challenges we anticipate, and the number and types of personnel required to address them effectively. As part of this process, we decided to create a roadmap for the SRE Team. Additionally, we concluded that establishing a mission and vision would provide a higher-level framework to guide the team’s activities. 3. Diversification of the term “SRE” Based on that, we started looking at the missions and visions of various companies’ SRE teams. Then, we came across a slide like this: From “What Does SRE Aim to Do?” Personally, this struck a chord with me the most. At KTC, there are several other cross-functional teams and groups in addition to the SRE Team, such as Platform Engineering, Cloud Infrastructure, DBRE, CCoE, and Security. The term "SRE" encompasses a broad range of responsibilities, so I felt it was important to define our specific role and focus within the context of the existing environment around us. How Did We Decide? So, in light of all the above, we decided to come up with a mission and vision. However, having no guidelines on how to go about it, we were fumbling in the dark to begin with. First, regarding the time commitment and timeline for the decision-making process, we decided to complete it within a relatively short period. This was partly because we anticipated that the process might drag on if we chose to make decisions incrementally during weekly meetings. We considered dedicating a full day to the process but realized that our ideas might be overly influenced by the conditions on that particular day. Ultimately, we decided to allocate one hour per day over five business days, with a weekend in between, to allow for a more balanced and thoughtful approach. After going through the process, my personal impression is that spreading it out over several days was a good decision. It gave me time to reflect, and I came up with ideas while taking a bath and just before falling asleep. Next, in terms of how we decided, we opted to base our approach on Google re:Work . We proceeded by following “Set and communicate a team vision” in the “Managers” theme. This time, our goal was to formulate a mission and vision. To achieve this, we proceeded by defining the core values, purpose, and mission within the relevant sections. For the vision, we approached it from the perspective of imagining what we would want the SRE Team and the company as a whole to look like once the mission had been accomplished. Therefore, we chose to define the vision based on the mission we had established. Day 1: Identifying What Values We Want to Cherish On the first day, we decided to start by identifying the values that each team member personally wants to cherish. Using Miro as our collaboration tool, each of us wrote on sticky notes the things we want to cherish, encompassing both technical and non-technical aspects. Coming up with ideas about the things you want to cherish can be quite challenging, so I realized that an alternative approach could be to identify ideas by considering the outcomes you would most want to avoid. After this, we had an open discussion about the values embodied by people we admire. Day 2: Doing a Deep Dive into the Core Values of the Team On the second day, we each shared and discussed the values we had identified on the first day. Even when the details differed, we discovered that by gradually abstracting our thoughts through questions like “Why do you want to cherish that value?”—similar to a why-why analysis—we arrived at shared underlying values. Taking note of these insights as you go could be helpful when defining your own mission. Next, we discussed the values we resonated with and brainstormed concrete behaviors that reflect those values. Since we were doing it in a pair this time, we picked out some values that we could relate to from among each other’s, and did a deep dive into those. For example, one of the values was along the lines of “Produce better output through good collaboration,” but that sounded a little abstract. So, we reworded it more concretely by asking what “good collaboration” and “better output” actually meant, and as we did so, we got a much clearer image of what it was about. Day 3: Exploring the Reason Why the Team Exists On the third day, we thought about the purpose (the reason why the team exists). We collaboratively answered six questions under the theme “Why Does This Team Exist?” and discussed our ideas along the way. One thing I felt requires some caution is that these questions focus on the current status quo, so people's answers about the fundamental reason for the team's existence might be biased especially if they are motivated to bring about changes to the current organizational situation. By reflecting on what we had done so far, abstracting those actions, and revisiting the reasons behind them, we started to uncover a potential candidate for the team’s fundamental reason for existence. Day 4: Deciding on a Mission On the fourth day, we finally decided on our mission. First, we each wrote on sticky notes our own thoughts about three questions, as a form of self-reflection . Then, we decided on a mission in light of everything we had done since the first day. To be honest, there were moments when we relied on inspiration, but we reviewed all the activities and conversations we had engaged in up to that point. From those, we identified phrases that seemed to stand out as potential keywords and crafted a mission statement that encapsulated and satisfied them. In making our decision, we also kept in mind whether our ideas aligned with the five key characteristics that a mission should have. Day 5: Deciding on a Vision On the fifth day, we decided on a vision by imagining what we wanted the SRE Team and the company as a whole to be like once we had achieved the mission. The Miro board we actually created (just the atmosphere of it) The Mission and Vision We Decided On The mission and vision we actually decided on are as follows: To explain the mission, first, I will look at the part about being able to provide products as fast as possible. KTC has a wide variety of products. We would like to enable them to provide their functions to users as quickly as possible, and create an environment where we can get feedback. However, just providing products quickly is not enough: we also need to deliver to users products that will be of value to them. In addition, no matter how quickly you can provide a valuable product, it will be meaningless if the users cannot use it to their satisfaction. So, we added the word “reliable.” For our vision, we envisioned what KTC would look like once it achieves the ability to provide reliable, highly valuable products as quickly as possible. We concluded that to simultaneously fulfill the quality-focused aspect of "reliable, highly valuable products" and the speed emphasized in "provide [them] as fast as possible," it is essential to strike the right balance between development and operations, guided by service level requirements. In Conclusion We managed to formulate a mission and vision for our team without a hitch. Although it's still early days since we established it, conversations are already emerging around questions like, "Considering our mission, is this something we should really pursue?" and, "If we decide to do this, to what extent should we take it?" This gives me the sense that the mission is beginning to serve effectively as a guiding framework for our team. That said, simply formulating them now doesn’t mean this is the end of the journey. I would like us to develop a roadmap for achieving our mission and vision and work together toward them as a unified team. Also, the SRE Team is looking for people to work with us. If this has sparked even a slight interest, please don’t hesitate to reach out to us. We look forward to hearing from you! https://hrmos.co/pages/kinto-technologies/jobs/1811937538128224258
はじめに こんにちは!iOSエンジニアのViacheslav Voronaです。今年の8月に東京で開催されたiOS開発者向けカンファレンス iOSDC Japan 2024 に参加しました。 セッションがほとんど日本語で行われるカンファレンスには初めて参加したので、少し緊張していましたが、実際に参加してみると、思ったよりもスムーズに理解できました。コードスニペットを見たり、自分がある程度理解しているトピックについて話を聞いたりすると、たとえすべての言葉を聞き取れなくても、意外と内容を追うことができました。スライドに英訳を入れてくれた方々に感謝です!これ以上ないくらい助かりました❤️ 今回はいくつかのセッションに参加しました。その中には、登壇者が趣味として取り組んでいるプロジェクトを紹介するものもありました。例えば、 ta.inoue さんの セッション では、GPSの仕組みを解説しつつ、 iOSデバイスを使って送信データのスニッフィングを実演していて、とても興味深い内容でした。GPSは原理としてはシンプルなのに、実際の構築は驚くほど複雑であるという点がいつも私を魅了します。このセッションも期待を裏切らないものでした。 また、 haseken さんによるUIKitのさまざまな ViewControllersの歴史や役割を 深堀り したセッションも印象的でした。日々の業務でViewControllerを使うことは多いですが、UIKitは非常に幅広く、用途が限られたView Controllerの中には知られていないものも結構あります(私も初めて知るものがいくつかありました)。このセッションもとても興味深かったです。 今日は、これらのセッションの中から、実務で役立つ可能性があり、今後も心に留めておきたいと感じたポイントをいくつかご紹介したいと思います。それでは、いってみましょう! 隠れたAPIを活用する実践例 どの開発者も、時おり、プロジェクトで使用しているサードパーティのコードを詳しく調べる必要に迫られることがあります。新しいプロジェクトのニーズにそのライブラリやフレームワークが合っているか確認するため、既存のツールをより深く理解するため、時にはサードパーティのコードのバグを追跡して報告するためなど、その理由はさまざまです。 しかし、私たちが日々使用していながらも謎めいているのが iOSフレームワーク(SwiftUIやUIKitなど)の存在です。これらのフレームワークには、十分に説明され推奨されているAPIだけではなく、まだ知られていない多くの機能が隠されています。こうした機能を見つけて活用することは、単なる興味深い挑戦にとどまらず、特定の場面でiOS開発者にとって有益な結果をもたらす可能性があります。これが noppe さんによるセッション 「iOSの隠されたAPIを解明し、開発効率を向上させる方法」 のテーマでした。 noppeさんのプレゼンテーションは次の3つのパートに分かれていました。 Perform(発見) :最初のパートでは、隠されたメソッドをどのように見つけるかについて簡単に説明がありました。主な手法は以下の通りです。Objective-C: 興味のあるクラスの .h ファイルを置き換える。Swift: .tbd や .swiftinterface ファイルを調整する。 Use Case(ユースケース) :2つ目のパートでは、隠れたAPIを使う可能性のあるシナリオが紹介されました。このパートが最も長く、以下のような具体的なケースが挙げられていました。 プロトタイピング: 便利で安全。非クリティカルなコードであれば、効率を優先して活用できる。たとえAPIが変更されても機能に影響しない。 テスト: 便利で比較的安全。特定のテストシナリオを簡単にカバーできる。ただし、非公開API自体にもテストを追加して、変更をすぐに検出できるようにするのが望ましい。 本番環境 : 明らかに安全ではない。将来的にAPIの挙動が変わる可能性があるうえ、App Storeの審査でリジェクトされるリスクもある。 Find(発見方法) :最後のパートでは、隠されたAPIを発見する具体的な方法について説明されました。主な手段として、 .h 、 .tbd 、 .swiftinterface ファイルの確認や、スタックトレースの分析、さらにコミュニティでの情報共有が挙げられています。 noppeさんのプレゼンテーションでは、隠されたAPIが役立つ具体例もいくつか紹介されましたので見ていきましょう。 プロトタイピングの段階で印象的だったのは、 UITextView に関連する例です。ご存じの通り、テキストビューにプレースホルダーを設定する機能は一般的ですが、Appleはそれを直接設定するためのAPIを提供していません。そのため、通常はラベルをサブビューとして追加するなど、カスタムソリューションを作成する必要があります。そこで登場するのが UITextView です。公開されていない setAttributedPlaceholder というメソッドがあり、これを使えばプレースホルダーを簡単に設定できます。このメソッドは本番環境での使用は許可されていませんが、プロトタイピングや概念実証の段階では時間を節約することができます。 テストに関連する例として挙げられたのは、 UIDebuggingInformationOverlay です。このツールは、iOS 11以前では簡単に使えましたが、現在ではアクセスするために 少し工夫 が必要です。それでも、UIKitの隠れた低レベルな機能を使えば、有効化することが可能です。 UIDebuggingInformationOverlay 非公開APIについて学ぶことは、普段使っているツールへの理解を深める良い方法です。本番環境での使用は推奨されませんが、そうしたAPIの存在を知ることで、通常とは異なる解決策が求められる場面で新たな可能性が広がります。また、それらの仕組みを知ることで、自分のAPI設計スキルを磨くことにもつながります。 このセッションは非常に実用的で、多くの刺激を受けました。参加できて本当に良かったと思います。 @ speakerdeck Server-driven UI 開発者として、私たちは常に業界の最新トレンドを追いかけています。それは、新しいフレームワークや技術をいち早く試してみたいという好奇心からだったり、同僚に遅れをとりたくないという理由だったりします。しかし、今年のiOSDCで私は、これまでほとんど知らかったデザインパラダイムについてのセッションに出会いました。その手法自体は以前から存在していたものの、これまで深く理解する機会がありませんでした。セッションはNadeさんによる サーバードリブンUI入門: 画面のStateを直接受け取るアプローチ というものでした。サーバードリブンUIは少しニッチな技術に思えますが、その存在を知り、注目しておくだけでも価値があると感じました。 サーバードリブンUIとは、UIの状態をバックエンドから直接受け取る仕組みです。このアプローチを使えば、アプリの新しいバージョンをリリースすることなくクライアントサイドのUIを変更できます。また、クライアント側のビジネスロジックを減らすという点でも非常に有用です。仕組みとしては、クライアントアプリ内にシンプルで再利用可能なUIコンポーネントをあらかじめ実装しておき、それらをサーバーのレスポンスによって制御します。サーバー側で、どのコンポーネントをどの画面に、どの順序で表示するのか、そしてそれぞれにどんな内容を含めるのかを指示します。 iOSの場合、SwiftUIを使用すると、ビューが Codable プロトコルに準拠できるため、サーバーの応答をそのまま反映させる形で実装することができ、このアプローチはより自然に感じられます。Nadeさんのプレゼンテーションでは、この点が明確に示されていました。 もちろん、このようなパラダイムを採用することで、伝統的な実装と比べていくつかのトレードオフが生じます。それでは、それらを簡単に見ていきましょう。 Server-driven UI の利点: 新しい機能をリリースする際にアプリの新バージョンを配布する必要がなく、バックエンド側で変更を加えるだけで済むため、リリースサイクルが短縮されます。 ビジネスロジックが全てのプラットフォームで統一されることで、 DRYer コードが実現します。これにより、 変更が即座に全てのクライアントに反映されます。 クライアント側でのビジネスロジックが最小限(理想的にはゼロ)になるため、開発者はUIやUXの細部に集中し、よりスムーズな体験を提供することに注力できます。 Server-driven UI の課題 事前に多くの汎用的なUI要素を定義して構築する必要があり、小規模なチームにとっては負担が大きい。 これらの汎用UI要素のすべての組み合わせをテストするのには、多大なコストがかかる。 サーバーサイドのアーキテクチャが、追加の「Backend for Frontend」レイヤーによりさらに複雑化する。 このように、サーバードリブンUIは、バックエンドの複雑さやインフラを管理できる大規模なチームに最も適したソリューションですが、クライアントの柔軟性や配信速度の向上という点では大きなメリットを提供できます。 @ speakerdeck 最後に iOSDC Japan 2024は、私にとってとても楽しめたうえに有益な体験でした。今回ご紹介した2つのセッションは、私が参加した中でも特に実務で役立つ内容でした。もし興味を持たれたなら、iOSDCの主催者がセッション録画を公開しているので、ぜひチェックしてみてください。 最後までお読みいただき、ありがとうございました。それでは、また次回!
This article is part of day 3 of KINTO Technologies Advent Calendar 2024 .🎅🎄 I am Somi, a Flutter application developer at KINTO Technologies (hereinafter, KTC). Flutter is an appealing framework that enables you to construct a diverse range of UIs independently of the platform. In particular, with CustomPaint, you can easily produce intricate designs that would be difficult to achieve with the basic widgets alone. Recently, when implementing a QR code recognition screen, an issue arose with creating a border for the recognition area. We tried to use the existing libraries, but there was a limit to how well they could achieve the curve design we wanted. Therefore, we solved the problem by drawing the border directly using CustomPaint and Path . In this article, I will detail what steps we took to complete the border for the QR code recognition screen using CustomPaint and Path. The Goal of the Border Design The border we implemented this time is a curved, translucent white one around the four corners of the QR code recognition area. Using the CustomPainter class , we defined a path on the Canvas with Path, then drew the border using a combination of curves and straight lines. Preparations for Drawing the Border Using CustomPainter First, we define a class for drawing the border, namely, _OverlayPainter . This class extends CustomPainter, and will be responsible for drawing the border on the Canvas. The following is sample code for drawing a border using an already defined _OverlayPainter. Later, I will explain in detail about the specifics of implementing it. class QrScanPageContent extends StatelessWidget { const QrScanPageContent({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("QR Code Scanner"), // Screen title ), body: CustomPaint( size: Size.infinite, // Draw it to fit the size of the whole screen painter: _OverlayPainter( squareSize: 200.0, // Size of the border area borderRadius: 20.0, // Roundness of the border’s corners borderThickness: 8.0, // Border thickness ), ), ); } } Setting up the background and recognition area We will create in specific detail the _OverlayPainter I mentioned above. First, we draw the background color and the QR code recognition area. We draw the background as a semi-transparent rectangle using the drawRect method, and the QR code recognition area as a rounded rectangle using the drawRRect method. For drawing each, we set the style (color and transparency) using the Paint class. In the next section, I will explain how to draw the border in detail. class _OverlayPainter extends CustomPainter { final double squareSize; final double borderRadius; final double borderThickness; _OverlayPainter({ required this.squareSize, required this.borderRadius, required this.borderThickness, }); @override void paint(Canvas canvas, Size size) { final centerX = size.width / 2; final centerY = size.height / 2; // Draw the background final backgroundPaint = Paint()..color = Colors.grey.withOpacity(0.5); canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), backgroundPaint); // Draw the recognition area final rect = RRect.fromRectAndRadius( Rect.fromCenter( center: Offset(centerX, centerY), width: squareSize, height: squareSize, ), Radius.circular(borderRadius), ); final innerPaint = Paint()..color = Colors.lightBlue.withOpacity(0.1); canvas.drawRRect(rect, innerPaint); // Here, we set the frame’s style and draw the frame. } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } The shouldRepaint method decides whether this CustomPainter requires redrawing. In this example, the background color and the size of the recognition area are fixed, so redrawing is unnecessary. Consequently, this method always returns false. However, if you want to draw dynamically or change the size or shape, you need to set this method to true. Setting the border style Next, to get ready to draw the border, we set the line style. Before drawing the border, we define the style using a Paint object. The Paint class provides tools for setting things like the color, thickness, and shape of a line. Here, we set the border to translucent white and define the line shape to be rounded. By setting the border to a translucent white color, we have ensured that the important recognition area can be spotted at a glance. final borderPaint = Paint() ..color = Colors.white.withOpacity(0.5) // Set the border color and transparency ..style = PaintingStyle.stroke // Set the outer border style ..strokeWidth = borderThickness // Line thickness ..strokeCap = StrokeCap.round; // Set the ends of the line to be rounded Calculating the coordinates and sizes To draw the border, we first need to calculate the coordinates and size of each corner. This will enable us to define the start and end points of each corner precisely. The following is an example calculation: const double cornerLength = 55; // Length of each corner double halfSquareSize = squareSize / 2; // Size of half of the recognition area double left = centerX - halfSquareSize; // Left boundary double right = centerX + halfSquareSize; // Right boundary Double top = centerY - halfSquareSize; // Top boundary double bottom = centerY + halfSquareSize; // Bottom boundary Coordinate system : In Flutter’s Canvas coordinate system, the top left is (0, 0). This means that the top side is calculated using centerY - halfSquareSize , and the bottom side is defined as centerY + halfSquareSize . cornerLength : Define what length of straight line to draw at each corner. halfSquareSize : Calculate the size of half of the QR code recognition area. left, right, top, bottom : Define the boundary coordinates of the recognition area with respect to the coordinates of the center. Pictorially, the above formulas look like the following figure. Drawing the Border First, we start to draw from the top left corner. To draw the top left corner, we define a path using the Path class. Path is a handy class that lets you specify a variety of shapes like lines, curves, and arcs, then draw them on the Canvas. 1. Draw a straight line from right to left We move the start point to the top end of the corner, then draw a straight line going left. Path topLeftPath = Path(); // Define a new path topLeftPath.moveTo(left + cornerLength, top); topLeftPath.lineTo(left + borderRadius, top); The above code results in drawing a line like the one below. 2. Draw a corner curve We add a curve that starts from an end point of the straight line. Using the arcToPoint method, we draw a curve from the start point to a specified end point. This enables us to create a natural join from the straight line to the curve. In the code below, we set the curve’s end point with Offset and its radius with Radius, giving us a rounded corner for the QR code area. topLeftPath.arcToPoint( Offset(left, top + borderRadius), // End point of the curve radius: Radius.circular(borderRadius), // Radius of the curve clockwise: false, // Draw the curve counterclockwise ); The code above generates a curve with a rounded corner, as shown below. 3. Draw a vertical straight line We extend a line downward from the endpoint of the curve. topLeftPath.lineTo(left, top + cornerLength); The code above adds a vertical line, as shown below. 4. Draw the path on the Canvas We use the defined Path to draw on the Canvas with borderPaint. canvas.drawPath(topLeftPath, borderPaint); Processing the remaining corners The following is a code example demonstrates how to draw the other three corners after the top left one: // Bottom left corner final bottomLeftPath = Path() ..moveTo(left + cornerLength, bottom) ..lineTo(left + borderRadius, bottom) ..arcToPoint( Offset(left, bottom - borderRadius), radius: Radius.circular(borderRadius), clockwise: true, ) ..lineTo(left, bottom - cornerLength); canvas.drawPath(bottomLeftPath, borderPaint); // Bottom right corner final bottomRightPath = Path() ..moveTo(right - cornerLength, bottom) ..lineTo(right - borderRadius, bottom) ..arcToPoint( Offset(right, bottom - borderRadius), radius: Radius.circular(borderRadius), clockwise: false, ) ..lineTo(right, bottom - cornerLength); canvas.drawPath(bottomRightPath, borderPaint); // Top right corner final topRightPath = Path() ..moveTo(right - cornerLength, top) ..lineTo(right - borderRadius, top) ..arcToPoint( Offset(right, top + borderRadius), radius: Radius.circular(borderRadius), clockwise: true, ) ..lineTo(right, top + cornerLength); canvas.drawPath(topRightPath, borderPaint); Summary Besides precise designs like the QR code border, using CustomPaint and the Path class enables you to create even more complex UI designs as well. Implementing the border for the QR code recognition screen ourselves reaffirmed how flexible Flutter is, and power of its Canvas functionality. However, when using CustomPainter, keep in mind that more complex your drawing logic can impact performance. If frequent redrawing is necessary, consider optimizing the processing and utilizing other existing widgets. I hope this article can serve as a useful reference for implementing UI design using CustomPaint and Path.
This article is part of day 4 of KINTO Technologies Advent Calendar 2024 Merry Christmas ✌️ Ryomm, who works on developing My Route (iOS) at KINTO Technologies (referred to as KTC), is stepping into a new role this time! Allow me to introduce "Manabyi," an incredibly exciting Slack bot created in collaboration with the Michi-no-eki project, as I take on the role of a phantom bot craftsman. What is manabyi It's an incredibly exciting Slack bot designed for gathering information on internal study sessions and events, as well as leveraging the collected data effectively. It contains all of the event-related aspects. ![Manabyi](/assets/blog/authors/ryomm/2024-12-04/01.png =200x) There are two main roles of Manabyi: Search for events Register and publicize new events When a new launch event is entered into Manabyi, it automatically announces the event to the relevant channels. Users can stay informed about internal events either by following the designated channels or by reaching out directly to Manabyi. You don't need to be a direct stakeholder to access the information. The use of Manabyi will be covered in the [Learning Road Station Series] on a later date. In this article, I'll focus on introducing the technology behind Manabyi. Manabyi's technique Manabyi is created using the Slack CLI. https://api.slack.com/automation/quickstart The Slack CLI simplifies the process by eliminating the need to build infrastructure, such as setting up a datastore on Slack's side. I also appreciate that the development environment can be set up directly on the Slack CLI side. The general structure of Manabyi is outlined as follows: Trigger There are three triggers in the manabyi. Features The type of trigger Add an event Link trigger Delete an event Link trigger Search for events Event trigger ![Link Trigger](/assets/blog/authors/ryomm/2024-12-04/03.png =500x) ![Link Trigger Form](/assets/blog/authors/ryomm/2024-12-04/04.png =500x) ![Event Trigger](/assets/blog/authors/ryomm/2024-12-04/02.png =500x) There are four types of workflow triggers in Slack. Trigger name Description Link trigger Once created, a URL will be issued and executed when the link is clicked in Slack (not valid outside Slack) Scheduled trigger Run in hours Event trigger Mentions and reactions Webhook trigger Executed when a specific URL receives a POST request https://api.slack.com/automation/triggers Manabyi is primarily designed for users to utilize the search function, while the add/delete functions are intended exclusively for event organizers. To prevent accidental additions or deletions of events, we have separated the types of triggers accordingly. When you register for an event, notifications are sent to two channels: the channel that invoked the bot and the channel designated for promoting the subscription. ( #notice-new-event ). ![Notifications](/assets/blog/authors/ryomm/2024-12-04/05.png =500x) This ensures that you're informed about newly launched events, regardless of the channel where they were created. Of course, you can also retrieve information by interacting with Manabyi using keywords of interest. Block Kit Slack uses a framework called Block Kit to create rich visual messages. You can experience it with the following tool called Block Kit Builder, so if you have never used it before, please give it a try. https://api.slack.com/tools/block-kit-builder The result of getting event information is also easier to use Block Kit. https://api.slack.com/block-kit ![Message using Block Kit](/assets/blog/authors/ryomm/2024-12-04/08.png =500x) The reference page links, such as those directing to a Slack channel, a Confluence page, or a kick-off video, can often be lengthy and cluttered. To address this, we streamlined the experience by embedding them under a "Details" button. The description text field is configured to use the rich_text type to accommodate requests for including links alongside plain text. Slack bot development tips corner Start by deciding whether or not to use Block Kit when sending messages. Initially, I used the string type but later decided to switch to the rich_text type. However, since the string type and rich_text type are not compatible, migrating the datastore was not a straightforward process. Moreover, once the registered data is converted to rich_text, I want to incorporate features like line breaks and links! After considering various factors, including those mentioned, we decided to take a bold approach by clearing the data in the datastore and starting fresh. Super exciting! If you can decide, it is a good idea to decide whether to use the Block Kit first. https://api.slack.com/automation/datastores Be careful with block_id For more information, go to this article: [ Slack CLI ] block_id conflicts and cannot send messages to Slack Workflows run only at build time For testing purposes, when creating a method that uses a UUID, you should pass the id as an argument from outside the method. const addEventFunctionStep = AddEventWorkflow.addStep( AddEventFunction, { id: Crypto.randomUUID(), // you want to generate an ID on the caller of the method title: formData.outputs.fields.title } ) However, the Workflow definition is executed only during build time, while the function is executed during subsequent calls. For this reason, any code that needs to be executed each time, such as generating a UUID, should be included within your method. Documents are too hard to find The Slack API documentation can generally be applied as is without modifications. In particular, I often explore what types can be used in trigger inputs, forms, and datastores. For you, this document is the solution! https://api.slack.com/automation/types Technology that supports manabyi Manabyi is currently treated as an inner source, and since the development environment is well-prepared, I’ll provide an introduction. CI/CD Test Slack CLI runs on Deno, so you can run tests with deno test . name: Slack App Test on: pull_request: types: [opened, synchronize, reopened, ready_for_review] jobs: build: runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 - name: Install Deno runtime uses: denoland/setup-deno@v1 with: deno-version: v1.x - name: Install Slack CLI if: steps.cache-slack.outputs.cache-hit != 'true' run: | curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash - name: Test the app run: | cd app/ deno test --no-check Deploy You can get a service token starting with xoxp- by running the slack auth token command in your console. Register this as a secret on GitHub. The workflow is defined as follows: name: ‍➡️ Slack App Deploy on: push: branches: [ main ] workflow_dispatch: jobs: build: runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 - name: Install Deno runtime uses: denoland/setup-deno@v1 with: deno-version: v1.x - name: Install Slack CLI if: steps.cache-slack.outputs.cache-hit != 'true' run: | curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash - name: Deploy the app env: SLACK_SERVICE_TOKEN: ${{ secrets.SLACK_SERVICE_TOKEN }} run: | cd app/ slack deploy -s --token $SLACK_SERVICE_TOKEN You can now deploy regular app code updates. However, it's important to note that trigger updates and datastore configuration changes cannot be automated, so these deployments must be handled manually. Issue Template Templates are provided to facilitate the submission of bugs, feature requests, and questions. To be honest, it's rarely used, but I appreciate it because it feels very OSS-like. Project We use the GitHub Project. We primarily use Jira for our internal projects, but since Manabyi doesn't utilize Confluence, it's more convenient to submit issues on GitHub. (I also wanted the team to experience the benefits of GitHub Projects.) Therefore, I decided to manage everything entirely on GitHub. Since I belong to the Mobile Development Group, my focus is primarily on Swift and Kotlin. As a result, many team members find writing TypeScript to be a challenging hurdle. Additionally, it's challenging to raise awareness about the existence of such a project outside the department, leaving many issues to address as an inner-source project. I am setting up a development environment with the hope that, as Manabyi continues to grow, someone will contribute to it someday. Conclusion That was an introduction to Manabyi. Manabyi is still in its infancy, but I hope it will grow alongside the culture of KTC...!
Introduction Hello! I am high-g ( @high_g_engineer ) from the New Car Subscription Development Group at the Osaka Tech Lab. In this article, I reflect on the Developers Summit 2024 KANSAI, held on September 18, 2024. What Is Dev Summit KANSAI? The original Developers Summit (hereinafter, Dev Summit) is a conference-type event for software developers that has been held annually in Tokyo and online since 2003. Dev Summit KANSAI has been held since 2011 as a spin-off event of that, and is a much-loved festival for IT engineers in Kansai. The theme for 2024 was “Let’s create the new standard together.” The sessions covered a wide range of topics, including security, AI, development methods, development productivity, DevOps, and engineer careers. On the day, all the sessions were virtually packed, and with those and the sponsor booths combined, the whole event was really bustling. https://event.shoeisha.jp/devsumi/20240918 Osaka Tech Lab’s First Time a Sponsor Our company participated in the event by setting up a sponsor booth and taking the podium for a sponsor session. Starting this fiscal year, KINTO Technologies has been sponsoring a variety of events, but this was Osaka Tech Lab’s first foray into sponsoring an event (that’s how we call KINTO Technologies’ Osaka office.) So we needed to think about: What kind of booth to set up The novelty items to prepare How to guide attendees at the booth To that end, we had meticulous discussions to make sure we were fully prepared in time for the big day. The Sponsor Booth Here is our completed booth! I would like to take this opportunity to touch on a few items in particular. ![KINTO Technologies’ completed sponsor booth](/assets/blog/authors/high-g/20241030/img4.jpg =512x) The Osaka Tech Lab Seen through Data First, I will talk about the board with all the graphs and figures on it in the center of the image. This is a visual representation of information gleaned from talking to Osaka Tech Lab’s members. An interesting feature is how the overall design mimics the parts of a plastic model kit! Our company’s designers created it quickly, but the final product is very polished. ![Board with Osaka Tech Lab represented visually through graphs](/assets/blog/authors/high-g/20241030/img5.jpg =512x) Survey board Next is the Survey Board on the right of the photo. This is a board for getting survey responses from booth visitors. We gave each visitor a sticker and asked them to place it on the job categories on the board that applied to them, helping visualize the IT domains they belong to. ![Board for a sticker survey](/assets/blog/authors/high-g/20241030/img6.jpg =512x) Survey Results The survey results revealed that many visitors are somewhat familiar with KINTO and KINTO Technologies, but there are still numerous target groups that we need to engage with Regarding occupations, many people placed stickers between 'back-end' and 'front-end.' While the nature of the event may have influenced this, it also highlighted that many engineers in Kansai tend to be more generalists than specialists. I believe we gathered valuable data to help expand awareness of the Osaka Tech Lab Thank you to everyone who cooperated with our survey board! ![Survey results](/assets/blog/authors/high-g/20241030/img7.jpg =512x) Kumobii Stuffed Toy There is a white, fluffy toy sitting on the left in the photo. This is KINTO’s official mascot character, Kumobii . Absolutely adorable, right? It was a huge hit with booth visitors, and Findy and KIKKAKE even posted it on their X accounts. Thank you so much! ![Twitter image: Findy](/assets/blog/authors/high-g/20241030/img8.jpg =512x) ![Twitter image: Kikkake](/assets/blog/authors/high-g/20241030/img9.jpg =512x) Novelty Item On the day, we gave the first 100 visitors to our booth a freebie multi-card tool (a tool set consisting of an aluminum card that can serve multiple roles). We also handed out Kumobii-shaped clips made of paper to people who came along to our sponsor session. ![Multi-card tool](/assets/blog/authors/high-g/20241030/img10.jpg =512x) ![Kumobii paper clip](/assets/blog/authors/high-g/20241030/img11.jpg =512x) Sponsor Session The speaker was Okita-san, who has been a member of the Osaka Tech Lab since it was established. The session theme was “Pursuing a Dual Career as a Project Manager and a Mobile App Engineer to Create the Future of Mobility from Osaka!: Challenges the Osaka Tech Lab and I Get to Take On in the Toyota Group.” The following is an overview of the session. An introduction to Okita-san’s career to date Carving out a career as a project manager who doubles as a mobile app engineer, something unprecedented in the company Sowing seeds for launching, developing, and operating products at Osaka Tech Lab The phrase “never forget your passion” left a big impression on me, and it struck me that the career Okita-san currently has is the fruit of always having soldiered on instead of just giving up and quitting, even when motivation was low. Closing Thoughts This was our first time taking part in Dev Summit KANSAI, and I was truly amazed at how many people were there despite it being a weekday. I got to go along to some of the sessions myself in between helping out with our booth, and I learned a lot, got lots of ideas for my next steps, and enjoyed the whole event very much indeed. Large-scale conferences that encompass a wide range of technical fields are rare in Kansai, so I hope this level of passion continues indefinitely. I would like to express my gratitude to all the staff involved in running Dev Summit KANSAI. Thank you very much.
This article is the second post in the KINTO Technologies Advent Calendar 2024 . Introduction to the background of our activities Hello. I'm “Kinchan” from the Manabi-no-Michi-no-Eki team. I usually work as a corporate engineer, maintaining and managing the IT system used by our entire company. Recently, I have been involved in various activities, including as a member of organizations such as the "Generative AI Utilization Project" and the "Technical Public Relations Group." In a previous tech blog , I introduced the origin of the "Manabi-no-Michi-no-Eki." It also described our "in-house podcasting" activities. In this article, I will provide a detailed introduction to our podcast initiative. This podcast initiative began with the following idea from HOKA-san, a member of the Manabi-no-Michi-no-Eki: I want to share various in-house activities through podcasts! In addition to HOKA-san's very simple motivation, our team was made up of members who basically never said “No,” which led us to pursue it as part of the Manabi-no-Michi-no-Eki team activities. Of course, since this is an initiative of the Manabi-no-Michi-no-Eki team, it is important to effectively combine it with “information dissemination for learning purposes.” After some discussions, we arrived at the following conclusion. Conducting interviews with persons who are hosting, managing, or participating in in-house study groups. And disseminating the interview results. Our purposes were as follows: To answer the question, "There are many study groups in the company, but who are organizing them and what motivates them?" To let our audience hear first-hand voices from those who have actually participated in in-house study groups and feel their value. If someone is interested, this could be an opportunity for them to join a study group. By making the existence of various study groups visualizing/audible, a "culture of learning" will take root within the company. "What is a podcast?" Even though we have decided to start podcasting, we had questions like, "What should we do to make a podcast?" Is it something that can be released in the form of Internet radio? (Will people outside the company listen to it?) Does it have to be distributed using a dedicated app? (This could involve preparation efforts by both the distributor and the listeners...) What kind of equipment is required? The more we thought about it, the more obstacles we found. That said, our goal is simply to "deliver in-house information to the rest of the company," so we decided to proceed with an "agile mindset" of "first using an available system to make a prototype to test out the feel," and then "receiving feedback and making kaizen. Now it's time for the first interview! Once that's decided, it's time to choose the study group to interview! It just so happened that a large-scale study session called a "joint study session" was scheduled within the company, so we decided to attend this study session first and conduct interviews with its management team members. However, on the day of the study session, things got hectic, and we were unable to record the interview. As a result, we decided to take an alternative approach by having the management team members gather at a later date for an interview. (In fact, this alternative approach became the model for our future podcasts.) The actual interview was very exciting, and we were able to complete the recording successfully. However, we then faced a new challenge! In fact, we had reached this point without deciding how to create the podcast content or how to distribute it. After much discussion and trial and error within our team, we settled on the following: Processing of the recorded data Using Clipchamp (a Microsoft 365 family product) to create a video file with a focus on audio. Streaming method The file is uploaded to the in-house SharePoint, where the audience can access and play it on their PC or smartphone. The podcast release flow was finally decided as follows: Completing the content Conducting an internal review within the Manabi-no-Michi-no-Eki team Conducting a review by the interviewees Conducting a supervisor review (after the first review, if there are no major concerns, the supervisor will hand over the decision-making responsibility to us for subsequent reviews.) In-house announcement (= the release) We have established this simple flow, suitable for in-house content only. After that, once all the checks passed, we were able to finally release it inside the company! ![](/assets/blog/authors/ktc-taku-yajima/2024-12-02/started-a-podcast001.png =700x) Subsequent podcasts Having gained confidence from the release of our first podcast, we are now moving forward with the planning of our second and third podcasts. With each interview, we accumulated knowledge and became able to formalize our process to a certain extent. Formalization of the planning process Collecting information on in-house study sessions and information dissemination activities The Manabi-no-Michi-no-Eki team will approach the management team members to conduct interviews. Podcasting the interview results Internal announcement after reviews by the management team and interviewees Formalization of recording, editing, and distribution Interviews will be conducted via Zoom (since our members are dispersed across multiple locations). Each person's PC microphone and conference room microphone will be utilized. The sound quality will be shelved for the time being. Zoom recording data will be used as the audio source. The audio source data will be edited and processed using Clipchamp. After editing the audio source data, it will be saved in the company's internal storage (MS SharePoint). Listeners will access the content via MS Stream. Thanks to this formalized process, we have been able to smoothly operate the system, and up to now, we have successfully distributed 11 pieces of content. About the future As we continue our podcasting activities, we have developed a desire to expand in various ways. We want to interview people who are involved in various in-house activities beyond study group members. We also want to interview members of the management and have them share their candid opinions with the rest of the company. We want to expand our output beyond just the company. We, the Manabi-no-Michi-no-Eki team, will continue to actively share information in the coming years. Stay tuned!
Introduction I am Okapi, from the Quality Assurance Group. Since I often participate in projects as the primary person in charge of QA, in this article, I'd like to share how QA team participates in projects, communicates with the development team, and progresses in its testing at KINTO Technologies. Purpose of This Article When proceeding a project with a team that has never worked with QA before, there is often some trial and error involved as they try to understand what QA can contribute and how the process will unfold. To ensure smoother progress in such cases, I aim to raise awareness of QA. What is QA? QA is an acronym for "Quality Assurance." "Quality Assurance" is a broad term that involves ensuring quality in various aspects. We conduct testing from the user's perspective, by focusing on scenario testing based on actual user usage assumptions and verifying screen UI to prevent any inconvenience and ensure usability. The primary roles of the QA and development teams in testing are outlined in the table below. | Item | QA | Development | Remarks | | ---- | ---- | ---- | ---- | | Verify specifications against system requirements | ㅤ◎ㅤㅤ | ㅤ〇ㅤㅤ| QA ensures that functions and performance meet system requirements, based on user's perspective. | |Verify according to scenarios of user use | ㅤ◎ㅤㅤ | ㅤ△ㅤㅤ| Mainly verified by QA | | Other than the above | ㅤ△ㅤ ㅤ| ㅤ◎ㅤㅤ| When requested, the development team's external integration testing and QA will verify within feasible resource limits | Overview of QA testing Phase Summary Test Plan Phase Share overall project schedule and specification documents (system requirements and screen specifications) to allow QA to create a test plan detailing how the subsequent phases will proceed. Test Analysis Phase Based on specification documents (system requirements and screen specifications), create test perspectives that clarify the scope of testing (to be tested/not to be tested). Test Design Phase Create test cases (prerequisites, procedures, and expectations) based on the test perspectives. Test Implementation Phase Conduct tests based on the created test cases, report defects, and verify modifications. Points Requiring Communication with the Development Team Test Plan Phase Ensure alignment with the development team based on the test plan, which outlines the QA testing period, verification environment, person in charge of conducting QA testing, development team contact, and target devices/browsers. Test Analysis Phase Use JIRA or Confluence to ask questions about the information necessary to create test perspectives. For areas with differing understandings or multiple development team contacts, hold meetings to confirm and organize the specifications. Once the specifications are organized, create test perspectives that clearly define the test scope (to be tested/not to be tested) and confirm alignment with the development team. For the test scope, from a black box testing perspective, the target is on areas users will actually interact with (as deemed necessary by QA for the project), while excluding areas users do not access, such as system administration screens. However, since scenario testing involves verifying the sequence of flow, areas that intersect with the user-side testing, even if typically excluded, are included as test targets. *For regression testing, where quality is assured, the test scope is adjusted based on the shared schedule and resources. Test Design Phase The procedure for conducting tests will be reviewed, but test cases are created based on the agreed-upon test perspectives. Therefore, alignment with the development team is basically not conducted at this phase. Test Implementation Phase Issues (expected results differing from specifications), questions (missing or unclear specifications), and improvement requests (aligned with specifications but unclear to users) are reported in JIRA. Once addressed by the development team (fixes), the QA team conducts re-verification. Testing is considered complete when all test cases and the JIRA tickets are addressed, or when the remaining JIRA tickets are excluded from the current QA test scope. Once implementation is complete, we participate in the overall retrospective (e.g., KPT analysis) to discuss areas for improvement and apply them to future projects. Future Challenges Depending on the project or product, "how to organize the documents" may differ, so after aligning on specifications in JIRA or Confluence, QA organizes and documents the overall user-related system specifications and workflows before moving forward. However, depending on the system's scale, this can be time-consuming. By organizing documents and processes within the QA team and ensuring smooth communication with the development team, we aim to efficiently summarize specifications for accurate understanding. Conclusion The QA team is an independent organization, which may give the development team the impression that they are requesting tasks to QA or that QA is simply conducting tests. However, as part of KINTO Technologies, we see ourselves as partners working together to create high-quality systems. Therefore, we aim to continue fostering a collaborative relationship.
KINTOテクノロジーズで my route(iOS) を開発しているRyommです。 my routeアプリのライブラリ管理ツールがついに!CocoaPodsからSwift Package Manager(以下SPM)に移行しました! はじめに my routeではCocoaPodsを使用していました。 しかし2024年夏ごろ、CocoaPodsから以下のお知らせが発布されました。 https://blog.cocoapods.org/CocoaPods-Support-Plans/ CocoaPodsがメンテナンスモードに移行するというお知らせです。 使えなくなるわけではありませんし、セキュリティの問題などには対応してくれます。ただし、GitHub issueにおけるサポートや新機能の追加などは行われなくなります。 そうすると今後、新しいライブラリを入れたい時にCocoaPodsがサポートされなくなっていたり、CocoaPods自体に何か問題が起こっても直すことができない、といった場面が起こり得ます。 以前からmy routeではSPMへの移行の話は持ち上がっていましたが、このCocoaPodsのお知らせが追い風となり、満を持してSPMへの移行に着手しました。 Swift Package Managerとは SPMとは、Appleが提供する純正ライブラリ管理ツールです。 https://github.com/swiftlang/swift-package-manager iOSでライブラリ管理ツールといえば、他にはCocoaPodsやMintなどのサードパーティツールがあります。これらと最も大きく違うのは、Xcodeに統合されているという点です。 これにより、必要なタイミングで Package.resolved を更新するだけで、プロジェクトファイルを開いた時やビルド時に最新の指定バージョンのライブラリを取り込んでくれます。 バージョン更新をするたびにチームへ pod install を実行してくれ〜と声をかける必要がなくなるのです。 移行したよ ひたすら移していくのみですが、いくつか躓いたポイントや工夫点があるので紹介します。 指定したバージョンのライブラリが降ってこない 使用するバージョンを変更する際、Xcodeで「Reset Package Caches」や「Update to Latest Package Versions」を実行しても、Package.swiftで指定しているバージョンが降ってこない問題がありました。DerivedDataを消しても直りません。 これは、 ~/Library/Caches/org.swift.swiftpm や ~/Library/org.swift.swiftpm などに深淵のキャッシュが残っているためです。これらを消すと正しいバージョンのライブラリが降ってくるようになります。 テストの時だけビルドが通らない my routeは複数のスキーマを持っており、それぞれ依存するライブラリが少し異なります。 そのため以下のようにプロジェクト自体のPackage.swiftとは分離した構成になっています。 プロジェクトファイルと同じワークスペース配下にDependenciesパッケージを作成し、このパッケージ内で各スキーマと1対1になるproduct(フレームワーク)となるようにライブラリを管理しています。 これらのproductを右側のスクリーンショットのように、それぞれのターゲットの Frameworks, Libraries, and Embedded Content にて紐付けています。 しかしこの構成にしていると、 xcodebuild build は通りますが、 xcodebuild build-for-testing が通らないという問題が起こりました。XcodeのGUI上ではRunとTestに対応するコマンドです。 本来、パッケージのものはパッケージ内でテストします。 しかし上記の構成では、プロジェクト本体のメインのターゲットでテストを実行します。つまり、パッケージの外でテストしているということです。 ということはこれは...違法建築...なのですが、ゆくゆくは同じパッケージ内にテストを詰めるように直していくということで、一旦構成は維持したままテストできるようにします。 RunとTestではLinkerあたりの挙動が異なるらしいことが原因でした。 Runのビルド生成物を①、Testのビルド生成物を②とします。 ①にはDependenciesのSPMが含まれていますが、②には含まれていません。 そのため、MainTarget内のテストがDependenciesのライブラリに依存しているとビルドに失敗してしまいます。 簡単な解決策としては、MainTarget内のSPMに持たせるとRunでもTestでもビルドに含まれるため、そちらにテストで必要なライブラリを移します。 根本的に解決するには、先述のように1つのパッケージ内でテストが簡潔するようにすると良いでしょう。 SwiftLintやLicensePlistをSPMに乗せる SwiftLintやLicensePlistはプロジェクトのBuild Phaseに含めてビルド時に実行されるようにしたいため、workspaceから独立した場所に別のパッケージを作成します。 Project/ ├── Test.xcworkspace ├── Test.xcodeproj ├── Test/ │ └── ... ├── ... └── tools // <- this! ├── Package.swift └── Package.resolved 新しく作成したtoolsパッケージにSwiftLintやLicensePlistなどBuild Phaseに含めたいライブラリを入れます。 そして以下のようなシェルを用意し、初回だけ実行してローカルにライブラリを落としておきます。 SPM_PACKAGE_PATH=tools SPM_BUILD_PATH=tools/.build/release echo "swiftlint" && $SPM_BUILD_PATH/swiftlint --version || swift run -c release --package-path $SPM_PACKAGE_PATH swiftlint --version echo "license-plist" && $SPM_BUILD_PATH/license-plist --version || swift run -c release --package-path $SPM_PACKAGE_PATH license-plist --version すると tools/.build/release/swiftlint で実行可能になるので、これをBuild Phaseに入れます。 LicensePlistも同様です。 Bitriseなどから実行する際も、シェルを実行してからプロジェクトをビルドすると実行されることが確認できます。 おわりに 移行してから数ヶ月経ち、まだまだいくつかの問題もありますが、ライブラリの入れ替えは楽になったと思います。 またライブラリ起因の問題かどうか判断したい時に、アプリ版のPraygroundsでサクッと確認できることが個人的には楽に感じます。 SPMがプロジェクトで使えるようになったことで、SPMでしか提供されていないApple製ライブラリも使えるようにもなりました。 今後はこれらのライブラリも使ってより良い実装にしていきたいです。
はじめに こんにちは、クラウドインフラグループの松尾です。 私たちクラウドインフラグループでは、社内全体のインフラ領域を設計から運用まで幅広く担当しています。 今回は、re:Inventで発表された AWS Transfer Family ウェブアプリ についてご紹介します。 https://aws.amazon.com/jp/about-aws/whats-new/2024/12/aws-transfer-family-web-apps/ 本記事では、ウェブアプリの概要、作成手順、設定方法、そして実際に使用してみた感想をお伝えします。参考になれば幸いです。 概要 AWS Transfer Family ウェブアプリ とは、簡単に言うと、ブラウザ上でS3バケット内のファイルを操作できるアプリケーションを、コードを書くことなく作成できるサービスです。 従来、GUIでS3にアクセスするにはAWSコンソールやWinSCPなどの専用ソフトウェアが必要で、設定も必要でした。 そのため、非技術者や一時的なユーザーには操作が難しいという課題がありました。 AWS Transfer Family ウェブアプリは、直感的なインターフェースで、技術的な知識がなくても簡単に操作できる点が特徴です。 さらに、IAM Identity CenterやS3 Access Grantsと統合されているため、細かいアクセス制御も可能です。 料金について AWS Transfer Family ウェブアプリの料金は、ユニット数(1ユニットで5分間に最大250セッション)に基づく1時間単位の従量課金制です。 リージョン 料金 東京 ユニット数*0.5 USD/時間 例えば、1ユニットを24時間フル稼働させると 12USD/日 、1か月では 360USD となり、 長時間の利用ではかなりのコストがかかります。 手軽なサービスの割には高額と言えますね。 利用ケースによっては過剰なコストとなる可能性があるため、用途に応じたサービスの利用が大事ですね。 Webアプリの作成方法 それでは早速アプリを作成してみましょう。 (執筆時点ではTerraform未対応のため、AWS Management Consoleを使用します。) AWS Management Consoleにログインし、AWS Transfer Familyのナビゲーションペインから「ウェブアプリ」を選択し、ウェブアプリを作成をクリック。 今回はNameタグのみ設定して「次へ」をクリック。 ※ IAM Identity Centerの設定が事前に必要 です。未設定の場合、先に準備してください。 ページタイトル、ロゴ、ファビコンを設定し、「作成」をクリック。 今回、ロゴ・ファビコンは設定しないでいきます。 これでウェブアプリの作成は完了です! 簡単ですね! ユーザー/グループとS3 Access Grantsの設定 次に、ウェブアプリにグループの割り当てとS3 Access Grantsを設定していきます。 グループの割り当て ウェブアプリ詳細画面で「ユーザーとグループの割り当て」をクリック。 今回はグループを用意したので、既存のグループを選択して割り当てます。 S3 Access Grantsの設定 次にAccess Grantsの設定です。ロケーションと権限の2つを設定します。 S3のナビゲーションペインからAccess Gratsに移動すると、「権限」 「ロケーション」タブがあるのでここから設定していきます。 ロケーションから設定します。 ロケーションではアクセス対象のS3バケット及び、S3バケットに対する権限を割り当てたIAMロールを指定します。 今回は検証のためAWS管理ポリシー"AmazonS3FullAccess"を付与してます。 権限では先ほど設定したロケーションを指定し、サブプレフィックス、許可、被付与者タイプ、IAM プリンシパルタイプ、IAM プリンシパルユーザーを設定し、権限を作成します。 IAM Identity Centerで認証を行うので、画像の通り、設定しています。 今回は設定していませんが、オプションとして、アプリケーション単位で制限をすることも出来るようです。 CORSの設定 バケットの中身を参照できるように対象のS3バケットで Cross-Origin Resource Sharing (CORS) からCORS設定を追加します。 今回は以下の内容を設定しました。 [ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET", "PUT", "POST", "DELETE", "HEAD" ], "AllowedOrigins": [ "https://${ウェブアプリID}.transfer-webapp.ap-northeast-1.on.aws" ], "ExposeHeaders": [ "Access-Control-Allow-Origin" ] } ] 試してみる ここまで出来たら準備完了です。 ウェブアプリのURLにアクセスし、認証を行ったらS3の管理画面が出てきました! ロケーションで設定したS3バケットが表示されていることが分かります。 それじゃあ実際に今回は対象バケットにs3ファイルの削除をしてみましょう。 Folderのリンクからオブジェクトを操作しようとしましたが、あれ?権限が不足してるようですね。 どうやらAccess Grantsのロケーションで設定したIAMロールでsts:SetContextの設定が必要だったようです。 User Guide にも記載がありました。 設定を追加して再度試したところ、無事オブジェクトの確認が出来ました。 ちなみにCORSの設定ができていないとここでNetwork Errorが出てきます それじゃあ、ここから本当にオブジェクトを選択して、削除をしてみます。 操作も簡単です。 削除したいオブジェクト(今回はテスト1.txt)を選択して三点リーダーからDeleteを選択するだけです。 Webアプリ上から削除されてますね。 コンソール上からも同様に削除できてることを確認しました。 感想 IAM Identity CenterやAccess Grantsの設定が必要でしたが、ノーコードでS3の管理アプリケーションを簡単に構築できる点は非常に便利だと感じました。 設定箇所が多いため、戸惑った部分も少しありましたが、 IaCに対応することで、さらに使いやすくなると思います。 また、本サービスは同じくre:Inventで発表された Storage Browser for Amazon S3 を基にしているため、 プログラムスキルがある方はそちらのサービスを利用することで独自のカスタマイズも可能です。 https://aws.amazon.com/jp/about-aws/whats-new/2024/12/storage-browser-amazon-s3/ このブログが少しでも参考になれば幸いです。
はじめに こんにちは。KINTOテクノロジーズ(以下KTC) プラットフォームグループ Platform Engineeringチームで内製ツールの開発・運用をおこなっている山田です。 Platform Engineeringチームが開発するCMDBについて詳しく知りたい方は以下の記事をご覧ください! https://blog.kinto-technologies.com/posts/2023-12-14-CMDB/ 今回はCMDBの機能の一つであるチャットボットに、 生成AIとText-to-SQL を活用したCMDBのデータ検索機能とcsv出力機能を実装したお話をしたいと思います。 CMDBのチャットボットではCMDBの利用方法の質問やCMDBで管理されたデータに関する質問が可能です。 CMDBのデータに関する質問は、元々はChromaDBを使ったRAGの仕組みで質問に対する回答を生成していましたが、以下の理由からText-to-SQLでの実装に移行しました。 RAGと比較したText-to-SQLのメリット データの正確性と即時性 CMDBのデータベースから直接最新データをリアルタイムで取得が可能 データの更新に追加の処理が不要 システムの簡素化 ベクトルDBやEmbedding処理のためのインフラが不要(ChromaDBとEmbeddingしたデータの追加用バッチが不要になった) これらの理由から、CMDBのような構造化されたデータを扱うシステムにおいては、Text-to-SQLの方が適していると判断しました。 Text-to-SQL とは Text-to-SQLは自然言語のクエリからSQLクエリに変換する技術のことです。そのためSQLの知識を持たないユーザーでもデータベースから必要な情報を簡単に抽出することが可能になります。 これにより、CMDBのデータベースで管理しているプロダクト、ドメイン、チーム、ユーザー、ECR, VMDR等の脆弱性情報などのデータを自然言語のクエリから取得できるようになります。 KTC内で活用できそうな例だと、 適切な管理がされていないドメイン(CMDBでプロダクトと紐づいていないドメイン)一覧を取得 全社員の Atlassian ID の取得 MSP(Managed Service Provider)チームがPCの脆弱性対応などの依頼を、該当者をメンションした形でチケット作成するため グループごとに担当するプロダクトの関連リソースに検出された脆弱性数の集計 AWSリソースの起動停止スケジュールが設定されていないプロダクトの抽出 以前までだとこのようなデータの抽出依頼がPlatform Engineeringチームに来た際、担当者がCMDBのデータベースから直接SQLクエリを実行し、データを抽出、加工して依頼者にデータを渡していました。 依頼者がCMDBのチャットボットでText-to-SQLを使ってデータを抽出することができるようになると、以下の図のようにわざわざ担当者に依頼をしなくても簡単にデータの抽出ができるようになります。 Text-to-SQLは便利な機能ですが、安全でないSQL生成のリスクに気を付けなければなりません。 以下の図の例は極端なケースですが、自然言語からSQLが生成されるため意図せずにデータの更新や削除、テーブル構造の変更をするSQL文が生成される恐れがあります。 そのため以下の方法で安全でないSQLの生成を回避する必要があります。 Read OnlyのDBエンドポイントに接続する DBのユーザーを参照権限のみに設定する アプリケーションの実装で SELECT 以外のコマンドは実行しないようバリデーションチェックをおこなう システム構成 こちらがCMDBのアーキテクチャになります。今回のお話に関係のないリソースは除外してあります。 最初に説明した通り、元々ベクトルDBとしてChromaDB、CMDBの使い方に関する情報をConfluenceから取得(LlamaIndexで実装)、CMDBのデータをデータベースから取得(Spring AIで実装)してChromaDBに投入していました。 今回はCMDBのデータに関する質問の回答を Spring AI + ChromaDB でのRAG機能から、Text-to-SQLを使った機能に移行しました。 Text-to-SQLの実装 ここからは実際のコードをお見せしながら実装内容を説明したいと思います。 CMDBデータの検索機能 スキーマ情報を取得 まずはLLMにSQLを生成させるときに必要なスキーマ情報を取得します。 スキーマ情報は少ない方が精度は上がるため、必要なテーブルのみ指定する方法にしました。 また、LLMがSQL文を生成するときの判断材料としてテーブルのカラムのコメントが重要になるため、事前に全て追加しておく必要があります。 def fetch_db_schema(): cmdb_tables=['table1', 'table2', ...] cmdb_tables_str = ', '.join([f"'{table}'" for table in cmdb_tables]) query = f""" SELECT t.TABLE_SCHEMA, t.TABLE_NAME, t.TABLE_COMMENT, c.COLUMN_NAME, c.DATA_TYPE, c.COLUMN_KEY, c.COLUMN_COMMENT FROM information_schema.COLUMNS c INNER JOIN information_schema.TABLES t ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME WHERE t.TABLE_SCHEMA = 'cmdb' AND t.TABLE_NAME IN ({cmdb_tables_str}) ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME, c.COLUMN_NAME """ connection = get_db_connection() try: cursor = connection.cursor() cursor.execute(query) return cursor.fetchall() finally: cursor.close() connection.close() 取得結果例 TABLE_SCHEMA TABLE_NAME TABLE_COMMENT COLUMN_NAME DATA_TYPE COLUMN_KEY COLUMN_COMMENT cmdb product プロダクトテーブル product_id bigint PRI プロダクトID cmdb product プロダクトテーブル product_name varchar プロダクト名 cmdb product プロダクトテーブル group_id varchar プロダクトの担当部署(グループ)ID cmdb product プロダクトテーブル delete_flag bit 論理削除フラグ 1=削除, 0=未削除 取得したスキーマ情報をLLMに渡すプロンプト用のテキストにフォーマットする def format_schema(schema_data): schema_str = '' for row in schema_data: schema_str += f"Schema: {row[0]}, Table Name: {row[1]}, Table Comment: {row[2]}, Column Name: {row[3]}, Data Type: {row[4]}, Primary Key: {'yes' if row[5] == 'PRI' else 'no'}, Column Comment: {row[6]}\n" return schema_str カラムごとに以下のようなテキストに変換してLLMにスキーマ情報を渡します。 Schema: cmdb, Table Name: product, Table Comment: プロダクトテーブル, Column Name: product_id, Data Type: bigint, Primary Key: PRI, Column Comment: プロダクトID Schema: cmdb, Table Name: product, Table Comment: プロダクトテーブル, Column Name: product_name, Data Type: varchar, Primary Key: no, Column Comment: プロダクト名 Schema: cmdb, Table Name: product, Table Comment: プロダクトテーブル, Column Name: group_id, Data Type: varchar, Primary Key: no, Column Comment: プロダクトの担当部署(グループ)ID Schema: cmdb, Table Name: product, Table Comment: プロダクトテーブル, Column Name: delete_flag, Data Type: bit, Primary Key: no, Column Comment: 論理削除フラグ 1=削除, 0=未削除 CMDBのチャットボットからの質問とスキーマ情報からLLMでSQLクエリを生成する ここが自然言語からSQLクエリを生成するText-to-SQLの部分です。 質問内容とスキーマ情報をもとにプロンプトで様々な条件を指定してLLMにSQLを生成させています。 例えば、 MySQL:8.0 で有効なクエリを生成する ID以外の条件式はあいまい検索にする 基本的に論理削除されたデータは検索対象外とする SQL文以外は生成しない コンテキスト情報の追加 「KTCの~」、「CMDBの~」という質問は「全ての~」に変換する リージョンに関する質問はAWSのリージョンに変換する 東京リージョン -> ap-northeast-1 に変換 などがあり、特に「SQL文以外は生成しない」の指示が重要で、これがうまく伝わらないと 「頂いた情報をもとに次のSQLを生成しました。SELECT ~」 このような不要なテキストも含めた回答になってしまうことがよくありました。 したがって不要なテキスト、説明、マークダウン形式などが生成されない、「SELECT ~」のみのSQL文が生成されるようなプロンプトが必要です。 def generate_sql(schema_str, query): prompt = f""" Generate a SQL query based on the given MySQL database schema, system contexts, and question. Follow these rules strictly: 1. Use MySQL 8.0 syntax. 2. Use `schema_name.table_name` format for all table references. 3. For WHERE clauses: - Primarily use name fields for conditions, not ID fields - Use LIKE '%value%' for non-ID fields (fuzzy search) - Use exact matching for ID fields - Include "delete_flag = 0" for normal searches - Use "delete_flag = 1" only when the question specifically asks for "deleted" items CRITICAL INSTRUCTIONS: - Output MUST contain ONLY valid SQL query. - DO NOT include any explanations, comments, or additional text. - DO NOT use markdown formatting. - DO NOT generate invalid SQL query. Process: 1. Carefully review and understand the schema. 2. Generate the SQL query using ONLY existing tables and columns. 3. Double-check query against schema for validity. System Contexts: - Company: KINTO Technologies Corporation (KTC) - System: Configuration Management Database (CMDB) - Regions: AWS Regions (e.g., Tokyo region = ap-northeast-1) Interpretation Rules: - "KTC" or "CMDB" in query: Refer to all information in the database Examples: "Employees in KTC" -> "All users" "KTC's products" -> "All products" "Domains on CMDB" -> "All domains" - Region mentions: Interpret as AWS Regions Example: "ECR repositories in Tokyo region" -> "ECR repositories in ap-northeast-1" Database Schema: {schema_str} Question: {query} """ return llm.complete(prompt).text.strip() LLM, Text-to-SQLで生成したSQLに対して、 SELECT 文のみ許可するようバリデーションチェックをおこなう 安全でないSQL生成リスクの対策としてRead OnlyのDBエンドポイントに接続していますが、クエリ以外のSQLが生成されていないか確認をします。 LLMで生成されたSQLクエリを実行する LLMで生成されたSQLクエリとSQLの実行結果、質問内容からLLMで回答を生成する 最後に実行したSQLクエリ、SQLの実行結果、質問内容をLLMに渡して回答を生成します。 ここではText-to-SQLのときの指示が多いプロンプトとは違い、多くの指示はしていないですが、DBスキーマの構成や物理名は回答に含めないような指示は追加しています。 def generate_answer(executed_sql, sql_result, query): prompt = f""" Generate an answer based on the provided executed SQL, its result, and the question. Ensure the answer does not include information about the database schema or the column names. Respond in the same language as the question. Executed SQL: {executed_sql} SQL Result: {sql_result} Question: {query} """ return llm.stream_complete(prompt) 実行結果 質問:プラットフォームグループのプロダクトを教えて この質問とデータベーススキーマからLLMが以下のようなSQLを生成します。 SELECT product_name FROM product WHERE group_name LIKE '%プラットフォーム%' AND delete_flag = 0; そして、これらの情報とSQLの実行結果をLLMに渡して回答を生成しています。 これはECRスキャン結果の脆弱性情報の取得結果です。 CMDBデータのcsvファイル出力機能 スキーマ情報を取得 取得したスキーマ情報をLLMに渡すプロンプト用のテキストにフォーマットする ここまではCMDBデータの検索機能と同じです。 CMDBのチャットボットからの出力依頼とスキーマ情報から、LLMでSQLクエリを含むJSONオブジェクトを生成する ここでCMDBのデータをcsvとして出力したい内容の自然言語から、LLMを使って出力時のカラム名とそれを検索するためのSQL文のJSONオブジェクトを生成させます。 条件の内容は基本的にCMDBデータの検索機能のプロンプトと同じですが、テンプレート通りのJSONオブジェクトを生成させるための指示を強調しています。 以下がそのプロンプトです。 prompt = f""" Generate a SQL query and column names based on the given MySQL database schema, system contexts and question. Follow these rules strictly: 1. Use MySQL 8.0 syntax. 2. Use `schema_name.table_name` format for all table references. 3. For WHERE clauses: - Primarily use name fields for conditions, not ID fields - Use LIKE '%value%' for non-ID fields (fuzzy search) - Use exact matching for ID fields - Include "delete_flag = 0" for normal searches - Use "delete_flag = 1" only when the question specifically asks for "deleted" items Process: 1. Carefully review and understand the schema. 2. Generate the SQL query using ONLY existing tables and columns. 3. Extract the column names from the query. 4. Double-check query against schema for validity. System Contexts: - Company: KINTO Technologies Corporation (KTC) - System: Configuration Management Database (CMDB) - Regions: AWS Regions (e.g., Tokyo region = ap-northeast-1) Interpretation Rules: - "KTC" or "CMDB" in query: Refer to all information in the database Examples: "Employees in KTC" -> "All users" "KTC's products" -> "All products" "Domains on CMDB" -> "All domains" - Region mentions: Interpret as AWS Regions Example: "ECR repositories in Tokyo region" -> "ECR repositories in ap-northeast-1" Output Format: Respond ONLY with a JSON object containing the SQL query and column names: {{ "sql_query": "SELECT t.column1, t.column2, t.column3 FROM schema_name.table_name t WHERE condition;", "column_names": ["column1", "column2", "column3"] }} CRITICAL INSTRUCTIONS: - Output MUST contain ONLY the JSON object specified above. - DO NOT include any explanations, comments, or additional text. - DO NOT use markdown formatting. Ensure: - "sql_query" contains only valid SQL syntax. - "column_names" array exactly matches the columns in the SQL query. Database Schema: {schema_str} Question: {query} """ LLM, Text-to-SQLで生成したSQLに対して、 SELECT 文のみ許可するようバリデーションチェックをおこなう LLMで生成されたSQLクエリを実行する ここもCMDBデータの検索機能と同じです。 実行結果を使ってcsvファイルを出力する LLMに生成してもらったSQLの結果とカラム名を使ってcsvファイルの出力をします。 column_names = response_json["column_names"] # LLMで生成したJSONオブジェクトからカラム名を取得 sql_result = execute_sql(response_json["sql_query"]) # LLMで生成したSQLの実行結果 csv_file_name = "output.csv" with open(csv_file_name, mode="w", newline="", encoding="utf-8-sig") as file: writer = csv.writer(file) writer.writerow(column_names) writer.writerows(sql_result) return FileResponse( csv_file_name, media_type="text/csv", headers={"Content-Disposition": 'attachment; filename="output.csv"'} ) 実行結果 出力したい内容とカラムを指定してチャットに投げると、以下の流れでcsvファイルを出力できるようになりました。 まず、チャットでのメッセージとデータベーススキーマからLLMが以下のようなJSONオブジェクトを作成します。 { "sql_query": "SELECT service_name, group_name, repo_name, region, critical, high, total FROM ecr_scan_report WHERE delete_flag = 0;", "column_names": ["プロダクト名", "部署名", "リポジトリ名", "リージョン名", "critical", "high", "total"] } これらの情報をもとにSQLを実行して、csvファイルを出力する流れです。 プロダクト名 部署名 リポジトリ名 リージョン名 critical high total CMDB プラットフォーム ××××× ap-northeast-1 1 2 3 CMDB プラットフォーム ××××× ap-northeast-1 1 1 2 CMDB プラットフォーム ××××× ap-northeast-1 1 1 2 次のステップ ここまで生成AIとText-to-SQLを活用して、CMDBデータの検索機能とcsvデータ出力機能を実装しましたが、以下のようにまだまだ改善の余地があります。 CMDBデータの検索機能では2回LLMを呼び出しているため、速度が遅い 複雑、あいまいな質問に弱い 自然言語は本質的にあいまいなので、質問の内容に対して複数の解釈ができてしまう スキーマの正確な理解 スキーマ情報は複雑で、テーブル間のカラムの関係を理解させるのが大変 コンテキスト情報の追加 現状は最初のプロンプトで最低限のコンテキスト情報を追加しています。今後、より多くのコンテキスト情報を追加するとなると、最初のLLM呼び出しの前に質問内容を大量のコンテキスト情報から適切な質問に変換する処理方法や、fine-tuningでKTC特有のコンテキスト情報を含むデータセットで追加学習させる方法を検討しています Query Routingの実装 フロントエンドから呼び出すAPIはCMDBのデータ検索とcsv出力で2つ分かれているため、APIは1つに統一して、質問の内容からどちらのAPIを呼び出すべきか判断できるように改善したい さいごに 今回は生成AIとText-to-SQLを活用したCMDBのデータ検索機能とcsv出力機能についてお話ししました。 生成AI関連の技術は日々新しいものが出続けているためキャッチアップが大変ですが、今後はこれまで以上にアプリケーション開発にAIが絡んでくるため、興味のあるものや社内のプロダクトに適用できそうなものは積極的に活用していきたいと思います。
はじめに こんにちは! KINTOテクノロジーズでiOSアプリ開発に携わっているFelixです。今回は、8月22日(木)から24日(土)に開催されたiOSDCでの体験を共有したいと思います。前回のブログでお話した trySwift に続き、今回が二回目のiOSカンファレンス参加となります。今回はなんとスポンサーとして参加し、自社ブースを出展しました! KINTOブース 私たちのブースはKINTOブルーを基調としたデザインで、着用する法被もブランドカラーに合わせたものでした! ブースでは、参加者が実際に使われているプロジェクトコードを読み解き、対応する設問番号をコードにスタンプしていくコーディングチャレンジを開催しました。 またノベルティとして、KINTOのマスコットステッカーや、iOSデバイスを模した台紙を来場者に配布し、ステッカーで自由に台紙を飾りつけられるようにしました。また、コーディングチャレンジの参加者には、リワードとしてエコバッグまたはマルチチェーンのいずれかをお渡ししました。 このイベントは、参加者と交流し、KINTOテクノロジーズについての意見を伺い、プロジェクトに対する貴重なフィードバックを集める絶好の機会となりました。会話を通じて、当社の製品がどのように認識されているかについて新たな気づきを得ることができ、今後の改善に役立つ有益な指針を得られました。 他社ブース 他の企業のブースも非常に興味深く、教育的なものが多くありました。特に目立っていたブースをいくつか紹介します。 Sansan のブースでは、自社のテクノロジースタックが展示されており、来場者がさまざまなツールやフレームワークに触れてその反応を確かめられる内容だったため、興味深いものでした。 DeNA DeNAのブースでは、コードを読んだり地図を見たりしてクロスワードパズルを解くという楽しいアクティビティが用意されており、非常に印象的でした。 Bitkey Bitkeyのブースでは、ビーコンアプリを実装するためにテストデバイスとMacBookが必要でしたが、テストアプリを開発してビーコンを持っている人を見つけるという体験はとても楽しいものでした。 Glassfiber Glassfiberのブースでは、クイズを開催し、多くの来場者の興味を引き付けていて、楽しくてためになる内容でした。 プレゼンテーション いくつかのセッションに参加しましたが、特に印象的だった2つを紹介します。 StoreKit 2によるモダンなアプリ内課金 まず、StoreKit 2の最新の使用法についてのセッションです。これまでStoreKitを使った経験がなかったため、とても参考になる内容でした。このセッションでは、StoreKit 2の導入、実装、テストについて説明され、StoreKit 1との詳細な比較も行われました。プレゼンテーションでは以下の重要なポイントが取り上げられました:・async/awaitを使用した非同期処理の簡素化 ・レシート検証の効率化 ・サンドボックス環境、TestFlight、StoreKitTestを用いたテスト手法 アプリ内課金を統合しようとしている人にとって非常に有益な情報でした。特に興味深かった点として、StoreKitが「顧客が支払いをしたのに購入したアイテムを受け取れない」というケースを直接サポートしていないことが挙げられます。この点には驚かされました。 StoreKit 2によるモダンなアプリ内課金 このセッションでは、StoreKit 2の実装とテストについて説明され、アプリ内課金の改善点とその簡素化に焦点を当てていました GPSでどのようにして現在地が分かるのか もう一つ興味深かったセッションは、モバイル端末が信号を受信し、位置を計算する仕組みについてのものでした。セッションでは、Core LocationがGPS、Wi-Fi、携帯電話の信号をどのように組み合わせて正確な位置を特定するかについても説明されました。基本的なGPSの原理である衛星との三角測量が紹介されただけではなく、非常に遠い、弱い信号を受信する際の複雑なエンジニアリングについても詳しく触れられていました。また、スマートフォンがネットワークデータを活用して迅速かつ正確に位置情報を取得する方法についても解説されました。私はこの分野についてほとんど知らなかったため、とても啓発的で多くの学びが得られるセッションでした。 GPSでどのようにして現在地が分かるのか この動画では、GPSとネットワークデータを活用して、スマートフォンがどのようにして迅速かつ正確に位置を特定するのかについて詳しく説明されていました。 結論 全体として、iOSDC 2024は素晴らしい経験でした。洞察に満ちたセッションから多くを学ぶ機会であると同時に、より広範なiOS開発者コミュニティと交流する貴重な場でもありました。KINTOのブースを主催することで、多くの才能ある方々と直接交流し、彼らのフィードバックを聞きながら、自分たちの仕事を有意義な形で紹介できたことは、とても意義深いものでした。また、私が参加したプレゼンテーション、特にStoreKit 2とGPS技術に関するセッションは、KINTOで現在進行中のプロジェクトに直結する、実用的で有益な情報を得ることができました。StoreKit 2のasync/awaitの改善により、アプリ内課金の実装が大幅に合理化され、プロセスがより効率的でユーザーフレンドリーなものになると確信しています。同様に、GPSやネットワーク三角測量の高度な利用方法は、アプリ内の位置情報サービスをさらに強化し、ユーザーにとってより正確で迅速な結果を提供できるようになるでしょう。これらの学びを開発プロセスに統合し、開発者として成長を続けながら、プロジェクトの改善にも活かしていきたいと考えています。最後までお読みいただきありがとうございました!
はじめに こんにちは、9月入社のkhです! 本記事では2024年8、9月入社のみなさまに、入社直後の感想をお伺いし、まとめてみました。 KINTOテクノロジーズに興味のある方、そして、今回参加下さったメンバーへの振り返りとして有益なコンテンツになればいいなと思います! Naito 自己紹介 Engineering Officeに所属しています。プロダクト横断のプロセス改善を担当しています。 所属チームの体制は? Engineering Officeは1月にできたばかりで、2人です。 KTCへ入社したときの第一印象?ギャップはあった? 入社前の面談等で色々お話うかがっていたので特にギャップはなかったです。 拠点が東京・名古屋・大阪にあるのですが行き来が活発で、配属初日に名古屋所属の人たち複数にご挨拶して、名古屋って近いんだな〜とおもいました。 社内でいろんな方とお話するようになって感じたのは、「カイゼン」が日常で行われていること みなさんトヨタ生産方式(TPS)を知っていて、ちょっと感動しました。 現場の雰囲気はどんな感じ? いろんな開発チームに話をきいたり、相談しにいくのですがみなさん本当に親切ですし、巻き込まれてくれて日々感謝です! ブログを書くことになってどう思った? 入社前からテックブログは見ていました。書く側にまわれてうれしいです。 Mariさんからの質問:この会社を選んだ理由はなんでしょうか? 私自身、日常生活で車に親しんでいるので関心があるドメインであることと、今後ドンドン変化していきそうな会社だなとおもっていてその変化を体感し、楽しみたいとおもいました。 R.Y. 自己紹介 香港出身、2023年来日、学校を卒業して、今年8月KTCに入社し、現在セキュリティプライバシーGに所属しております。 所属チームの体制は? セキュリティプライバシーGに業務によってチームを分けられます。私はディフェンスチームに所属し、主に脆弱性診断とセキュリティ相談チケットを対応いたします。 KTCへ入社したときの第一印象?ギャップはあった? オフィスが広いです。フリースペースがたくさんあってみんな自由に使えます。勉強会と交流会が充実していて、業務範囲外の知識も身に付けられます。 現場の雰囲気はどんな感じ? みんな親しみがあって、話しかけやすいと思います。Slackで業務以外に、趣味や地域のチャンネルもたくさんあって、同じ趣味を持っている方と交流しやすい環境です。 ブログを書くことになってどう思った? 入社三ヶ月以来経験したことを見返して、入社予定のみんなに共有できて嬉しいです。 Naitoさんからの質問:ご出身地(香港)に旅行で行ってみたいです。オススメの観光地やお食事があったら教えて下さい! ディズニーのファンであれば、ぜひ香港ディズニーランドに行ってみてください。コンパクトなので1日で回りやすいパークです。 食事としては、B級グルメがたくさんあります。例えば、弾力のあるプリプリとした食感のカレーフィッシュボール、魚肉から作ったシュウマイ、紅茶の味が濃い香港スタイルミルクティーなどがおすすめです。香港では食べ歩きが普通ですので遠慮なくこういう文化を体験してください。 (ちなみに日本でも香港流行っている食事が食べられますので、香港に興味がある方ぜひご覧ください 譚仔三哥(タムジャイサムゴー)公式サイト ) tHyt 自己紹介 モビリティプロダクト開発部に所属するtHytです。一部エンジニアリードをやりつつ、主にフロントエンドエンジニアとして日々仕事をしております。 所属チームの体制は? チームとしては9名在籍しており、フロント・バック・インフラと各方面のエキスパートが集まっています。 KTCへ入社したときの第一印象?ギャップはあった? まだ若い会社ということもあり、制度として整っていない部分も多いのかと思いきやしっかり整備されていたのでびっくりした記憶があります。 現場の雰囲気はどんな感じ? Osaka Tech Labにて勤務していますが、メンバーがそこまで多くないためチーム問わず会話が発生していて飽きません。チームのメンバーは自分を除いて室町オフィスで勤務していますが、日々のミーティングや出張などを通して楽しく仕事できています。 ブログを書くことになってどう思った? 入社前から入社エントリーの存在は知っており、ついに自分が書く日が来たか…!という気持ちです。自己紹介のみならずテックブログでアウトプットをしていきたいなと思います。 R.Y.さんからの質問:入社後、慣れてなかったことがありますか?どうやって乗り越えましたか? 圧倒的に出張ですね…。自分のチームで大阪に勤務しているのは自分だけとなるため、今はコミュニケーション促進のために月1程度の頻度で東京へ出張しています。(出張を許可していただけるのは非常にありがたい) KTCに入るまで出張は1回しかしたことがなかったですし、新幹線に普段乗らないので最初は不安だらけでした。 今では慣れてきたのもあり、出張については特に不自由なくできるようになりました。 リモートでもコミュニケーションは取れるのですが、対面で行うコミュニケーションはまた一味違うので継続していきたいなと思っています。 kh ![alt text](/assets/blog/authors/kh/kh.png =250x) 自己紹介 分析Gに所属しています。分析するためのデータの収集、基盤を構築しています。 所属チームの体制は? リーダーと自分含めて10名になります(協力会社さん、派遣さん含む) KTCへ入社したときの第一印象?ギャップはあった? 新しめな会社なので、ベンチャー気質と思っていましたが、程よいゆるさを感じました。 現場の雰囲気はどんな感じ? 様々なバックボーンを持った集まりで、それぞれの異なる良さを活かし、和気藹々としています。 ブログを書くことになってどう思った? 元々TechBlogで積極的に発信していることを認識していたので、その第一歩を踏み出したのかなと思いました。 tHytさんからの質問:データ分析のコツみたいなのがあれば教えてください! 自分はデータ分析をすると言うより、データ分析をするための基盤を作ることがメインなので、ほしい回答とは異なるかもしれません。いかにすごいデータ分析を行なっても、使用するデータの品質が悪いと全く役に立つものにはなりません。分析基盤からデータの品質を確保することがデータ分析のコツの第一歩と思います。 woshahua 自己紹介 Woven payment solution開発Gに所属してる高です!中国出身ですが、関西弁が根付いてるエンジニアです。趣味はバスケ、サウナとポーカー、最近はプライベートでポーカーの勝率分析のアプリを作ってます! 所属チームの体制は? エンジニアメインの体制です、Woven側のエンジニアとも一緒に仕事させていただいてます。所属組織には関わらず目指す目標に対してチーム一丸で走れているのがとても良いです。 KTCへ入社したときの第一印象?ギャップはあった? 車領域ではまだ新しい会社ですが、幅広く色々なサービスや開発に挑戦してる印象でした。 ギャップはほぼなかったですが、入社後はより会社が社員の技術発信を応援してる点やほぼ毎週何らか社内勉強会が実施されてることに驚きました。 現場の雰囲気はどんな感じ? 技術経験に縛られず、広い技術範囲で色々挑戦できる環境だと思います。 困ったことがある際はすぐチームメンバーが助けてくれるので、とても恩恵を受けてます。 ブログを書くことになってどう思った? 会社としても、チームとしても積極的に技術発信を応援してると思うので、積極的に色々発信していきたいです! khさんからの質問:ペットを飼われていると伺いましたが、ペットとの生活で日常や仕事に影響を与えるものはありますでしょうか? 家でモルモットを飼っているんですが、仕事終わりにとても癒されています。在宅勤務もできるので、ペットと一緒に過ごす時間が増えて嬉しいです。仕事する上で心理的な余裕は良いパフォーマンスにも貢献できると思うので、自分に対してペットは仕事の緊張を緩めて心理的な余裕を与えてくれていると思ってます! Mari 自己紹介 開発支援部人事G採用Tに所属しています。エンジニアを中心に採用活動をしています。 所属チームの体制は? 私含め、6名です(1月からは全員室町オフィスにいます!) KTCへ入社したときの第一印象?ギャップはあった? オフィスが素敵!エンジニアの方々がみんなコミュニケーションとりやすく明るく楽しいと思いました! 現場の雰囲気はどんな感じ? みんな優しくて裏表がなく楽しい雰囲気です!真面目で、周りの人に対して、いつも気を配っている人しかいません。 ブログを書くことになってどう思った? 何を書こうか悩みましたが率直な自分の今の気持ちを書きました!みんなのブログも参考にしました! woshahuaさんからの質問:仕事中や休日に行うリフレッシュ方法はありますか? 仕事中の時はリフレッシュするために散歩に行ったりします。休日はなるべく外出したりジムに行ったりして体を動かすようにしています。 C4 ![alt text](/assets/blog/authors/kh/20250123/c4.jpg =250x) 自己紹介 KTC所属ですがKINTO管理部へ出向しており、主にオフィス総務を担当しています。 MBTI診断がISFJ-Tだったので管理部ってなかなか向いているのではないかと勝手に思っています。 所属チームの体制は? 所属チームで名古屋に勤務しているのは私を含めて8名です。 KTCへ入社したときの第一印象?ギャップはあった? 凄くお洒落なオフィスだなという印象でした。 オフィス勤務は初めてで、もっと堅苦しいイメージをしていたんですがチームの雰囲気は柔らかくフレンドリーですし勤務外でも食事に行ったりとコミュニケーションが盛んなのだなと感じます。 現場の雰囲気はどんな感じ? 自チームだけに限らず、まわりの皆さんがとにかく優しいと感じます。忙しそうな中で質問をしたりしても、嫌な顔をされた事がありません。 毎週1on1やチームMTGがあるので、チームメンバーがどんな動きをしているのか自分が関わっていなくても知る事が出来ますし自分の業務の困り事なども相談出来るタイミングを作っていただけている事がすごくありがたいです。 ブログを書くことになってどう思った? 正直な所、自分が受け持っている業務が事務・庶務系なのでエンジニアでない私の話をテックブログへ掲載して果たしてどなたかの参考になれるのだろうか…と考えたりしました(笑) Mariさんからの質問:私はMBTI診断はESFPでした!診断結果が当たりすぎていて怖いと思っていますw特に「細かいことが苦手」という部分が当たっていると思っています。。ISFJ-Tという診断は結構当たっていますか?特に当たっている部分はどんなところですか? 「変化に抵抗がある」 「完璧主義」 「自分に批判的」 の部分がグサッときました(笑) 人に喜んでもらえるなら自分のことは後!なマインドもかなりあるので結構当たっているなと私も感じました。 自己分析に適していると思うのでMBTI診断、皆さんにオススメです!! Mari 自己紹介 モビリティプロダクト開発部 DX企画推進G DX Planningチーム に所属のUI / UXデザイナーです。販売店様の業務DX化をサポートするプロダクトのデザインを行なっています。 所属チームの体制は? チームリーダー1名に、ディレクター3名、デザイナー4名の体制です。 KTCへ入社したときの第一印象?ギャップはあった? 母体が大きな会社なので仕組みやルールはしっかりしている一方で、新しい会社でもあるので雰囲気は自由です。ギャップは特になかったです。 現場の雰囲気はどんな感じ? タスクは多めで忙しいですが、その分やりがいがあり、活気にあふれた充実した雰囲気です。人間関係の雰囲気はとても良く、和気あいあいとしています。 ブログを書くことになってどう思った? 何を書こうか悩みましたが、素直な感想を書きました!他の方のブログも読むのが楽しみです。 C4さんからの質問:ご自宅にお猫がおられるとの事!我が家も猫×5と同居していますので今まで購入されたお猫様グッズの中で1番反応が良かったオススメを教えて下さい。(おやつでもおもちゃでもなんでもOKです!) 5匹 !! よいですね〜 🥰 うちの子は女王様気質で何を買っても「そんな子供だましのもの、興味ないんだけど」って態度であしらわれるんですが、やはりちゅーるは麻薬みたいで、特に高級食材が使われてるちょっと高いやつだと満足そうです😂 あと、本にゃんのご意向はわかりませんが、mirutoという自動猫トイレを購入して、掃除も楽だしかなり役に立っております !! ちなみに購入したキャットタワーは1度も乗る事なく1ヶ月以上経過し、泣く泣くそのまま粗大ゴミになりました….😇 さいごに みなさま、入社後の感想を教えてくださり、ありがとうございました! KINTO テクノロジーズでは日々、新たなメンバーが増えています! 今後もいろんな部署のいろんな方々の入社エントリが増えていきますので、楽しみにしていただけましたら幸いです。 そして、KINTO テクノロジーズでは、まだまださまざまな部署・職種で一緒に働ける仲間を募集しています! 詳しくは こちら からご確認ください!
はじめに VSCodeとCopilotの組み合わせが最高に楽しいです。楽しすぎて本業を忘れてしまいそうです。 本日はそんな楽しすぎる開発体験をみなさんにもぜひ知って欲しいという思いで記事を書きます。 この記事は KINTOテクノロジーズアドベントカレンダー2024 の25日目の記事です🎅🎄 今回の内容 去年辺りから熱くなったAI界隈ですが、今年もAIの話題が尽きない一年でしたね! KTCでは積極的にAIを取り入れて開発生産性低減の道を探っています。 今回はVSCode x Copilotの組み合わせでできることを紹介します。 でも、AIってなんか微妙なコードしか出してこないし、自分で書いた方が早いから使わないよね、という方もいるかもしれません。 僕も初めはそうでしたが、先日開催されたGitHub Universeで発表されたCopilot Chat/Editsを見て、AIとの共同作業が楽しくなるかもしれないと感じました。 と言うことで、今回は実際にCopilot Chat/Editsを使って、AIと共同でプログラミングをする中で、普段使いで有効だと思うTipsを紹介します。 https://reg.githubuniverse.com/flow/github/universe24/attendee-portal/page/livestream?tab.day=20241029 なんでFlutter? モバイル開発をする上で、iOSもAndroidも各OSの標準IDEを使って開発をするのが主流です。 しかし、今回はGitHub Copilotを使ってプログラミングするという観点で、VSCodeを使って開発することを前提とします。 そう考えると、モバイル開発で公式にVSCodeをサポートしているのはFlutterがメジャーです。 先日 FlutterKaigi にも参加し得るモノが多かったので、今回はFlutterを使ってAIとの共同作業をすることにします。ちなみに、FlutterKaigiのアプリ、本当に些細なIssueですが、僕もコントリビュートしました😀 実際に何ができるのか? GitHub Universeで発表された機能はPreview機能が多く、使用できる機能は限られていますが、すでにリリースされているCopilot Chat/Editsを使いこなすだけでも、AIとの共同作業が楽しくなり、開発者体験の向上が実感できました。 エンジニアの皆さんがAIを使ってコーディングするというと、ノーコードでアプリを作るとか、コードを自動生成するとか、そういったイメージがあるかもしれません。確かに、コードを自動生成する事は可能ですが、それはAIのほんの一部の機能でしかないです。 また、複雑な機能を持つコードを一度の指示で動作保証できるほどのコードを生成することは(指示出しが)難しいです。 この記事で紹介するのは、テストコードの作成、コードの整形・分割、リファクタリングなど、普段皆さんがコーディングでやっていることをAIとの共同作業を通じて行い、コードの品質を向上させることができるCopilot Chat/EditsのTipsになります。 それでは、ここからTipsの紹介です。 コードの要約 Copilot Chatの機能を使うと、コードの要約を簡単に作成できます。これを使えば、オンボーディング時のコード理解が早まります。 コメント記載 BEFORE AFTER コードにコメントを追加する際、Copilot Editsを使うと、Copilotが直接ファイルにコメントを追加してくれます。これにより、コードの理解が深まります。 また、すでにあるコメントを修正する際も、Copilotが適切なコメントを修正してくれます。 例えば上のクラスのaddメソッドに対して、バリデーションを追加した場合以下の様に指示するとコメントを更新してくれます。 BEFORE AFTER テストコードの作成 copilot editsのテキスト入力欄の上に現在のファイル以外に参照できるファイルを増やす事ができるので、テストコードを書いてもらうときは、 テスト用のファイルを作成し、そのファイルを開いたまま、参照ファイルに対象ファイルを追加すれば、テストコードを書いてくれます。 コードの整形・分割 Flutter等の宣言型UIを採用している言語でUIを作成しているとどうしてもネストが深くなってしまう問題が頻発します。 :::details 整形前のコード class SomethingPage extends StatelessWidget { const SomethingPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Something Page'), ), body: Center( child: Container( padding: const EdgeInsets.all(16.0), color: Colors.blueAccent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(8.0), color: Colors.redAccent, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(4.0), color: Colors.greenAccent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(2.0), color: Colors.yellowAccent, child: const Text('Deeply Nested Widget 1'), ), Container( padding: const EdgeInsets.all(2.0), color: Colors.purpleAccent, child: const Text('Deeply Nested Widget 2'), ), ], ), ), ], ), ), ], ), ), ), ); } } ::: こういった問題もCopilot Editsを使うと、コードを見やすい単位で分割、整形してくれます。 :::details 整形後のコード class SomethingPage extends StatelessWidget { const SomethingPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Something Page'), ), body: Center( child: Container( padding: const EdgeInsets.all(16.0), color: Colors.blueAccent, child: const NestedContainer(), ), ), ); } } class NestedContainer extends StatelessWidget { const NestedContainer({super.key}); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ InnerContainer(), ], ); } } class InnerContainer extends StatelessWidget { const InnerContainer({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(8.0), color: Colors.redAccent, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ DeeplyNestedContainer(), ], ), ); } } class DeeplyNestedContainer extends StatelessWidget { const DeeplyNestedContainer({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(4.0), color: Colors.greenAccent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ DeeplyNestedWidget1(), DeeplyNestedWidget2(), ], ), ); } } class DeeplyNestedWidget1 extends StatelessWidget { const DeeplyNestedWidget1({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(2.0), color: Colors.yellowAccent, child: const Text('Deeply Nested Widget 1'), ); } } class DeeplyNestedWidget2 extends StatelessWidget { const DeeplyNestedWidget2({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(2.0), color: Colors.purpleAccent, child: const Text('Deeply Nested Widget 2'), ); } } ::: リファクタリング 歴史あるコードでよく見かける、依存度が複雑に絡み合ってしまったメソッドも、Copilot Editsを使うことで、SOLID原則に基づいたリファクタリングが可能です。 :::details 依存度が複雑なメソッドを持つクラス class SomethingLogic { Future<void> performComplexOperation() async { // Fetch data from remote server final response = await http.get(Uri.parse('https://example.com/data')); if (response.statusCode != 200) { throw Exception('Failed to load data'); } final data = jsonDecode(response.body); // Save data to local database final database = openDatabase( join(await getDatabasesPath(), 'app_database.db'), onCreate: (db, version) { return db.execute( 'CREATE TABLE data(id INTEGER PRIMARY KEY, value TEXT)', ); }, version: 1, ); final db = await database; await db.insert( 'data', {'id': data['id'], 'value': data['value']}, conflictAlgorithm: ConflictAlgorithm.replace, ); // Save data to shared preferences final prefs = await SharedPreferences.getInstance(); await prefs.setString('data', jsonEncode(data)); // Perform some complex logic if (data['value'] == 'special') { // ...existing code... print('Special value found!'); // ...existing code... } else { // ...existing code... print('Regular value found.'); // ...existing code... } } } :::: :::details SOLID原則に基づいたリファクタリングをしてもらったコード class DataFetcher { final http.Client client; DataFetcher(this.client); Future<Map<String, dynamic>> fetchData(String url) async { final response = await client.get(Uri.parse(url)); if (response.statusCode != 200) { throw Exception('Failed to load data'); } return jsonDecode(response.body); } } class DatabaseHelper { Future<Database> getDatabase() async { return openDatabase( join(await getDatabasesPath(), 'app_database.db'), onCreate: (db, version) { return db.execute( 'CREATE TABLE data(id INTEGER PRIMARY KEY, value TEXT)', ); }, version: 1, ); } Future<void> saveData(Database db, Map<String, dynamic> data) async { await db.insert( 'data', {'id': data['id'], 'value': data['value']}, conflictAlgorithm: ConflictAlgorithm.replace, ); } } class SharedPreferencesHelper { Future<void> saveData(Map<String, dynamic> data) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('data', jsonEncode(data)); } } class SomethingLogic { final DataFetcher dataFetcher; final DatabaseHelper databaseHelper; final SharedPreferencesHelper sharedPreferencesHelper; SomethingLogic(this.dataFetcher, this.databaseHelper, this.sharedPreferencesHelper); Future<void> performComplexOperation() async { final data = await dataFetcher.fetchData('https://example.com/data'); final db = await databaseHelper.getDatabase(); await databaseHelper.saveData(db, data); await sharedPreferencesHelper.saveData(data); if (data['value'] == 'special') { // ...existing code... print('Special value found!'); // ...existing code... } else { // ...existing code... print('Regular value found.'); // ...existing code... } } } :::: ここまでのリファクタリングを自分でやると、時間がかかりますが、Copilot Editsを使うことで、短時間でリファクタリングが可能です。 だいぶ見やすいコードになったので、ここから自身の経験をもとに、さらにリファクタリングを行う事も可能です。 その他 これ以外にも、基本的なSuggestionsの機能が強力で、普段であれば実装に困った際はブラウザとIDEを行き来していましたが、ほとんどの困りごとをCopilotとの相談で解決できるので、開発への集中が途切れなくなりました。これは地味に大きいです。 指示だしのコツ AIとの共同作業は、新しい発見がたくさんありますが、AIに対する指示だしが重要な要素だなと感じました。 僕の周りにもAIが使いづらく感じているエンジニアは多くいますが、AIに対して具体的な指示を出すことに慣れると、AIとの共同作業が楽しくなると思います。 僕が感じた指示だしのコツは シンプルな内容 具体的な内容 どのファイル 何をする どのようにする 一度に複数の指示を出さない 特に、指示を出す側がしっかりとしたプログラミング原則や設計思想を理解していることが重要だと感じます。 まとめ 今回はVSCode x Copilotの組み合わせによって、AIとできることを紹介しました。 Copilot Chat/Editsを使うことで、コードの要約、コメント記載、テストコードの作成、コードの整形・分割、リファクタリングが簡単にできる事が少しでも伝えられたら幸いです。 エンジニア界隈ではAIに対して賛否両論あり、 僕の周りのエンジニアもまだまだAIに対して抵抗がある人が多いです。 世間の流れは確実にAIに向かっていて、今後もAIは進化し、どんどんできることが増えていくことが予想されます。 僕らエンジニアはどうAIを使っていくかを考える時代になったのかなと感じでいます。 この記事が少しでもAIとの共同作業に対して興味を持っていただけたら幸いです。