
- TOP
- ã¿ã°äžèЧ
- Android
Android
ã€ãã³ã
ãã¬ãžã³
æè¡ããã°
ããã«ã¡ã¯ãFindyã§ã¢ãã€ã«ã¢ããªéçºãæ
åœããŠããå è€ãšäž»èšã§ãã Findyåã®ã¢ãã€ã«ã¢ããªãFindy Eventsãã«ã€ããŠã¯ãå
æ¥ React Nativeéžå®ã®çµç·¯ãšç«ã¡äžãã®å
šäœå ãå
¬éããŸããã åèšäºã§ã¯UIã©ã€ãã©ãªåšãã«ã¯æ·±ãèžã¿èŸŒããªãã£ãã®ã§ãä»åã¯ãã®ç¶ç·šã§ããUIã©ã€ãã©ãªã®éžå®ãšå®è£
ãã¿ãŒã³ã«çµã£ãŠãå±ãããŸãã å
·äœçã«ã¯ãåœåæ¡çšããŠããTamaguiããHeroUI Native + react-native-unistylesã«ä¹ãæãããŸã§ã®å€æãšãWrapper Componentã軞ã«ããUIæ§ç¯ã®é²ãæ¹ãäžå¿ã§ãã ãªããæ¬èšäºã§é¡æã«ããŠããFindy Eventsã¯ã App Store ãš Google Play ã§å
¬éããŠããŸãã Tamaguiæ¡çšã®èæ¯ Tamaguiã䜿ã£ãŠã¿ãŠèŠããŠããèª²é¡ Android宿©åºæã®æå Sheet衚瀺çŽåŸã®ã¿ãããå¹ããªã Sheetå
ã®ScrollViewã§ã¹ã¯ããŒã«ãããšSheetãéãã Expo SDK 54察å¿ã®ã¿ã€ãã³ã° HeroUI Native + react-native-unistylesã«å€æŽã決ããçç± Î²çæ¡çšã®ãªã¹ã¯ãšãã®å¯Ÿçæ¹é å
·äœçãªã³ãŒãäŸ HeroUI Nativeã®Wrapper Component react-native-unistylesãšWrapper Componentãçµã¿åãããUI Componentã®å®è£
ãŸãšã Tamaguiæ¡çšã®èæ¯ Findyã®ã¢ãã€ã«ã¢ããªéçºã§ãåœåUIã©ã€ãã©ãªãšããŠæ¡çšããŠããã®ã¯ Tamagui ã éžå®æã®åè£ã¯ gluestack-ui ãšTamaguiã®2ã€ã§ãæçµçã«Tamaguiãéžã³ãŸããã æ±ºãæãšãªã£ãçç±ã¯å€§ãã3ã€ãããŸãã 1ã€ç®ã¯ãã©ã€ãã©ãªãšããŠã®ä¿¡é Œæ§ãšæ
å ±éã§ãã Tamaguiã¯v0.1.0ã2021幎3æã«ãªãªãŒã¹ããã5幎以äžã«ããã£ãŠéçºãç¶ããŠããOSSã§ãã GitHubã®ã¹ã¿ãŒæ°ãæäŸãããComponentæ°ã®å
å®åºŠãããæ¬çªãããã¯ãã«æ¡çšããŠã倧ããå€ããªããšå€æããŸãããæŽå²ãããåããããäžã®èšäºãæ¯èŒçå€ããéçºæã®èª¿æ»ã³ã¹ããæããããç¹ãåŸæŒãææã§ãã 2ã€ç®ã¯ãiOSãšã³ãžãã¢ãšããŠã®éçºçµéšãšã®èŠªåæ§ã§ãã iOSéçºã§ã¯OSSãšããŠæäŸãããUIããã®ãŸãŸå©çšããããããã¯èš±å®¹ç¯å²ã§æ¹å€ããã¢ãããŒããäžè¬çã§ãTamaguiã¯ãã®ã¡ã³ã¿ã«ã¢ãã«ã«åèŽããŠããŸããã SwiftUIãšåã宣èšçUIã®ææ³ãæã¡ã瞊æ¹åã«äžŠã¹ãSwiftUIã® VStack ãTamaguiã® YStack ã«å¯Ÿå¿ããããã«ãåèŠçŽ ã軞æ¹åã«ç©ã¿äžããŠã¬ã€ã¢ãŠããçµã¿ç«ãŠãèãæ¹ãå
±éããŠããç¹ã銎æã¿ããããã€ã³ãã§ãã äžæ¹ã®gluestack-uiã¯ãshadcn/uiãšåæ§ã«CLIã§Componentã®ã³ãŒããèªåã®ãªããžããªã«çæããå¿
èŠã«å¿ããŠç·šéããŠäœ¿ãæ¹åŒãæ¡ã£ãŠããŸããTailwindã©ã€ã¯ãªã¯ã©ã¹æå®ãšåãããŠãã¢ãã€ã«ãšã³ãžãã¢åºèº«ã®èªåã«ã¯ããåã£ä»ãã«ããæããŸããã 3ã€ç®ã¯ãPropsããŒã¹ã§çŽæçã«ã¹ã¿ã€ãªã³ã°ã§ããããšã§ãã <Stack m="$2" p="$2" backgroundColor="$background"> ã®ããã«ãComponentã«å¯ŸããŠPropsçµç±ã§ãã¶ã€ã³ããŒã¯ã³ãçŽæ¥åœãŠãããæèŠãšããŠã¯æãªããã®CSSãSwiftUIã®modifierã«è¿ãæžãå³ã§æ±ããŸãã ãã¶ã€ã³ããŒã¯ã³ã«ããããŒã管çã§ã©ã€ã/ããŒã¯ããŒãã®åãæ¿ãã容æãªç¹ã決ãæã«ãªããŸããã Tamaguiã䜿ã£ãŠã¿ãŠèŠããŠããèª²é¡ Tamaguiãæ¡çšããŠã¢ããªãéçºããäžã§ã幟ã€ãèŠããŠãã課é¡ããããŸãããHeroUI Nativeãžã®ä¹ãæãã倿ããèæ¯ã§ããããããããã§ççŽã«å
±æããŸãã Android宿©åºæã®æå iOSã·ãã¥ã¬ãŒã¿ãAndroidãšãã¥ã¬ãŒã¿ã§ã¯åé¡ãªããã®ã®ãAndroid宿©ã§ã®ã¿èµ·ããæåã«å¹Ÿã€ãã¶ã€ãããŸãããããã§ã¯å°è±¡ã«æ®ã£ã2ã€ã®ã±ãŒã¹ããå®ã³ãŒããšåãããŠç޹ä»ããŸãã Sheet衚瀺çŽåŸã®ã¿ãããå¹ããªã ã¡ãã¥ãŒã確èªãã€ã¢ãã°ãšã㊠Sheet ã衚瀺ããçŽåŸã«ãå
éšã®ãã¿ã³ãã¿ããããŠãåå¿ããªãããšããããŸããã æåã¯ããã«ã¿ãããå¿
èŠãªã®ããšæããŸããããå°ãæéããããšåå¿ãããããUIã¢ãã¡ãŒã·ã§ã³ã®å®äºãŸã§ã¿ãããåãä»ããããŠããªããšæšæž¬ããŸãããããã©ã«ãã®ã¢ãã¡ãŒã·ã§ã³ã¯å®äºãŸã§ã«æéããããããã®éã¿ãããåãããŒããŠããããã§ãã ããã§ Sheet ã« animation="200ms" ã®ããã«çãæéã®ã¢ãã¡ãŒã·ã§ã³ãæç€ºçã«æå®ãããšããã衚瀺å®äºãŸã§ã®æéãçããªãã衚瀺çŽåŸã®ã¿ããã«ãããåå¿ããããã«ãªã£ãŠè§£æ¶ããŸããã <Sheet modal open = { open } onOpenChange= { setOpen } snapPoints= {[ 50 ]} animation= "200ms" // æç€ºããªããšAndroid宿©ã§è¡šç€ºçŽåŸã®ã¿ãããå¹ããªã > < Sheet . Overlay /> < Sheet . Frame padding = "$4" > < Button onPress = { handleConfirm } > 確å®ãã </ Button > </ Sheet . Frame > </Sheet> Sheetå
ã®ScrollViewã§ã¹ã¯ããŒã«ãããšSheetãéãã éžæè¢ãå€ãé
ç®ãéžã°ããUIãšããŠãéžæè¢äžèЧã ScrollView ã§è¡šç€ºãã Sheet ãåºããŠããã®ã§ãããPixel 10ã§åäœç¢ºèªããŠãããšããã Sheet å
ãã¹ã¯ããŒã«ããããšãããšãã®ãŸãŸ Sheet ããšéããŠããŸãäºè±¡ãçºçããŸããã 該åœç®æã¯ããããæ¬¡ã®ãããªæ§æã§ãã import { ScrollView } from "react-native" ; import { Sheet } from "tamagui" ; < Sheet modal open = { open } onOpenChange = { setOpen } snapPoints = { [ 80 ] } > < Sheet . Overlay /> < Sheet . Frame > < ScrollView > { options. map (( option ) => ( < Option key = { option. id } option = { option } /> )) } </ ScrollView > </ Sheet . Frame > </ Sheet > Sheet å
ã® ScrollView ã§ã®ã¹ã¯ããŒã«æäœãã Sheet ãéããããã®ãã©ãã°ãžã§ã¹ãã£ãšè¡çªããŠããã®ãåå ãšæšæž¬ããŠããŸãã æ«å®å¯Ÿå¿ãšã㊠Sheet åŽã« disableDrag ãæå®ãããšãããã¹ã¯ããŒã«æã« Sheet ãéããäºè±¡ã¯è§£æ¶ãããŸããã <Sheet modal open = { open } onOpenChange= { setOpen } snapPoints= {[ 80 ]} disableDrag // ãã©ãã°ã§Sheetãéããæåãç¡å¹åããScrollViewã®ã¹ã¯ããŒã«ãšè¡çªããªãããã«ãã > < Sheet . Overlay /> < Sheet . Frame > < ScrollView > { options. map (( option ) => ( < Option key = { option. id } option = { option } /> )) } </ ScrollView > </ Sheet . Frame > </Sheet> ãã ã disableDrag ãæå®ãããšãæ¬æ¥ãã©ãã°ã§éããããã¯ãã® Sheet ãéããããªããªããUXäžã®åŠ¥åãçºçããŠããŸããŸãã Sheet åšãã¯Android宿©ã§ã®æåãå®å®ããªãã±ãŒã¹ãç¶ããããšããããæ ¹æ¬çã«ã¯Tamaguiã® Sheet èªäœã®å©çšãèŠçŽãå¿
èŠããããšæãã¯ãããåŸã«HeroUI Nativeãžã®ä¹ãæããæ€èšããäžå ã«ããªããŸããã Expo SDK 54察å¿ã®ã¿ã€ãã³ã° Expoã¯React Nativeã§ã¯ãã¹ãã©ãããã©ãŒã ã®ã¢ããªãéçºããããã®ãã¬ãŒã ã¯ãŒã¯ã§ãã¢ãã€ã«ã¢ããªã®èšŒææžåšããç°¡åã«æ±ããä»çµã¿ããæ§ã
ãªäŸ¿å©æ©èœãSDKãšããŠæäŸããŠããŸãããã®Expo SDK 54ã¯2025幎9æ11æ¥ã«ãªãªãŒã¹ãããTamaguiã®GitHubäžã§ã察å¿ã«åããWIP Pull Requestãè°è«ãåããŠããŸããã äžæ¹ã§ãTamaguiåŽã®å¯Ÿå¿çããªãªãŒã¹ãããã®ã¯2025幎11æ15æ¥ã§ã察å¿çãªãªãŒã¹æ¥ã®ã¢ããŠã³ã¹ã¯äºåã«ã¯ãªãã£ããšèšæ¶ããŠããŸãã 5幎以äžã®æŽå²ãæã€ã©ã€ãã©ãªã§ãå
éšã«å€ãã®å®è£
ãæ±ããŠããåãææ°Expo SDKãžã®è¿œåŸã¯ç°¡åã§ã¯ãªãã®ã ãããšæšæž¬ããŠããŸããOSSã©ã€ãã©ãªãæ¡çšãã以äžããããã远åŸã³ã¹ããšã¯ãã¬ãŒããªãã ãšæ¹ããŠæããåºæ¥äºã§ããã HeroUI Native + react-native-unistylesã«å€æŽã決ããçç± ãããŸã§èŠãŠãã課é¡ãèžãŸããæ¬¡ã®UIåºç€ãšããŠHeroUI Nativeãšreact-native-unistylesãçµã¿åãããæ§æãéžã³ãŸããã HeroUI Nativeãéžãã çç±ã¯ãComponentã®å®æåºŠãé«ããç»é¢å®è£
ã®ã¹ããŒããäžãããããšèããããã§ããããã©ã«ãã®èŠãç®ãæŽç·ŽãããŠãããã¢ãã¡ãŒã·ã§ã³ãã€ã³ã¿ã©ã¯ã·ã§ã³ãäœã蟌ãŸããŠããããã现ãã調æŽãå ããªããŠããã®ãŸãŸç»é¢ãçµã¿ç«ãŠãããŸããFindy Eventsã¯ãŸã æ©èœãå¢ãããŠããæ®µéã«ãããå¿
èŠãªç»é¢ãçŽ æ©ã圢ã«ã§ããããšã¯ãUIã©ã€ãã©ãªã«åŒ·ãæ±ããŠããç¹ã§ããã ã¹ã¿ã€ãªã³ã°ã«react-native-unistylesãæ¡çšããã®ã¯ãã¹ã¿ã€ã«å®çŸ©ãæšæºã®APIã«å¯ãããã£ãããã§ããHeroUI Nativeèªäœã¯å
éšã®ã¹ã¿ã€ãªã³ã°ã«UniwindïŒTailwind v4ããŒã¹ã®ä»çµã¿ïŒã䜿ã£ãŠããŸããããããã¯ãåŽã®ã¹ã¿ã€ã«ã¯ãReact Nativeæšæºã®StyleSheet APIã«è¿ãæžãå³ã§æ±ããããšèããŸãããreact-native-unistylesãªã StyleSheet.create ãèµ·ç¹ã«ãè²ãfontSizeãspacingãradiusãšãã£ãããŒã¯ã³ãå®çŸ©ã§ããã¢ãã€ã«ãšã³ãžãã¢ã«ãšã£ãŠã銎æã¿ãããæžãå³ã«ãªããŸãã Î²çæ¡çšã®ãªã¹ã¯ãšãã®å¯Ÿçæ¹é äžæ¹ã§ãHeroUI Nativeã®æ¡çšã«ã¯ãªã¹ã¯ããããŸããã æ€èšããŠããåœæã®HeroUI Nativeã¯ãŸã Î²çæ®µéã®ã©ã€ãã©ãªã§ãããæ£åŒçã®v1.0.0ã¯2026幎3æ19æ¥ã«ãªãªãŒã¹ãããFindy Eventsã®ã¢ããªãªãªãŒã¹æç¹ã§ã¯v1.0ç³»ã«å°éããŠããŸãããã ãæ¡çšã倿ããæç¹ã§ã¯ãŸã æ£åŒçåã®ã©ã€ãã©ãªã§ããÎ²çæ®µéã®ã©ã€ãã©ãªãæ¬çªãããã¯ãã®åå°ã«æ®ãã以äžãç¹ã«ç Žå£ç倿Žã«ã¯æ³šæãå¿
èŠã§ãããAPIããŸã åºãŸããã£ãŠããããããŒãžã§ã³ã¢ããã®ãã³ã«å©çšåŽã®ã³ãŒãä¿®æ£ãè¿«ãããå¯èœæ§ãããããã§ãã ããã§ãHeroUI Nativeã®Componentã¯ç»é¢ããçŽæ¥äœ¿ãããå¿
ãWrapper Componentãæãã§å©çšããæ¹éã«ããŸãããWrapper Componentãšããå¢çã1ææãããšã§ãHeroUI NativeåŽã®APIãå€ãã£ãŠããä¿®æ£ãPrimitiveå±€ã«éã蟌ããããŸãã ãã®æ¹éã¯ãç Žå£ç倿Žãžã®åãã§ãããšåæã«ãå°æ¥çã«HeroUI Nativeã®Componentãèªåãã¡ã®ç¬èªå®è£
ãžåãæ¿ããããªã£ãå Žåã«ãå¹ããŸããå©çšåŽã¯Wrapper Componentçµç±ã§ããComponentã«è§ŠããŠããªããããå
åŽã®å®è£
ãHeroUI Nativeããç¬èªå®è£
ãžå·®ãæ¿ããŠããç»é¢åŽã®ã³ãŒãã«åœ±é¿ãåºããã«ç§»è¡ã§ããŸãã å
·äœçãªã³ãŒãäŸ ããã§ã¯ãHeroUI Nativeã®ComponentãWrapper ComponentãšããŠå®çŸ©ããreact-native-unistylesãšçµã¿åãããŠç»é¢ãæ§ç¯ãããŸã§ã®æµãã玹ä»ããŸãã HeroUI Nativeã®Wrapper Component ãŸããæãã·ã³ãã«ãªäŸãšããŠã Skeleton ã®Wrapper Componentã®ã³ãŒããèŠãŠãããŸãããã // src/components/primitives/skeleton/skeleton.component.tsx import type { SkeletonProps } from "heroui-native" ; import type { PropsWithChildren } from "react" ; import { Skeleton as HeroUISkeleton } from "heroui-native" ; type Props = PropsWithChildren < Pick < SkeletonProps , "isLoading" | "style" > >; export const Skeleton = ( { isLoading = true , style , children } : Props ) => { return ( < HeroUISkeleton isLoading = { isLoading } variant = "shimmer" style = { style } > { children } </ HeroUISkeleton > ); } ; HeroUI Nativeã®Componentã¯å€ãã®Propsãæã£ãŠããŸãããã¢ããªå
ã§å®éã«äœ¿ãPropsã¯éãããŸãã Wrapper Componentãå®çŸ©ããæã«ã Pick<> ã§å¿
èŠãªPropsã ããå
¬éããããšã§ãå©çšåŽã®ã€ã³ã¿ãŒãã§ãŒã¹ãã·ã³ãã«ã«ä¿ã€ããšãã§ããŸãã ãŸãã variant ã®ããã«ã¢ããªå
šäœã§åºå®ãããèšå®å€ã¯Wrapper Componentå
ã§åã蟌ãããšã§ãå©çšåŽãæèããå¿
èŠããªããªããŸãã æ¬¡ã«ãããå°ãè€éãªäŸãšããŠã Button ã®Wrapper Componentã®ã³ãŒããèŠãŠã¿ãŸãããã HeroUI Nativeã«ã¯ Button.Label ã Card.Body ã®ããã«ããµãComponentãæã€Compound ComponentããããŸãã ã©ããããåŸã Button.Label ã®ãããªåŒã³åºãæ¹ãç¶æãããå Žåã¯ã Object.assign() ã䜿ã£ãŠèŠªComponentã«ãµãComponentãçŽä»ããŸãã // src/components/primitives/button/button.component.tsx import type { PropsWithChildren } from "react" ; import { Button as HeroUIButton } from "heroui-native" ; import type { ButtonLabelProps, ButtonRootProps } from "heroui-native" ; import { StyleSheet } from "react-native-unistyles" ; export type ButtonComponentProps = PropsWithChildren < Pick < ButtonRootProps , "style" | "size" | "onPress" | "isDisabled" | "animation" > >; export type ButtonLabelComponentProps = PropsWithChildren < Pick < ButtonLabelProps , "style" > >; const ButtonRoot = ( { children , style , size , onPress , isDisabled , animation , } : ButtonComponentProps ) => { return ( < HeroUIButton style = { style } size = { size } onPress = { onPress } isDisabled = { isDisabled } animation = { animation } > { children } </ HeroUIButton > ); } ; const ButtonLabel = ( { children , style } : ButtonLabelComponentProps ) => { return ( < HeroUIButton . Label style = { style } > { children } </ HeroUIButton . Label > ); } ; export const Button = Object . assign (ButtonRoot, { Label : ButtonLabel, } ); ãã®ããã« Button.Label ã®åœ¢ã§ã¢ã¯ã»ã¹ã§ãããããå©çšåŽã®ã³ãŒãã¯HeroUI Nativeã®å
ã®APIãšåã䜿ãå¿å°ãç¶æã§ããŸãã react-native-unistylesãšWrapper Componentãçµã¿åãããUI Componentã®å®è£
ç¶ããŠãå®çŸ©ããWrapper Componentãreact-native-unistylesãšçµã¿åãããŠãå®éã®ç»é¢ã§å©çšããUI ComponentãäœãäŸã玹ä»ããŸãã ãŸããã«ã©ãŒããŒã¯ã³ãå®çŸ©ãããã¡ã€ã«ãçšæããŸãã // src/styles/generated/primitive-colors.ts export const primitiveColors = { black : "#000000" , white : "#ffffff" , ... blue400 : "#377ecd" , blue500 : "#055ec1" , blue600 : "#044b9a" , ... } as const ; export type PrimitiveColors = typeof primitiveColors ; 次ã«ãreact-native-unistylesã§ãããŒãã«æ²¿ã£ãã¹ã¿ã€ãªã³ã°ãå¹ççã«è¡ããããã«ã StyleSheet.configure ã§ã¢ããªå
šäœã®ããŒãããŒã¯ã³ãå®çŸ©ããŸãã æ¬ã¢ããªã§ã¯ãè²ããã©ã³ããµã€ãºãspacingãè§äžžãªã©ãããŒã¯ã³ãšããŠç®¡çããŠãããComponentããšã®é
è²ãããã«éçŽããŠããŸãã // src/styles/index.ts import { StyleSheet } from "react-native-unistyles" ; import { primitiveColors } from "./generated/primitive-colors" ; const tokens = { colors : primitiveColors, fontSize : { xs : 11 , sm : 12 , md : 14 , lg : 16 , xl : 20 , "2xl" : 22 } , radius : { xs : 2 , sm : 4 , md : 6 , lg : 8 , xl : 12 } , space : { "2xs" : 4 , xs : 8 , sm : 12 , md : 16 , lg : 24 } , } as const ; const lightTheme = { // ã°ããŒãã«å®çŸ© background : primitiveColors.white, backgroundPress : primitiveColors.grey50, ... // Componentããšã®å®çŸ© button : { danger : { background : primitiveColors.red500, color : primitiveColors.white, backgroundPress : primitiveColors.red600, } , } , fontSize : tokens.fontSize, radius : tokens.radius, space : tokens.space, } as const ; const darkTheme = { ... } StyleSheet .configure( { themes : { light : lightTheme, dark : darkTheme } , } ); StyleSheet.create ã®ã³ãŒã«ããã¯é¢æ°ãããã®ããŒãããŒã¯ã³ã«ã¢ã¯ã»ã¹ã§ããããã«ã¹ã¿ã€ã«å®çŸ©èªäœã颿°ã«ããããšã§ãPropsã®å€ã«å¿ããåçãªã¹ã¿ã€ã«åãæ¿ããå®çŸã§ããŸãã ãããèžãŸããŠãå
çšã®Button Wrapper Componentãã«ã¹ã¿ãã€ãºãããDangerButtonãUI Componentã®äŸãèŠãŠã¿ãŸãããã // src/components/buttons/danger-button/danger-button.component.tsx import { StyleSheet , useUnistyles } from "react-native-unistyles" ; import { Button } from "@/components/primitives/button" ; import type { ButtonComponentProps, ButtonLabelComponentProps, } from "@/components/primitives/button" ; const DangerButtonRoot = ( { children , style , ... rest } : ButtonComponentProps ) => { const { theme } = useUnistyles(); return ( < Button style = { [ styles.root, style ] } animation = { { highlight: { backgroundColor: { value: theme.button.danger.backgroundPress } , }, }} { ...rest } > { children } </ Button > ); } ; const DangerButtonLabel = ( { children , style } : ButtonLabelComponentProps ) => { return < Button . Label style = { [ styles. label , style ] } > { children } </ Button . Label > ; } ; export const DangerButton = Object . assign (DangerButtonRoot, { Label : DangerButtonLabel, } ); const styles = StyleSheet .create(( theme ) => ( { root : { backgroundColor : theme.button.danger.background, } , label : { color : theme.button.danger.color, } , } )); ãã®ããã«ãHeroUI Nativeã® className ãªã©ãé èœãããããã¯ãåºæã®ã¹ã¿ã€ã«å®çŸ©ãreact-native-unistylesã§äžããŠãããHeroUI Nativeãšreact-native-unistylesã§è²¬åãåå²ããŠããŸãã ãŸãšã æ¬èšäºã§ã¯ãFindy Eventsã®éçºã§åœåæ¡çšããŠããTamaguiããHeroUI Native + react-native-unistylesãžä¹ãæãããŸã§ã®å€æãšãWrapper Componentã軞ã«ããUIå®è£
ãã¿ãŒã³ã玹ä»ããŸããã æ¯ãè¿ã£ãŠæ¹ããŠæããã®ã¯ãUIã©ã€ãã©ãªã®éžå®ã¯ãäžåºŠæ±ºããŠçµãããã§ã¯ãªããéçšããªããç¶ç¶çã«èŠçŽãåæã§åãåãã¹ãã ãšããããšã§ãã Tamaguiã¯éžå®æã®è©äŸ¡ãšããŠã¯åŠ¥åœãªåè£ã§ãæ¡çšåŸãå€ãã®å Žé¢ã§ååã«æ©èœããŠããŸãããäžæ¹ã§ãAndroid宿©ç¹æã®æåãExpo SDK远åŸãŸã§ã®ã¿ã€ã ã©ã°ãªã©ããããã¯ããšããŠé·ãè²ãŠãäžã§ç¡èŠã§ããªã課é¡ãèŠããŠããŸããβçãšããã¿ã€ãã³ã°ã掻ãããŠHeroUI Native + react-native-unistylesã«èžã¿èŸŒãã 倿ã¯ãéçºäœéšã®æ¹åãšãã圢ã§çŽ çŽã«å¹ããŠããŸãã å®è£
é¢ã§ã¯ãHeroUI Nativeããã®ãŸãŸUI ComponentãšããŠäœ¿ãã®ã§ã¯ãªããWrapper Componentçµç±ã§åã»Propsãçµããã¹ã¿ã€ã«å®çŸ©ã¯react-native-unistylesã«å¯ãã責ååé¢ãæ©èœããŸããããããã¯ãåºæã®ããŒããé
è²ãWrapper ComponentåŽã§åãæ¢ããããšã§ãUIã©ã€ãã©ãªæŽæ°ã®åœ±é¿ç¯å²ãæå°åãã€ã€ãç»é¢åŽã®ã³ãŒããã·ã³ãã«ã«ä¿ãŠãŠããŸãã Findyåã®ã¢ãã€ã«ã¢ããªãšããããšã§ãæè¡éžå®ãã¢ãŒããã¯ãã£ãææ¢ãã§é²ããéšåãå€ããããŸããããã®éœåºŠã®å€æãšæ¯ãè¿ããã®ãã®ã倧ããªè³ç£ã«ãªã£ãŠãããšæããŸããæ¬èšäºã®çµéšè«ããåããããªéžå®ãèšèšã«åãåã£ãŠããæ¹ã®å€æææã«ãªãã°å¬ããã§ãã ãã¡ã€ã³ãã£ã§ã¯äžç·ã«åãã¡ã³ããŒãåéããŠããŸããå°ãã§ãèå³ãæã£ãŠããã ããæ¹ã¯ããã²ãã¡ããã芧ãã ããïŒ herp.careers
â»æ¬èšäºã¯ Claude Code ãšã®ååã§å·çãã人éãã¬ãã¥ãŒã®äžæçš¿ããŠããŸãã 1. ã¯ããã« ããã«ã¡ã¯ãå
±éãµãŒãã¹éçºã°ã«ãŒãã®é³¥å±
( @yu_torii )ã§ãã ååã®èšäºã§ã¯ãSlack äžã§ LLM ãæŽ»çšãã瀟å
ãã£ãããããã®å®è£
äºäŸã玹ä»ããŸããã @ card ä»åã¯ããã®ããã¯ããã°ã®ãé¢é£ããèšäºããšãé¢é£ããæ±äººãæ©èœããŒãããåæ§ç¯ãã話ãããŸãã ãé¢é£ããèšäºããé¢é£ããæ±äººããšã¯ åèšäºããŒãžã®äžéšã«ã2ã€ã®ã¬ã³ã¡ã³ãã»ã¯ã·ã§ã³ããããŸãã é¢é£ããèšäº: çŸåšèªãã§ããèšäºãšå
容ãè¿ãèšäºãæå€§12件衚瀺 é¢é£ããæ±äºº: èšäºã®æè¡é åã«é¢é£ãã KINTO Technologies ã®æ±äººæ
å ±ãæå€§8件衚瀺 èªè
ãèå³ã®ããæè¡é åãæ·±æãããå°ç·ã§ãããéå»ã®èšäºã®çºèŠã«ãã€ãªãããŸããæ¡çšãžã®æ¥ç¹ã§ããããŸãã ä»çµã¿ã®åºæ¬ïŒEmbedding ãšã³ãµã€ã³é¡äŒŒåºŠ ãã®æ©èœã®æ žã¯ Embedding ïŒåã蟌ã¿ãã¯ãã«ïŒã§ããEmbedding ã¢ãã«ã«ããã¹ããå
¥åãããšããã®æå³ãè¡šãæ°çŸãæ°å次å
ã®æ°å€ãã¯ãã«ãè¿ã£ãŠããŸããæå³çã«è¿ãããã¹ãå士ã¯ããã¯ãã«ç©ºéäžã§è¿ãäœçœ®ã«é
眮ãããŸãã 2 ã€ã®ãã¯ãã«ã®ãè¿ãããæž¬ãææšã ã³ãµã€ã³é¡äŒŒåºŠ ã§ããå€ã 1 ã«è¿ãã»ã©æå³ãè¿ãã0 ã«è¿ãã»ã©ç¡é¢ä¿ïŒçŽäº€ïŒã§ãããã¹ãŠã®èšäºã Embedding ãããã¢ããšã«ã³ãµã€ã³é¡äŒŒåºŠãèšç®ããŠã¹ã³ã¢ã®é«ãé ã«äžŠã¹ãã°ããé¢é£ããèšäºãã®ã©ã³ãã³ã°ãåŸãããŸãã æ§ã·ã¹ãã ã®èª²é¡ ãã®æ©èœã¯ä»¥åãPython + Azure OpenAI ã® Embedding API ã§å®è£
ãããŠããŸãããéçšãç¶ããäžã§ 3 ã€ã®åé¡ãåºãŠããŸããã å·®åæŽæ°ãç¡ããæ¯åå
šèšäºãå Embed CI ãèµ°ããã³ã«å
šèšäºïŒåœæ 900 ä»¶è¶
ïŒã Azure OpenAI ã«éã£ãŠ Embedding ããŠããŸããã1 èšäºã®è¿œå ã§ãå
šä»¶ååŠçãèµ°ãããã«ãæéã®å€§åãå ããŠããŸããã Azure OpenAI ã® 429 (Rate Limit) ãšã©ãŒãé »çº 900 ä»¶è¶
ã®èšäºãäžæ°ã«éããšãAzure OpenAI ã®ã¬ãŒãå¶éã«é »ç¹ã«ãããããŠããŸããããªãã©ã€ããžãã¯ãå
¥ããŠãã¿ã€ãã³ã°æ¬¡ç¬¬ã§ CI ã倱æããåå®è¡ãå¿
èŠã«ãªãããšãçãããããŸããã§ããã å€éš API äŸå = ã³ã¹ãå¢å Embedding API ã®åŒã³åºãåæ°ããã«ãã®ãã³ã«ç©ã¿äžãããã³ã¹ããå¢ãç¶ããŠããŸãããèšäºæ°ãå¢ããã»ã©ç¶æ³ã¯æªåããæ§é ã§ãã ä»åãã£ãããš ãããã®åé¡ã解決ãããããGo + OllamaïŒããŒã«ã« EmbeddingïŒã§ã·ã¹ãã ãäžããåæ§ç¯ããŸããã SHA-256 ããã·ã¥ã§å€æŽèšäºã ãå Embed ããå·®åæŽæ°ãšãOllama ã«ãã CI ã©ã³ããŒäžã§ã®ããŒã«ã«å®è¡ïŒå€éš API åŒã³åºããŒãïŒã§ãæ§ã·ã¹ãã ã® 3 ã€ã®èª²é¡ãè§£æ¶ããŸããã PoC ã§ã®ã¢ãã«éžå®ããããã©ãŒãã³ã¹æé©åãCI/CD ãã€ãã©ã€ã³ã®æ§ç¯ãŸã§ãå®è£
ã®å
šäœåãæžããŸããéçºã«ã¯ Claude Code ã䜿ããŸããïŒããŸãã§è§ŠããŸãïŒã ãã®èšäºã§åŸãããããš Go + Ollama + Qwen3-Embedding ã§ããŒã«ã« Embedding ã«ããé¡äŒŒåºŠèšç®ãçµãæ¹æ³ Ollama num_ctx ã®ãµã€ã¬ã³ããã©ã³ã±ãŒã·ã§ã³ïŒç¡èŠåã®æååãè©°ãïŒåé¡ äºåæ£èŠåãš min-heap Top-K ã«ããã³ãµã€ã³é¡äŒŒåºŠã©ã³ãã³ã°ã®å¹çå SHA-256 å·®åãã£ãã·ã¥ã§å€æŽèšäºã ãå Embed ããä»çµã¿ :::message ãã®èšäºã®å
容ã¯å·çæç¹ïŒ2026幎4æïŒã®å®è£
ã«åºã¥ããŠããŸããOllama ã Qwen3-Embedding ã®ããŒãžã§ã³ã¢ããã«ãããAPI ã®ä»æ§ãããã©ãŒãã³ã¹ç¹æ§ãå€ããå¯èœæ§ããããŸãããŸããèšäºäžã®ãã³ãããŒã¯å€ã¯ GitHub Actions ã©ã³ããŒã§ã®èšæž¬çµæã§ãããç°å¢ã«ãã£ãŠç°ãªããŸãã ::: 2. PoC æ€èšŒãšã¢ãã«éžå® æ§ã·ã¹ãã ã®èª²é¡ïŒã»ã¯ã·ã§ã³ 1 ã§è¿°ã¹ã 429 ãšã©ãŒã»å
šéå®è¡ã»ã³ã¹ãå¢å ïŒã解決ãããããããŒã«ã« Embedding ãžã®ç§»è¡ã決ããŸãããGo ã§äœ¿ãã Embedding ã©ã€ãã©ãªã 3 ã€ã®æ¹åŒã§ PoC æ€èšŒããŸããã 3 ã€ã® PoC ã¢ãããŒã æ¹åŒ 1: hugotïŒPure Go ONNX ã©ã³ã¿ã€ã ïŒ knights-analytics/hugot 㯠Go ãã€ãã£ãã® ONNX ã©ã³ã¿ã€ã ã§ãbge-m3 ã Qwen3 ã® ONNX ã¢ãã«ãçŽæ¥å®è¡ã§ããŸããcgo äžèŠã§ãããONNX ã¢ãã«ãã¡ã€ã«ã®ãµã€ãºã巚倧ïŒbge-m3 ã§çŽ 2.2GBïŒã§ãCI ç°å¢ã§ã®ããŠã³ããŒããšã¡ã¢ãªç®¡çã«èª²é¡ããããŸããã æ¹åŒ 2: kelindar/searchïŒllama.cpp via puregoïŒ kelindar/search ã¯äžèŠ Pure Go ã«èŠããŸãããå
éšã§ã¯ purego çµç±ã§ llama.cpp ã®ãã€ããªãåŒã³åºããŠããŸããcgo ã¯äœ¿ã£ãŠããŸããããå®è³ªçã« llama.cpp ãã€ããªãžã®å€éšäŸåããããŸããããcgo äžèŠãã®è¡šé¢çãªç¹åŸŽã«æããããããæ¡ä»¶ã§ãã æ¹åŒ 3: Ollama APIïŒHTTP ã¯ã©ã€ã¢ã³ãïŒ éžãã ã®ã¯ Ollama ã® HTTP API ã Go ã¯ã©ã€ã¢ã³ãããåŒã¶æ¹åŒã§ãã client, err := api.ClientFromEnvironment() if err != nil { slog.Error("Ollama ã¯ã©ã€ã¢ã³ãäœæå€±æ", "error", err) os.Exit(1) } resp, err := client.Embed(ctx, &api.EmbedRequest{ Model: model, Input: testTexts, }) æ¯èŒè¡š æ¹åŒ cgo ã¢ãã«ç®¡ç ãããå¯Ÿå¿ ã³ã³ããã¹ãå¶åŸ¡ å€å® hugot (ONNX) äžèŠ æå â à Ⳡã¢ãã«ãµã€ãºåé¡ kelindar (llama.cpp) purego çµç±ã§äžèŠã«èŠããã llama.cpp ãã€ããªäŸå æå à à à å®è³ªå€éšäŸå Ollama API äžèŠ èªå â â ( num_ctx ) â éžå®ã®æ±ºãæ cgo äžèŠã§ GOOS=linux GOARCH=arm64 go build äžçºã®ã¯ãã¹ã³ã³ãã€ã«ãå£ããªããOllama ãã¢ãã«ã®ããŠã³ããŒãããã©ã€ããµã€ã¯ã«ç®¡çãŸã§æ
ããããã Embed API ã§è€æ°ããã¹ããäžåºŠã«éä¿¡ã§ããã num_ctx ã§ã³ã³ããã¹ããŠã£ã³ããŠãæç€ºå¶åŸ¡ã§ããã ãªã Qwen3-Embedding-0.6B ã Qwen3-Embedding-0.6B ãéžãã çç±ã¯ã2025 幎ãªãªãŒã¹ã®ææ°ã¢ãã«ã§ãéåååŸ 639MB ãš CI ã©ã³ããŒã®ã¡ã¢ãªã«åãŸããµã€ãºã ã£ãããšã1024 次å
ãã¯ãã«ã§è¡šçŸåãšèšç®éã®ãã©ã³ã¹ãè¯ããæ¥æ¬èªã»è±èªã®ãã€ãªã³ã¬ã«ãµããŒãã¯ãåœããã°ã®éçšäžã®å¿
é èŠä»¶ã§ãããRAG ã®æ€çŽ¢ç²ŸåºŠãæ±ããããã¿ã¹ã¯ã§ã¯ãªãé¢é£èšäºã®æšèŠçšéãªã®ã§ãæé«ç²ŸåºŠã¢ãã«ã¯äžèŠã§ãã :::details éååãšã¯ éååïŒQuantizationïŒã¯ãã¢ãã«ã®éã¿ïŒãã©ã¡ãŒã¿ïŒãå
ã®ç²ŸåºŠïŒéåžž float16 = 16bitïŒããããå°ãªããããæ°ïŒ8bitã4bit ãªã©ïŒã«å€æããææ³ã§ãã粟床ã¯ãããã«äœäžããŸãããã¢ãã«ãµã€ãºãšã¡ã¢ãªäœ¿çšéã倧å¹
ã«åæžã§ããŸãã Qwen3-Embedding-0.6B 㯠Ollama ã§ Q8_0ïŒ8bit éååïŒ ãšããŠé
åžãããŠããã595M ãã©ã¡ãŒã¿ã§ 639MBãäžæ¹ãbge-m3 㯠F16ïŒ16bitïŒé
åžã®ããããã©ã¡ãŒã¿æ°ã¯ã»ãŒåãïŒ568MïŒã§ããµã€ãºã 1.2GB ãšçŽ 2 åã«ãªããŸãã ::: :::message PoC ã®æ®µéã§ã¯ bge-m3 ãåè£ã§ããããã¢ãã«ãµã€ãºã ãã§ãªããã³ãããŒã¯ã§ã Qwen3 ãåªäœã§ããã MTEB ãã³ãããŒã¯ ã®è±èªæ€çŽ¢ïŒ61.82 vs 57.03ïŒãå€èšèªæ€çŽ¢ïŒ64.64 vs 58.36ïŒãã³ãŒãæ€çŽ¢ïŒ75.41 vs 41.38ïŒã§ Qwen3-Embedding-0.6B ãäžåã£ãŠããŸããbge-m3 ãåªäœãªã®ã¯é·ææ€çŽ¢ïŒMLDR: 59.51 vs 50.26ïŒã§ãããå
é 4000 æåã«åãè©°ããæ¬ã·ã¹ãã ã§ã¯è©²åœããŸãããOllama ã§ã®ã¢ãã«ãµã€ãºãçŽååïŒ639MB vs 1.2GBïŒã§ãCI ãã£ãã·ã¥ã®å¹çãå«ããŠç·åçã« Qwen3 ãéžæããŸããã ::: 3. ã¢ãŒããã¯ãã£ã®å
šäœå ãã€ãã©ã€ã³ flowchart LR A["_posts/*.md"] --> B["Markdown<br>ã¯ãªãŒãã³ã°"] B --> C["Ollama Embed API<br>(Qwen3-Embedding)"] C --> D["SHA-256<br>ãã£ãã·ã¥"] D --> E["ã³ãµã€ã³é¡äŒŒåºŠ<br>ã©ã³ãã³ã°"] E --> F["related_posts.json"] Markdown ãã¯ãªãŒãã³ã°ã㊠Ollama ã§ Embedding ãååŸããã³ãµã€ã³é¡äŒŒåºŠã§ã©ã³ãã³ã°ã㊠JSON ãåºåããŸãã ããã±ãŒãžæ§æ cmd/related-content-gen/ âââ main.go # CLI ãšã³ããªãã€ã³ã âââ internal/ â âââ markdown/ # Markdown ããŒã¹ã»ã¯ãªãŒãã³ã° â â âââ cleaner.go # frontmatter é€å»ãURL/assets é€å» â â âââ parser.go # _posts/*.md ã®èªã¿èŸŒã¿ â âââ embedding/ # Ollama ã¯ã©ã€ã¢ã³ãã»ãã£ãã·ã¥ â â âââ client.go # Embed API ã©ãããŒïŒnum_ctx å¶åŸ¡ïŒ â â âââ cache.go # SHA-256 ããã·ã¥ããŒã¹ã®å·®åæŽæ° â âââ similarity/ # é¡äŒŒåºŠèšç®ã»ã©ã³ãã³ã° â â âââ cosine.go # ã³ãµã€ã³é¡äŒŒåºŠïŒãã¹ãçšïŒ â â âââ ranking.go # L2æ£èŠå + dotProductãmin-heap Top-K â âââ output/ # JSON åºå â âââ json.go # UTF-8ã4ã¹ããŒã¹ã€ã³ãã³ããHTMLãšã¹ã±ãŒããªã âââ go.mod internal ããã±ãŒãžã«åé¢ããããšã§ãåããã±ãŒãžãåäžè²¬ä»»ãæã¡ãç¬ç«ããŠãã¹ãå¯èœã«ãªã£ãŠããŸãã run() 颿°ã®ãã€ãã©ã€ã³ ã¡ã€ã³åŠç㯠run() 颿°ã«éçŽãããŠããŸãã func run(...) error { // 1. èšäºã®èªã¿èŸŒã¿ãšã¯ãªãŒãã³ã° posts, err := markdown.ParsePosts(postsDir) // 2. Ollama ã¯ã©ã€ã¢ã³ãäœæ client, err := embedding.NewClient(ollamaURL, model, numCtx) // 3. ãã£ãã·ã¥èªã¿èŸŒã¿ â äžèŠãšã³ããªåé€ â 倿Žèšäºæ€åº cache, err := embedding.LoadCache(cacheFile) cache.Prune(posts) dirty := cache.FindDirty(posts, model) // 4. 倿Žåã®ã¿ EmbedïŒ1ä»¶ãã€åŠçããŠéœåºŠãã£ãã·ã¥ä¿åïŒ for _, p := range dirty { vectors, err := client.Embed(ctx, []string{text}) cache.Entries[p.Slug] = embedding.CacheEntry{...} cache.Save(cacheFile) // äžæèæ§ã®ããæ¯åä¿å } // 5. ã³ãµã€ã³é¡äŒŒåºŠã§ã©ã³ãã³ã° rankings := similarity.RankRelatedPosts(postVectors, 12) // 6. JSON åºå output.WriteJSON(outPath, postsOutput) } Next.js ããã³ããšã³ããšã®é£æº åºåããã JSON 㯠Next.js ã® getStaticProps ã§ãã«ãæã«èªã¿èŸŒãŸããŸãã static/related_posts/related_posts.json â lib/related_posts.ts ãèªã¿èŸŒã¿ ããã³ããšã³ãåŽã§ã¯ãJSON ã«é¢é£èšäºããŒã¿ãããã°ããã䜿ããç¡ããã°ã«ããŽãªããŒã¹ã®ãã©ãŒã«ããã¯ã«åãæ¿ãããŸããGo CLI ãšããã³ããšã³ãã®éã®å¥çŽã¯ããã® JSON ã¹ããŒãã ãã§ãã 4. Markdown ã®ã¯ãªãŒãã³ã°ãšååŠç åœããã°ã®èšäºã¯ Zenn Markdown ïŒ :::message ã :::details ã @[card]() ãªã©ïŒã§æžãããŠããŸããåèšäºãã¡ã€ã«ã®å
é ã«ã¯ YAML frontmatterïŒã¿ã€ãã«ãèè
ãå
¬éæ¥ãã«ããŽãªãªã©ã®ã¡ã¿æ
å ±ïŒãããããããããã®ãŸãŸ Embed ãããšãã€ãºã«ãªããŸãã ã¯ãªãŒãã³ã°ãã€ãã©ã€ã³ frontmatter ã®åé¢: --- ã§å²ãŸãã YAML ããããŒããã¿ã€ãã«ã ãæœåºããæ®ãã®ã¡ã¿æ
å ±ïŒauthor, date, category çïŒã¯é€å» URL ã®é€å»: http:// / https:// ã§å§ãŸããã¹ãŠã® URL ãé€å» ã¢ã»ãããªã³ã¯ã®é€å»: /assets/ ãå«ããªã³ã¯ïŒç»åãã¹ãªã©ïŒãé€å» ã¯ãªãŒãã³ã°ã®ãšã³ããªãã€ã³ã㯠CleanMarkdown 颿°ã§ãfrontmatter ããã¿ã€ãã«ãæœåºãã€ã€ãæ¬æã®ãã€ãºãé€å»ããŸããfrontmatter ããŒã¹ã«ã¯ strings.Cut ã䜿ãã --- ããªãã¿éã® YAML ã gopkg.in/yaml.v3 ã§è§£æããŠããŸãã :::details ã³ãŒãã®è©³çްïŒcleaner.go / parser.goïŒ var ( reURL = regexp.MustCompile(`https?://[^\s)\]>]+`) reAsset = regexp.MustCompile(`!?\[[^\]]*\]\(/assets/[^)]+\)|/assets/[^\s)]+`) ) func CleanMarkdown(raw []byte) (title, content string) { s := string(raw) if len(s) == 0 { return "", "" } title, body := splitFrontmatter(s) body = removeURLs(body) body = removeAssetLinks(body) return title, body } func splitFrontmatter(s string) (title, body string) { const delimiter = "---" _, after, ok := strings.Cut(s, delimiter) if !ok { return "", s } before, after, ok := strings.Cut(after, delimiter) if !ok { return "", s } var fm frontmatter if err := yaml.Unmarshal([]byte(before), &fm); err == nil { title = fm.Title } return title, after } type Post struct { Slug string // ãã¡ã€ã«åãã .md ãé€å» Title string // frontmatter ã® title ãã£ãŒã«ã Content string // ã¯ãªãŒãã³ã°æžã¿æ¬æ } func ParsePosts(dir string) ([]Post, error) { entries, err := os.ReadDir(dir) // ... *.md ãã¡ã€ã«ãèªã¿èŸŒã¿ãCleanMarkdown ã§åŠç return posts, nil } ::: ãã€ã³ãã¯ãEmbedding æã«ã¿ã€ãã«ãããã¹ãã®å
é ã«çµåããããšïŒ title + "\n" + content ïŒãã»ã¯ã·ã§ã³ 5.1 ã§è¿°ã¹ãŸãããEmbedding ã¢ãã«ã¯ããã¹ãã®å
é éšåãéèŠããåŸåããããããã¿ã€ãã«ã®æ
å ±ããã¯ãã«ã«åŒ·ãåæ ãããŸãã 5. Embedding ã®æé©å Embedding åŠçã®é«éåã§ 2 ã€ã®å·¥å€«ãããŸããã 5.1 : ããã¹ããå
é 4,000 æåã«åãè©°ããŠåŠçæéãçŽ 1/8 ã«ççž® 5.2 : å®è£
äžã«èžãã Ollama num_ctx ã®ç¡èŠååãè©°ãåé¡ 5.1 ããã¹ãåãè©°ãã®æé©å æåã¯èšäºã®å
šæããã®ãŸãŸ Ollama ã«éã£ãŠããŸãããCI ã§å®è¡ãããšãå
šèšäºã® Embedding ã«æ°åæéãããèšç®ã§ããå
šæãæ¬åœã«å¿
èŠãªã®ããæ€èšŒããŸããã ãŸããå
šèšäºã®ã¯ãªãŒãã³ã°æžã¿ããã¹ãé·ã®ååžã調ã¹ãŸããã å¹³å: çŽ 8,000 æå äžå€®å€: çŽ 6,300 æå äžäœ 10%: 14,600 æåä»¥äž æå€§: 53,000 æåè¶
倧åã®èšäºã¯ 10,000 æå以å
ã«åãŸããŸãããäžéšã®é·æèšäºã¯ 40,000 æåãè¶
ããŸããé·ãèšäºã®åŸåã«ã¯åèæç®ãªã¹ããè£è¶³æ
å ±ãå€ããèšäºã®ããŒãã衚ãæ
å ±ã¯å
é ã«éäžããåŸåããããŸããã ããã§ãå
é N æåã«åãè©°ããŠãå質ãç¶æã§ãããïŒããæ€èšŒãããããé·æã®äžäœ 5 èšäºã§ å
šæ EmbeddingïŒ num_ctx=8192 æç€ºæå®ïŒãããŒã¹ã©ã€ã³ ãšããŠãåãè©°ãæåæ°ãå€ããŠé¡äŒŒåºŠãšéåºŠãæ¯èŒããŸããã åãè©°ã ããŒã¹ã©ã€ã³ãšã®é¡äŒŒåºŠ å¹³åé床 é«éå å
šæ 1.000 229 ç§ 1.0x 2,000 æå 0.868 13 ç§ 17.6x 4,000 æå 0.887 29 ç§ 7.9x 6,000 æå 0.902 42 ç§ 5.5x 8,000 æå 0.909 53 ç§ 4.3x 4,000 â 8,000 æåã«å¢ãããŠãé¡äŒŒåºŠã®æ¹å㯠+2.2 ãã€ã³ã ïŒ0.887 â 0.909ïŒã«çãŸããŸãããé床㯠1.8 åé
ããªããŸããé¢é£èšäºã®ã©ã³ãã³ã°å質ã«åœ±é¿ãåºãªãããšãæ¬çªããŒã¿ã§ç¢ºèªããäžã§ã å
é 4,000 æå + num_ctx=8192 ãæ¡çšããŸããã :::message ãªãå
é ã®åãè©°ããæå¹ãïŒ 2 ã€ã®èŠå ãçžä¹ããŠããŸãã ã¢ãã«ã®äœçœ®ãã€ã¢ã¹ : Transformer ããŒã¹ã® Embedding ã¢ãã«ã§ã¯ãããã¹ãå
é ãžã®æ¹ä¹±ããã¯ãã«ã«äžãã圱é¿ãæ«å°ŸããçŽ 15% 倧ããããšãå ±åãããŠããŸãïŒ arXiv:2412.15241 ïŒãQwen3-Embedding ã RoPE ãæ¡çšãã Transformer ã¢ãã«ã§ãããåæ§ã®åŸåããããšèããããŸãã ã³ã³ãã³ãã®æ§é ãã€ã¢ã¹ : æè¡ããã°ã¯ãã¿ã€ãã«âå°å
¥âæŠèŠâ詳现ãã®éãã©ãããæ§é ãæã¡ãããŒãæ
å ±ãåé ã«éäžããŸãïŒãããã Lead Bias ïŒã ::: 5.2 Ollama ã® num_ctx ã«æœãèœãšã穎 5.1 ã®æ€èšŒã«å
¥ãåã«ã num_ctx åšãã§çœ ãèžã¿ãŸãããOllama ã§ Embedding ãæ±ã人ã¯å
šå¡åŒã£ãããããåé¡ã§ãã äœãèµ·ããã åãè©°ããæ€èšŒããåã«ããŸã num_ctx ã®å¹æã確èªããããšæ¬¡ã® 2 ãã¿ãŒã³ã§å
šæ Embedding ãæ¯èŒããŸããã A: å
šæ + num_ctx=4096 B: å
šæ + num_ctx=8192 A ãš B ã®ã³ãµã€ã³é¡äŒŒåºŠãå
šèšäºã§ 1.000 ã§ãããå®å
šã«åäžã®ãã¯ãã«ã§ããåŠçæéãå¹³åçŽ 70 ç§ã§å·®ããªãã35,000 æåè¶
ã®èšäºã§ã³ã³ããã¹ãé·ãåã«ããã®ã«ãçµæãå€ãã£ãŠããŸããã èšäº æåæ° å¹³ååŠçæé(ç§) A-B é¡äŒŒåºŠ torii-ai_tool_slack 35,417 68 1.000 Android-Compose-OO-Nav 37,803 76 1.000 aurora-mysql-stats 32,648 71 1.000 Jetpack-Compose-Anim 34,621 65 1.000 SecureDBPassword 38,978 69 1.000 å¹³å çŽ 70 ç§ 1.000 åå : Options ã«å
¥ããªããš num_ctx ã¯å¹ããªã num_ctx ã EmbedRequest.Options ã§ æç€ºçã«æž¡ããªãéã ãOllama 㯠VRAM ã«å¿ããããã©ã«ãå€ ïŒ24GiB æªæºã§ 4kã24-48GiB ã§ 32kã48GiB 以äžã§ 256kã OLLAMA_CONTEXT_LENGTH ç°å¢å€æ°ã§å€æŽå¯èœïŒã䜿ããè¶
éåãç¡èŠåã§åãè©°ããŸãã ãã¿ãŒã³ B ã§ num_ctx=8192 ãèšå®ããã€ããããAPI ã® Options ã«æž¡ãããŠããããA ãšåã 4096 ããŒã¯ã³ã§åŠçãããŠããŸãããé¡äŒŒåºŠ 1.000 ã¯ãäž¡æ¹ãšãåãå
¥åãåŠçããŠãã蚌æ ã§ãã :::message alert 泚æ: Ollama ã¯å
¥åããã¹ããã³ã³ããã¹ãé·ãè¶
ããŠããšã©ãŒãè¿ããŸãããAPI ã¬ã¹ãã³ã¹ã«ãåãè©°ãã®æç¡ã瀺ããã£ãŒã«ãããããŸãããæå³ããäžå®å
šãª Embedding ãçæãããå¯èœæ§ããããŸãããã㯠Ollama ã® Issue #14259 ã§ãå ±åãããŠããŸãã ::: ä¿®æ£ãšå¹æã®ç¢ºèª num_ctx ã EmbedRequest.Options ã§æç€ºçã«æž¡ãããä¿®æ£ããã®ããæ¬¡ã®å®è£
ã§ãã func (c *Client) Embed(ctx context.Context, texts []string) ([][]float32, error) { req := &api.EmbedRequest{ Model: c.model, Input: texts, } if c.numCtx > 0 { req.Options = map[string]any{"num_ctx": c.numCtx} } resp, err := c.api.Embed(ctx, req) if err != nil { return nil, fmt.Errorf("Ollama Embed API ãšã©ãŒ: %w", err) } // ã¬ã¹ãã³ã¹ã®ããªããŒã·ã§ã³ïŒä»¶æ°ã»ç©ºãã¯ãã«ãã§ãã¯ïŒ if len(resp.Embeddings) != len(texts) { return nil, fmt.Errorf("ã¬ã¹ãã³ã¹æ°ãäžäžèŽ: %d embeddings / %d texts", len(resp.Embeddings), len(texts)) } return resp.Embeddings, nil } ä¿®æ£åŸã¯ A-B é¡äŒŒåºŠã 0.947 ã«äžãããB ã®åŠçæé㯠A ã®çŽ 3 åïŒ229 ç§ vs 78 ç§ïŒã«ãªããŸããã8192 ããŒã¯ã³åãåŠçããŠããããšãæéãããè£ä»ããããŸãã èšäº æåæ° A(ç§) B(ç§) A-B é¡äŒŒåºŠ torii-ai_tool_slack 35,417 77 224 0.969 Android-Compose-OO-Nav 37,803 81 218 0.920 aurora-mysql-stats 32,648 84 224 0.919 Jetpack-Compose-Anim 34,621 74 243 0.947 SecureDBPassword 38,978 73 238 0.977 å¹³å 78 229 0.947 CLI ã®ããã©ã«ãå€ã¯ --num-ctx=8192 ã«èšå®ãã4000 æååãè©°ããšçµã¿åãããããšã§ç¡èŠåã®æååãè©°ããçºçããªãããšãä¿èšŒããŠããŸãã Ollama å©çšè
ãžã®æèš Ollama ã§ Embedding ã LLM ãæ±ããªãïŒ num_ctx 㯠Modelfile ã® PARAMETER ããAPI ã® Options.num_ctx ã§ æç€ºçã«èšå®ãã å
¥åã®ããŒã¯ã³æ°ãäºåã«ææ¡ããã³ã³ããã¹ãé·ã«åãŸãã確èªãã é¡äŒŒåºŠãå質ãããªããå€ãããªãããšãã¯ãç¡èŠååãè©°ããçã 5.3 ã³ãŒããããã¯ã¯æ®ãã¹ããïŒ å
é 4000 æåã®ãã¡ãã³ãŒããããã¯ã倧éã«å«ãŸããèšäºããããŸããAndroid Compose ã®ããã²ãŒã·ã§ã³èšäºã§ã¯ 2,213 æåïŒ55%è¶
ïŒãã³ãŒãã§ãããã³ãŒããé€å»ããŠæ¬æãå¢ããæ¹ãè¯ãããã«æããŸãã æ¥è±ç¿»èš³ãã¢ïŒåã postId ã§ locale ãç°ãªãèšäºïŒã®ã³ãµã€ã³é¡äŒŒåºŠã§æ€èšŒããŸããã ã³ãŒããããã¯ãã: 0.893 ã³ãŒããããã¯é€å»: 0.868 ã³ãŒããããã¯ãé€å»ãããšé¡äŒŒåºŠãäžãããŸããã ã¯ã©ã¹åã颿°åãã©ã€ãã©ãªåïŒ NavHost ã Composable ã goroutine ãªã©ïŒã¯èšèªã«äŸåããŸãããæ¥æ¬èªã®èšäºã§ãè±èªã®èšäºã§ããåãæè¡ãªãã³ãŒãäžã«åãããŒã¯ãŒããåºçŸããŸããã³ãŒããããã¯ã¯ã¯ãªãŒãã³ã°å¯Ÿè±¡ããé€å€ïŒæ®ãïŒãšããŸããã åãè©°ãã®å®è£
const maxEmbedRunes = 4000 for _, p := range dirty { text := p.Title + "\n" + p.Content if p.Content == "" { text = p.Title } if runes := []rune(text); len(runes) > maxEmbedRunes { text = string(runes[:maxEmbedRunes]) } vectors, err := client.Embed(ctx, []string{text}) // ... } []rune ã«å€æããŠããã¹ã©ã€ã¹ããããšã§ããã«ããã€ãæåïŒæ¥æ¬èªïŒã®éäžã§åããããšãé²ãã§ããŸãã 6. SHA-256 å·®åãã£ãã·ã¥ã«ããå¹çå ã»ã¯ã·ã§ã³ 1 ã§è¿°ã¹ããæ¯åå
šéå®è¡ãã®åé¡ã解決ãããããå·®åãã£ãã·ã¥ãå°å
¥ããŸããããååããäœãå€ãã£ããããé«éã«å€å®ããå¿
èŠããããŸããããã¡ã€ã«ã®æŽæ°æ¥æïŒmtimeïŒã¯ Git ã®ãã§ãã¯ã¢ãŠãã§ãªã»ããããããã CI ç°å¢ã§ã¯äœ¿ããŸãããããã§ãã³ã³ãã³ãèªäœã® SHA-256 ããã·ã¥ã§å€æŽãæ€ç¥ããæ¹åŒãæ¡çšããŸããã ãã£ãã·ã¥ã®èšèš type Cache struct { Version int `json:"version"` ModelName string `json:"model_name"` Entries map[string]CacheEntry `json:"entries"` } type CacheEntry struct { ContentHash string `json:"content_hash"` Vector []float32 `json:"vector"` } SHA-256 ã«ãã倿޿€ç¥ èšäºã®ã¿ã€ãã«ã𿬿ãçµåã㊠SHA-256 ããã·ã¥ãèšç®ããååã®ãã£ãã·ã¥ãšæ¯èŒããŸãã func ContentHash(title, content string) string { h := sha256.New() h.Write([]byte(title + "\n" + content)) return hex.EncodeToString(h.Sum(nil)) } func (c *Cache) FindDirty(posts []markdown.Post, modelName string) []markdown.Post { if c.ModelName != modelName { return posts // ã¢ãã«å€æŽ â å
šèšäºãåEmbed } var dirty []markdown.Post for _, p := range posts { entry, ok := c.Entries[p.Slug] if !ok || entry.ContentHash != ContentHash(p.Title, p.Content) { dirty = append(dirty, p) } } return dirty } ã¢ãã«åãå€ãããšå
šèšäºã dirty ã«ãªããŸããEmbedding ã¢ãã«ãå€ããã°æ¬¡å
æ°ããã¯ãã«ç©ºéãç°ãªããããå€ããã£ãã·ã¥ã¯ç¡å¹ã§ãã ãã£ãã·ã¥ãã㌠flowchart TB A["èšäºèªã¿èŸŒã¿<br>(956ä»¶)"] --> B["ãã£ãã·ã¥èªã¿èŸŒã¿"] B --> C{"ã¢ãã«å€æŽ?"} C -->|Yes| D["å
šèšäºãEmbed"] C -->|No| E["SHA-256æ¯èŒ"] E --> F{"倿Žãã?"} F -->|Yes| G["倿Žåã®ã¿Embed"] F -->|No| H["ã¹ããã"] D --> I["1ä»¶ãã€ä¿å<br>(äžæèæ§)"] G --> I Embed ã®ãã³ã«ãã£ãã·ã¥ãã¡ã€ã«ãä¿åããŸããCI ã®ã¿ã€ã ã¢ãŠããäžæãèµ·ããŠãããããŸã§åŠçããåã¯ãã£ãã·ã¥ã«æ®ããŸããæ¬¡åå®è¡æã¯äžæç®æããåéã§ãããããååã®å
šé Embedding ãè€æ°åã«åããŠé²ããããŸãã ååæ§ç¯ã§å¹ãããäžæèæ§ã ãã®ã1 èšäºããšã« cache ãã¡ã€ã«ãžä¿åããšããèšèšããååæ§ç¯ã§å®éã«åœ¹ã«ç«ã¡ãŸããã åœæ 956 ä»¶ãã£ãå
šèšäºã®ååå
šéãã«ãã§ã¯ãOllama ã§ã® Embedding åŠçã GitHub Actions ã® job timeoutïŒ timeout-minutes: 60 ïŒã«åãŸããã5 åé£ç¶ã§ 60 å timeout ã«å°éããŸãããããã§ã 6 åç®ã® run ã§å®èµ°ã§ããã®ã¯ãå cancelled run ã§å®äºããŠããåã® Embedding ãæ¬¡ã® run ã«åŒãç¶ãããããã§ãã run çµæ Generate related content 1 ã 5 åç® timeout å 60 å 6 åç® success 55 å çŽ¯èš çŽ 6 æé ãããæç«ãããã®ã¯ 2 ã€ã®åã¿åããã§ãã ã¢ããªåŽ : 1 èšäº Embed ããããšã« output/embeddings_cache.json ãžä¿å CI åŽ : actions/cache/save@v5 ã if: always() ã§èµ°ããã - name: Save embeddings cache if: always() # timeout/cancel æã cache save ãèµ°ããã uses: actions/cache/save@v5 with: path: output/embeddings_cache.json key: embeddings-cache-${{ hashFiles('_posts/**') }}-${{ github.run_id }} if: always() ãä»ããŠãããšãjob ã timeout/cancel ã§çµãããšãã«ã cache save ã¹ããããèµ°ããŸããçµæãéäžãŸã§åŠçãã Embedding 㯠cache ã«æ®ããæ¬¡ run 㯠restore-keys ã®ãã©ãŒã«ããã¯ã§å run ã® cache ãæŸã£ãŠæ®ãåããç¶è¡ã§ããã ãã®ä»çµã¿ããªããã°ã60 å timeout ã§æ¯å Embedding ãå·»ãæ»ãã6 æéã§å®èµ°ããããšã¯ãªãã£ãã¯ãã§ãã 7. ã³ãµã€ã³é¡äŒŒåºŠã©ã³ãã³ã°ã®æé©å Embedding ãã¯ãã«ãåŸãããããèšäºéã®é¡äŒŒåºŠãèšç®ããŠã©ã³ãã³ã°ãçæããŸãã956 èšäºã®åèšäºãä»ã® 955 ä»¶ãšæ¯èŒãããããçŽ 91 äžåã®å
ç©èšç®ãèµ°ããŸãããã®èŠæš¡ãªã FAISS çã® ANNïŒè¿äŒŒæè¿åæ¢çŽ¢ïŒã©ã€ãã©ãªãå°å
¥ããããããbrute-force ã®æ¹ãã·ã³ãã«ã§äŸåãå¢ããŸããã æåã®å®è£
ïŒæ¯åãã«ã èšç® + å
šä»¶ãœãŒãïŒã§ã¯ãã¹ãã§çŽ 1.6 ç§ããã£ãŠããŸãããäºåæ£èŠå + min-heap ãžã®å€æŽãšãã«ãŒãã¢ã³ããŒãªã³ã°ã® 2 段éã§ 730ms ãŸã§æ¹åããŸããã :::::details æé©åã®è©³çް 1. äºåæ£èŠå (Pre-normalization) ã³ãµã€ã³é¡äŒŒåºŠã®åŒã¯ä»¥äžã§ãã $$ \cos(a, b) = \frac{a \cdot b}{|a| \times |b|} $$ æ¯å 2 ã€ã®ãã¯ãã«ã®é·ãïŒãã«ã $|a|$ïŒãèšç®ããã®ã¯ç¡é§ãªã®ã§ãå
šãã¯ãã«ã®é·ããäºåã« 1 ã«æããŠãããŸãïŒæ£èŠåïŒããããšåæ¯ã $1 \times 1 = 1$ ã«ãªããã³ãµã€ã³é¡äŒŒåºŠã¯å
ç© $a \cdot b$ïŒåèŠçŽ ãæããŠè¶³ãã ãïŒãšçãããªããŸããæ£èŠåã¯èšäºæ°åïŒ956åïŒã ãããã®åŸã® 91 äžåã®ãã¢æ¯èŒã§ã¯æãç®ãšè¶³ãç®ã ãã§æžã¿ãŸãã :::details ã³ãµã€ã³é¡äŒŒåºŠã®è£è¶³ å
ç© $a \cdot b$ 㯠2 ã€ã®ãã¯ãã«ã®åèŠçŽ ãæããŠè¶³ããå€ã§ããæå³ãè¿ãèšäºå士ã¯å
ç©ã倧ãããªããŸãããé·ãèšäºã®ãã¯ãã«ã¯å€ã倧ãããªããã¡ã§ãå
ç©ã ãã ãšããã¯ãã«ã®é·ããã«åŒã£åŒµãããŸãããã«ã $|a|$ ã§å²ãããšã§é·ãã®åœ±é¿ãæ¶ããçŽç²ã«ãåããïŒæå³ã®è¿ãïŒã ããæ¯èŒããã®ãã³ãµã€ã³é¡äŒŒåºŠã§ããçµæã¯ $-1$ ã $1$ ã®ç¯å²ã§ã1 ã«è¿ãã»ã©æå³ãè¿ãã æ£èŠåãšã¯ãåèŠçŽ ããã«ã ã§å²ã£ãŠãã¯ãã«ã®é·ãã 1 ã«ããåŠçã§ããåãã¯ãã®ãŸãŸãé·ãã ãæããŸãã å
: a = [3, 4] â é·ã = â(9+16) = 5 æ£èŠå: a' = [0.6, 0.8] â é·ã = â(0.36+0.64) = 1 ::: 2. min-heap Top-K å
š 955 ä»¶ã®ã¹ã³ã¢ã sort.Slice ã§ãœãŒãããŠããŸããããå®éã«å¿
èŠãªã®ã¯äžäœ 12 ä»¶ã ãããµã€ãº 12 ã® min-heapïŒGo æšæºã©ã€ãã©ãªã® container/heap ïŒã䜿ããã¹ã³ã¢ãæå°å€ãã倧ãããã°å
¥ãæ¿ããæ¹åŒã«å€æŽãèšç®é㯠$O(N \log N)$ ãã $O(N \log K)$ ã«æ¹åããŸãã 3. ã«ãŒãã¢ã³ããŒãªã³ã° å
ç©èšç®ã®ããããã¹ïŒçŽ 91 äžå à 1024 次å
ïŒã« 4-way ã«ãŒãã¢ã³ããŒãªã³ã°ãé©çšã4 ã€ã®ç¬ç«ããã¢ãã¥ã ã¬ãŒã¿å€æ°ã䜿ãããšã§ãåã®ã«ãŒãçµæãžã®äŸåãæã¡åããCPU ãä¹ç®ãšå ç®ã䞊åå®è¡ã§ããããã«ãªããŸãã :::details ã«ãŒãã¢ã³ããŒãªã³ã°ã®è£è¶³ éåžžã®ã«ãŒãã§ã¯ 1 ã€ã®å€æ° sum ã«é çªã«è¶³ããŠãããŸãã sum += a[0]*b[0] ã®çµæãåºããŸã§æ¬¡ã® sum += a[1]*b[1] ãå§ããããŸããïŒããŒã¿äŸåïŒã 4-way ã§ã¯ 4 ã€ã®å€æ° s0, s1, s2, s3 ã«åããŠãããããç¬ç«ã«èšç®ããŸããCPU ã¯äŸåé¢ä¿ã®ãªãåœä»€ãåæã«å®è¡ã§ããããïŒåœä»€ã¬ãã«äžŠåæ§ïŒã4 ã€ã®ä¹ç®ã»å ç®ã䞊åã«èµ°ããŸããæåŸã« s0 + s1 + s2 + s3 ã§åèšããã ãã§ãã éåžž: sum += a[0]*b[0] â sum += a[1]*b[1] â sum += a[2]*b[2] â sum += a[3]*b[3] ïŒåã®çµæãåŸ
ã£ãŠããæ¬¡ãžïŒ 4-way: s0 += a[0]*b[0] s1 += a[1]*b[1] s2 += a[2]*b[2] s3 += a[3]*b[3] ïŒ4ã€åæã«å®è¡ïŒ â s0 + s1 + s2 + s3 ::: // äºåæ£èŠå: å
šãã¯ãã«ã®ãã«ã ã 1 ã«ãã normalized := normalizeAll(slugs, vectors) // min-heap Top-K: äžäœ maxResults ä»¶ã ããå¹ççã«æœåº h := &minHeap{} for j, other := range slugs { if i == j { continue } score := dotProduct(vi, normalized[j]) if h.Len() < maxResults { heap.Push(h, ScoredItem{Key: other, Score: score}) } else if score > (*h)[0].Score { (*h)[0] = ScoredItem{Key: other, Score: score} heap.Fix(h, 0) } } // 4-way ã«ãŒãã¢ã³ããŒãªã³ã° func dotProduct(a, b []float32) float32 { var s0, s1, s2, s3 float32 n := len(a) i := 0 for ; i <= n-4; i += 4 { s0 += a[i]*b[i]; s1 += a[i+1]*b[i+1] s2 += a[i+2]*b[i+2]; s3 += a[i+3]*b[i+3] } for ; i < n; i++ { s0 += a[i] * b[i] } return s0 + s1 + s2 + s3 } ::::: ããã©ãŒãã³ã¹æšç§» æ®µé ææ³ ã©ã³ãã³ã°åŠçæéïŒ956èšäºïŒ åæ æ¯åãã«ã èšç® + sort.Slice ~1.58s 1 äºåæ£èŠå + min-heap Top-K ~1.18s 2 + ã«ãŒãã¢ã³ããŒãªã³ã°ïŒ4-wayïŒ 730ms æçµçãªã¹ããã¯ïŒ ææš å€ èšäºæ° 956 ä»¶ ãã¯ãã«æ¬¡å
æ° 1024 é¡äŒŒåºŠèšç®åæ° çŽ 912,980 åïŒ956 à 955ïŒ ã©ã³ãã³ã°åŠçæé 730ms ãªããGo ã® map ã¯ã€ãã¬ãŒã·ã§ã³é åºãéæ±ºå®çã§ããåãå
¥åã«å¯ŸããŠåžžã«åã JSON åºåãåŸãããã slices.Sort ã§ã¹ã©ãã°ããœãŒãããŠããåŠçããŠããŸãããããå¿ãããš CI ã®ãã³ã« diff ãçºçããäžèŠãªã³ããããçãŸããŠããŸããŸãã 8. GitHub Actions ã§ã® CI/CD ã¯ãŒã¯ãããŒå
šäœå flowchart LR A["create-branch"] --> B["generate-related-content<br>(ARM runner + Ollama)"] A --> C["generate-metadata"] A --> D["generate-search-index"] B --> E["create-pull-request"] C --> E D --> E E --> F["auto-merge"] create-branch ã§ãã©ã³ããäœæããåŸã3 ã€ã®ãžã§ãã䞊åå®è¡ãããŸãã ARM ã©ã³ããŒã®éžæ Embedding åŠçã«ã¯ arm-ubuntu-latest-4 ã©ã³ããŒã䜿çšããŠããŸããGitHub ã® ARM ã©ã³ããŒã¯ x86 ã®çŽåé¡ïŒ1åããã $0.004 vs $0.008ïŒã§ãååã®å
šé Embedding ã®ããã«æ°æéããããžã§ãã§ã¯ã³ã¹ãå·®ã倧ãããªããŸãã Ollama ã¢ãã«ãã£ãã·ã¥ 639MB ã®ã¢ãã«ãã¡ã€ã«ãæ¯åããŠã³ããŒãããªãããã actions/cache ã§ãã£ãã·ã¥ããŸãã cache/restore + cache/save ãã¿ãŒã³ :::message actions/cache@v5 ã® save-always ãªãã·ã§ã³ã¯éæšå¥šã«ãªããŸããã代ããã« cache/restore ãš cache/save ãåé¢ãã cache/save ã« if: always() ãä»ãããã¿ãŒã³ã䜿ããŸãã ::: - name: Restore embeddings cache uses: actions/cache/restore@v5 with: path: output/embeddings_cache.json key: embeddings-cache-${{ hashFiles('_posts/**') }}-${{ github.run_id }} restore-keys: embeddings-cache- # ... Embedding å®è¡ ... - name: Save embeddings cache if: always() uses: actions/cache/save@v5 with: path: output/embeddings_cache.json key: embeddings-cache-${{ hashFiles('_posts/**') }}-${{ github.run_id }} if: always() ã«ãããã¿ã€ã ã¢ãŠãæã§ããã£ãã·ã¥ãä¿åããŸããã»ã¯ã·ã§ã³ 6 ã®ã1 ä»¶ãã€ä¿åããšçµã¿åãããŠãäžæãšåå®è¡ãç¹°ãè¿ããŠããã£ãã·ã¥ãèç©ãããŸãã ãã£ãã·ã¥ããŒã« run_id ãä»ããçç± GitHub Actions ã®ãã£ãã·ã¥ã¯åãããŒã§äžæžãã§ããŸããïŒã€ãã¥ãŒã¿ãã«ïŒãããã¯ã¿ã€ã ã¢ãŠãâåå®è¡ã®ãã¿ãŒã³ã§åé¡ã«ãªããŸãã run_id ãªãã®å Žå: key: embeddings-cache-abc123 1åç®: save "abc123" â â
200èšäºåä¿å 2åç®: restore "abc123" â 200èšäºåŸ©å
â 远å 200èšäº â save "abc123" â â ããŒãæ¢ã«ååš 3åç®: restore "abc123" â 1åç®ã®200èšäºåãããªãïŒ2åç®ã®ææãæ¶ããïŒ run_id ããã®å Žå: save key: embeddings-cache-abc123-{run_id} â æ¯åãŠããŒã¯ restore-keys: embeddings-cache-abc123- â ãã¬ãã£ãã¯ã¹äžèŽã§ææ°ãååŸ 1åç®: save "abc123-100" â â
200èšäºå 2åç®: restore "abc123-" â run100ãã200èšäºåŸ©å
â 远å 200èšäº â save "abc123-200" â â
400èšäºå 3åç®: restore "abc123-" â run200ãã400èšäºåŸ©å
â ç¶ããã push ã®ãªãã©ã€ããžã㯠3 ã€ã®ãžã§ãã䞊åã§ãã©ã³ãã« push ãããããç«¶åãçºçããŸããææ°ããã¯ãªãä»ãã®ãªãã©ã€ã§å¯ŸåŠããŸãã pushed=false for i in 1 2 3 4 5; do git pull --rebase origin "$BRANCH_NAME" && git push origin "$BRANCH_NAME" && pushed=true && break echo "Push failed (attempt $i), retrying..." sleep $((i * 2)) done [ "$pushed" = "true" ] || { echo "ERROR: All push attempts failed"; exit 1; } å€ããã©ã³ãã®åé¡ èªåçæçšãã©ã³ããååã®å®è¡ããæ®ã£ãŠããå Žåãå€ãã³ãŒããããŒã¹ã«ãªããŸãã git reset --hard ${{ github.sha }} ã§æ¯åããªã¬ãŒå
ã®ææ°ã³ãããã«ãªã»ããããŸãã workflow_dispatch ã§ã®ãã¹ãå®è¡ main ã«ããŒãžåã®åäœç¢ºèªã§ã¯ workflow_dispatch ããªã¬ãŒãäžæçã«è¿œå ããŸããããã ããGUI ã® Actions ã¿ãã«ã¯ããã©ã«ããã©ã³ãã®ã¯ãŒã¯ãããŒãã衚瀺ãããªããããfeature ãã©ã³ãã® workflow_dispatch 㯠GUI ããå®è¡ã§ããŸããã CLI çµç±ã§ããã° --ref ã§ãã©ã³ããæå®ããŠå®è¡å¯èœã§ãã gh workflow run "Auto Create Related Data" --ref feat/related-content-gen-go-rewrite 9. å®éçšã§èŠãã广 æ§ã·ã¹ãã ïŒPython + Azure OpenAIïŒããæ°ã·ã¹ãã ïŒGo + OllamaïŒãžã®ç§»è¡ã§ãã»ã¯ã·ã§ã³ 1 ã§æãã 3 ã€ã®èª²é¡ã¯ããããæ¬¡ã®ããã«å€ãããŸããã èª²é¡ æ§ïŒPython + Azure OpenAIïŒ æ°ïŒGo + OllamaïŒ å®è¡æŠç¥ æ¯åå
šé EmbedïŒ900+ ä»¶ïŒ å·®åã®ã¿ EmbedïŒSHA-256 ããã·ã¥æ¯èŒïŒ Rate Limit (429) é »çºã»ãªãã©ã€ã§äžå®å® æ§é çã«çºçããªãïŒå€éš API ãªãïŒ æšè«ã³ã¹ã åŸé課éïŒAzure OpenAIïŒ ãŒãïŒCI ã©ã³ããŒå
å®çµïŒ æ¯èŒãã¹ãã¯åçºã®åŠçç§æ°ã§ã¯ãªãããèšäºè¿œå ã®ãã³ã«å
šéåèšç®ãå¿
èŠãããå€éš API å¶çŽã«éçšãæ¯ãåããããããšããéçšç¹æ§ã§ããæ§ã¯ Azure ã®ãããŒãžãäžŠåæšè«ãæ°ã¯ self-hosted CI ã©ã³ã㌠1 å°ã®ã·ãŒã±ã³ã·ã£ã«åŠçã§ããããã尺床ãéããŸãã å·®åæŽæ°æã®å®æž¬äŸ 959 èšäºäž 49 ä»¶ïŒ5%ïŒã dirty ã ã£ã run ã§ã¯ã 19 å 21 ç§ã§å®èµ° ããŸããïŒself-hosted runner 1 å°ã»é次åŠçã§ 1 èšäºãããçŽ 22ã24 ç§ïŒãå·®åãŒããªã Embed ã¯ã¹ããããããã©ã³ãã³ã°èšç®ãšåºåã ãã§ 1ã2 åã§å®äºããŸãã æ®èª²é¡ dirty ã 150 ä»¶ãè¶
ããç¶æ³ïŒcache eviction çŽåŸã cron ãé·æé倱æããŠããããšãªã©ïŒã§ã¯ timeout-minutes: 60 ã«åãŸããªãããšããããŸããçŸç¶ã¯è€æ° run ã«åããŠé²æãç©ã¿äžããèšèšã§ã«ããŒããŠããŸãããæ¬¡ã®æã¡æãšã㊠timeout å»¶é·ãš output/embeddings_cache.json ã® git 管çåãåè£ã§ã GitHub Actions cache 㯠7 æ¥ã¢ã¯ã»ã¹ãªãã§èªå eviction ãããããã鱿¬¡ cronïŒææ 9 æïŒã§ Restore ãè§Šã£ãŠ keep-warm ããŠããŸãããã確å®ã«ãããªã git 管çåããS3 ãªã©ã®å€éš storage ã«å¯ããæããããŸã 10. ãŸãšã æ¬èšäºã§ã¯ãé¢é£èšäºã®ã¬ã³ã¡ã³ãã·ã¹ãã ã Go + OllamaïŒããŒã«ã« EmbeddingïŒã§åæ§ç¯ããéçšã玹ä»ããŸããããªããé¢é£æ±äººã«ã€ããŠãåæ§ã® Embedding + ã³ãµã€ã³é¡äŒŒåºŠã®ä»çµã¿ã§çæããŠããŸãã é
ç® çµæ 察象èšäºæ° 960 ä»¶ååŸïŒå·çæç¹ïŒ ã©ã³ãã³ã°èšç® 730msïŒEmbedding çæã¯å«ãŸããæž¬å®æç¹ 956 ä»¶ïŒ ããã¹ãåãè©°ã å
é 4000 æåã§å
𿿝 88.7% ã®é¡äŒŒåºŠãç¶æ å·®åãã£ãã·ã¥ å·®åãŒããªã 1ã2 åãå°æ°å·®åãªãæ°åãåæ°å å€éšäŸå Ollama + Qwen3-EmbeddingïŒAPI ããŒäžèŠïŒ SHA-256 å·®åãã£ãã·ã¥ã§å€æŽèšäºã ããå Embed ããã©ã³ãã³ã°ã¯äºåæ£èŠåãš min-heap Top-K ã§ 730msïŒ956èšäºã®ãã¢ã¯ã€ãºèšç®ïŒãå€éš API äŸåãæé€ããŠã429 ãšã©ãŒãšã³ã¹ãã®åé¡ãè§£æ¶ããŸããã ååã®å
šé Embedding 㯠CPU ã©ã³ããŒã§æ°æéããããã¢ãã«å€æŽãåæå°å
¥æã«ãåãã³ã¹ããæãããšã«ãªããŸããæ±ãæ¹ã¯ã»ã¯ã·ã§ã³ 6 ãš 9 ã«æžããéãã§ãGPU ã©ã³ããŒã䜿ããã°æ¹åããŸãããçŸæç¹ã§ã¯ CI ã®å¶çŽã§ãã ãã 1 ã€ãæšèŠå質ã®å®éè©äŸ¡ããŸã ãããŸããããEmbedding ã®é¡äŒŒåºŠã 88.7% ä¿ãããŠãããããšãšãé¢é£èšäºã®æšèŠã劥åœã§ãããããšã¯å¥ã®åé¡ã§ããæ§ã·ã¹ãã ãšã® Top-K äžèŽçããã¯ãªãã¯ã¹ã«ãŒçã®èšæž¬ãæ®ã£ãŠããŸãã ããã¹ãåãè©°ããæ¹åã®äœå°ããããŸããçŸåšã¯å
é 4000 æåãã«ãŒã³åäœã§ã«ããããŠããŸãããæã®éäžã§åããå¯èœæ§ããããŸããå¥ç¹ïŒ ã ïŒãæ¹è¡ã®äœçœ®ã§åãæ¹ããEmbedding ã®å
¥åãšããŠã¯ã¯ãªãŒã³ã§ããä»åã®ãŠãŒã¹ã±ãŒã¹ã§ã¯åœ±é¿ã¯è»œåŸ®ã§ããã粟床ã远æ±ããå Žåã¯æ€èšã«å€ããŸãã 11. ãã®ä»çµã¿ã®å¿çšå¯èœæ§ ãããŒã«ã« Embedding + ã³ãµã€ã³é¡äŒŒåºŠ + å·®åãã£ãã·ã¥ãã®ä»çµã¿ã¯ãããã°ã®é¢é£èšäºã«éããŸãããConfluence ã Notion ã®ç€Ÿå
ããã¥ã¡ã³ããåããã€ãã©ã€ã³ã§ Embedding ããã°ãããã®ä»æ§æžã«é¢é£ããããã¥ã¡ã³ãããèªåæç€ºã§ããŸããOllama ã¯ããŒã«ã«å®è¡ãªã®ã§ã瀟å€ã«éä¿¡ã§ããªã瀟å
ææžã§ãæ±ããŸãã SHA-256 å·®åãã£ãã·ã¥ãš 1 ä»¶ãã€ä¿åã®äžæèæ§ãã¿ãŒã³ã¯ãã®ãŸãŸæµçšã§ããŸããOllama + 軜éã¢ãã«ãªã API ããŒäžèŠã§ CI ã§ãããŒã«ã«ã§ãåããŸãã ããŸã: Claude Code ãšã®éçºããã»ã¹ ä»åã®éçºã¯ Claude Code ãšã®ãã¢ããã°ã©ãã³ã°ã§é²ããŸããã kairo ã«ããéçºã¯ãŒã¯ãã㌠éçºã¯ãŒã¯ãããŒã«ã¯ã¯ã©ã¹ã¡ãœãã瀟㮠tsumiki ã® kairo ã䜿ããŸãããkairo 㯠Claude Code åãã®ã¹ãã«ã§ã4 ã€ã®ã³ãã³ãã§ãœãããŠã§ã¢éçºãé²ããŸãã kairo-requirements : EARS èšæ³ã§æ©èœã»éæ©èœèŠä»¶ãå®çŸ©ãä»å㯠3 æ¹åŒã® PoC æ¯èŒïŒONNX / llama.cpp / OllamaïŒããã®ãã§ãŒãºã§å®è¡ããŸãã kairo-design : èŠä»¶ããã¢ãŒããã¯ãã£å³ãããŒã¿ãããŒãåå®çŸ©ãçæ kairo-tasks : èšèšãå®è£
ã¿ã¹ã¯ã«åå²ãäŸåé¢ä¿ãšãã¹ãã±ãŒã¹ãå®çŸ©ãä»å㯠10 ã¿ã¹ã¯ã»3 ãã§ãŒãºã«åè§£ kairo-loop : ã¿ã¹ã¯ã 1 ã€ã〠Red â Green â Refactor ã® TDD ãµã€ã¯ã«ã§å®è£
ã7 ã¿ã¹ã¯ããã®ã³ãã³ãã§åããŸãã PR ã¬ãã¥ãŒ å®è£
åŸã® PR ã¬ãã¥ãŒã§ã¯ãClaude Code ã«ä»¥äžã®ããã«æç€ºããŸããã /pr-review-toolkit:review-pr all pr-review-toolkit 㯠Anthropic å
¬åŒã® Claude Code ãã©ã°ã€ã³ã§ã6 çš®ã®ã¬ãã¥ãŒãšãŒãžã§ã³ãïŒã³ãŒãå質ããšã©ãŒãã³ããªã³ã°ããã¹ãã«ãã¬ããžãã³ã¡ã³ãæŽåæ§ãåèšèšãã³ãŒãç°¡çŽ åïŒã䞊åã«ã¬ãã¥ãŒããŸããã»ã¯ã·ã§ã³ 5.2 ã®ã¬ã¹ãã³ã¹ããªããŒã·ã§ã³ïŒä»¶æ°ã»ç©ºãã¯ãã«ãã§ãã¯ïŒã¯ããã®ã¬ãã¥ãŒã§ææãããåé¡ãžã®å¯Ÿå¿ã§ãã Go 1.26 ã§ã®æé©å Claude Code ã«ãGo 1.26 ã§æé©åããŠããšæç€ºããŸããã go fix ã«ããèªåå€æïŒ strings.Index â strings.Cut ã sort.Strings â slices.Sort ã context.Background() â t.Context() ãªã©ïŒã«å ããæ°ããèšèªæ©èœãã©ã€ãã©ãª API ãæŽ»çšãããªãã¡ã¯ã¿ãªã³ã°ã宿œãããŸããã èšäºã®å·çã»æ ¡æ£ ãã®èšäºèªäœã Claude Code ã§å·çããŠããŸããæ ¡æ£ã«ã¯ 3 ã€ã®ããŒã«ã䜿ããŸããã textlint + ja-technical-writing : åé·è¡šçŸãæ¥ç¶è©ã®éè€ãªã©ãæ¥æ¬èªã®æè¡ææžåãæ ¡æ£ skill-deslop : AI çææç« ã«ç¹æã®åé·ãã¿ãŒã³ïŒåããã©ãå眮ããååæ
ã®å€çšãªã©ïŒã®æ€åºã»é€å» Codex plugin for Claude Code : OpenAI å
¬åŒã® Claude Code ãã©ã°ã€ã³ã§ãCodex CLI ããµããšãŒãžã§ã³ããšããŠåŒã³åºããŸããèšäºå
šäœã®è«çç Žç¶»ãæ°å€ççŸã®ãã§ãã¯ã«äœ¿ããŸãããå®éšããŒã¿æŽæ°ã«äŒŽãæ°å€ã®äžæŽåãã³ãŒãã¹ããããã®å€æ°åäžäžèŽãªã©ã人éã®ã¬ãã¥ãŒã§ã¯èŠèœãšããããåé¡ãæ€åºã§ããŸãã ãããŸã§èªãã§ããã ãããããšãããããŸãããäœãã®åèã«ãªãã°å¹žãã§ãããªãããã®èšäºã®äžéšã«è¡šç€ºãããŠãããé¢é£ããèšäºããšãé¢é£ããæ±äººãããæ¬èšäºã§ç޹ä»ããä»çµã¿ã§çæãããå®ç©ã§ãã
ã¯ããã« ã¯ãããŸããŠãæ ªåŒäŒç€Ÿã¿ããã«ã§ãµãŒããŒãµã€ããšã³ãžãã¢ãããŠãã糞äºäžé¢¯( Issa ) ...





















