中文 | Anglais
Un pipeline pour créer un chat en ligne et hors ligne dans Unity.

Avec la sortie de Unity.Sentis , nous pouvons utiliser certains modèles de réseau neuronal à l'exécution, y compris le modèle d'intégration de texte pour le traitement du langage naturel.
Bien que discuter avec l'IA ne soit rien de nouveau, dans les jeux, comment concevoir une conversation qui ne s'écarte pas des idées du développeur mais qui est plus flexible est un point difficile.
UniChat est basé sur Unity.Sentis et la technologie d'intégration de vecteur de texte, qui permet au mode hors ligne de rechercher du contenu texte basé sur des bases de données vectorielles.
Bien sûr, si vous utilisez le mode en ligne, UniChat comprend également une boîte à outils en chaîne basée sur Langchain pour intégrer rapidement LLM et l'agent dans le jeu.
Ce qui suit est le tableau d'écoulement d'Unichat. Dans la boîte Local Inference , les fonctions peuvent être utilisées hors ligne:

manifest.json : {
"dependencies" : {
"com.cysharp.unitask" : " https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask " ,
"com.huggingface.sharp-transformers" : " https://github.com/huggingface/sharp-transformers.git " ,
"com.unity.addressables" : " 1.21.20 " ,
"com.unity.burst" : " 1.8.13 " ,
"com.unity.collections" : " 2.2.1 " ,
"com.unity.nuget.newtonsoft-json" : " 3.2.1 " ,
"com.unity.sentis" : " 1.3.0-pre.3 " ,
"com.whisper.unity" : " https://github.com/Macoron/whisper.unity.git?path=Packages/com.whisper.unity "
}
}Unity Package Manager en utilisant Git URL https://github.com/AkiKurisu/UniChat.git public void CreatePipelineCtrl ( )
{
//1. New chat model file (embedding database + text table + config)
ChatPipelineCtrl PipelineCtrl = new ( new ChatModelFile ( ) { fileName = $ "ChatModel_ { Guid . NewGuid ( ) . ToString ( ) [ 0 .. 6 ] } " } ) ;
//2. Load from filePath
PipelineCtrl = new ( JsonConvert . DeserializeObject < ChatModelFile > ( File . ReadAllText ( filePath ) ) )
} public bool RunPipeline ( )
{
string input = "Hello!" ;
var context = await PipelineCtrl . RunPipeline ( "Hello!" ) ;
if ( ( context . flag & ( 1 << 1 ) ) != 0 )
{
//Get pipeline output
string output = context . CastStringValue ( ) ;
//Update history
PipelineCtrl . History . AppendUserMessage ( input ) ;
PipelineCtrl . History . AppendBotMessage ( output ) ;
return true ;
}
} pubic void Save ( )
{
//PC save to {ApplicationPath}//UserData//{ModelName}
//Android save to {Application.persistentDataPath}//UserData//{ModelName}
PipelineCtrl . SaveModel ( ) ;
} Le modèle d'incorporation est utilisé BAAI/bge-small-zh-v1.5 par défaut et occupe le moins de mémoire vidéo. Il peut être téléchargé dans la version, mais ne prend en charge que le chinois. Vous pouvez télécharger le même modèle depuis HuggingFaceHub et le convertir au format ONNX.
Le mode de chargement est facultatif UserDataProvider , StreamingAssetsProvider et ResourcesProvider , si installé Unity.Addressables , facultatif AddressableProvider .
Le chemin de fichier UserDataProvider est le suivant:

ResourcesProvider Placez les fichiers dans le dossier des modèles dans le dossier Ressources.
StreamingAssetsProvider Placez les fichiers dans le dossier Modèles dans le dossier StreamingAssets.
Address AddressablesProvider de est le suivant:

Unichat est basé sur Langchain C # en utilisant une structure de chaîne pour connecter les composants en série.
Vous pouvez voir un échantillon dans l'exemple de Repo.
L'utilisation simple est la suivante:
public class LLM_Chain_Example : MonoBehaviour
{
public LLMSettingsAsset settingsAsset ;
public AudioSource audioSource ;
public async void Start ( )
{
var chatPrompt = @"
You are an AI assistant that greets the world.
User: Hello!
Assistant:" ;
var llm = LLMFactory . Create ( LLMType . ChatGPT , settingsAsset ) ;
//Create chain
var chain =
Chain . Set ( chatPrompt , outputKey : "prompt" )
| Chain . LLM ( llm , inputKey : "prompt" , outputKey : "chatResponse" ) ;
//Run chain
string result = await chain . Run < string > ( "chatResponse" ) ;
Debug . Log ( result ) ;
}
} L'exemple ci-dessus utilise Chain pour appeler directement LLM, mais pour simplifier la recherche de la base de données et faciliter l'ingénierie, il est recommandé d'utiliser ChatPipelineCtrl comme le début de la chaîne.
Si vous exécutez l'exemple suivant, la première fois que vous appelez LLM et la deuxième fois que vous répondez directement à partir de la base de données.
public async void Start ( )
{
//Create new chat model file with empty memory and embedding db
var chatModelFile = new ChatModelFile ( ) { fileName = "NewChatFile" , modelProvider = ModelProvider . AddressableProvider } ;
//Create an pipeline ctrl to run it
var pipelineCtrl = new ChatPipelineCtrl ( chatModelFile , settingsAsset ) ;
pipelineCtrl . SwitchGenerator ( ChatGeneratorIds . ChatGPT ) ;
//Init pipeline, set verbose to log status
await pipelineCtrl . InitializePipeline ( new PipelineConfig { verbose = true } ) ;
//Add system prompt
pipelineCtrl . Memory . Context = "You are my personal assistant, you should answer my questions." ;
//Create chain
var chain = pipelineCtrl . ToChain ( ) . Input ( "Hello assistant!" ) . CastStringValue ( outputKey : "text" ) ;
//Run chain
string result = await chain . Run < string > ( "text" ) ;
//Save chat model
pipelineCtrl . SaveModel ( ) ;
} Vous pouvez tracer la chaîne à l'aide de la méthode Trace() , ou ajouter un symbole de script UNICHAT_ALWAYS_TRACE_CHAIN dans les paramètres du projet.
| Nom de méthode | Type de retour | Description |
|---|---|---|
Trace(stackTrace, applyToContext) | void | Chaîne de trace |
stackTrace: bool | Permet le traçage de pile | |
applyToContext: bool | S'applique à toutes les sous-chaines |

Si vous avez une solution de synthèse de la parole, vous pouvez vous référer à VitsClient la mise en œuvre d'un composant TTS?.
Vous pouvez utiliser AudioCache pour stocker la parole afin qu'il puisse être lu lorsque vous prenez une réponse dans la base de données en mode hors ligne.
public class LLM_TTS_Chain_Example : MonoBehaviour
{
public LLMSettingsAsset settingsAsset ;
public AudioSource audioSource ;
public async void Start ( )
{
//Create new chat model file with empty memory and embedding db
var chatModelFile = new ChatModelFile ( ) { fileName = "NewChatFile" , modelProvider = ModelProvider . AddressableProvider } ;
//Create an pipeline ctrl to run it
var pipelineCtrl = new ChatPipelineCtrl ( chatModelFile , settingsAsset ) ;
pipelineCtrl . SwitchGenerator ( ChatGeneratorIds . ChatGPT , true ) ;
//Init pipeline, set verbose to log status
await pipelineCtrl . InitializePipeline ( new PipelineConfig { verbose = true } ) ;
var vits = new VITSModel ( lang : "ja" ) ;
//Add system prompt
pipelineCtrl . Memory . Context = "You are my personal assistant, you should answer my questions." ;
//Create cache to cache audioClips and translated texts
var audioCache = AudioCache . CreateCache ( chatModelFile . DirectoryPath ) ;
var textCache = TextMemoryCache . CreateCache ( chatModelFile . DirectoryPath ) ;
//Create chain
var chain = pipelineCtrl . ToChain ( ) . Input ( "Hello assistant!" ) . CastStringValue ( outputKey : "text" )
//Translate to japanese
| Chain . Translate ( new GoogleTranslator ( "en" , "ja" ) ) . UseCache ( textCache )
//Split them
| Chain . Split ( new RegexSplitter ( @"(?<=[。!?! ?])" ) , inputKey : "translated_text" )
//Auto batched
| Chain . TTS ( vits , inputKey : "splitted_text" ) . UseCache ( audioCache ) . Verbose ( true ) ;
//Run chain
( IReadOnlyList < string > segments , IReadOnlyList < AudioClip > audioClips )
= await chain . Run < IReadOnlyList < string > , IReadOnlyList < AudioClip > > ( "splitted_text" , "audio" ) ;
//Play audios
for ( int i = 0 ; i < audioClips . Count ; ++ i )
{
Debug . Log ( segments [ i ] ) ;
audioSource . clip = audioClips [ i ] ;
audioSource . Play ( ) ;
await UniTask . WaitUntil ( ( ) => ! audioSource . isPlaying ) ;
}
}
}Vous pouvez utiliser un service de discours à texte tel que Whisper.Unity pour l'inférence locale ?.
public void RunSTTChain ( AudioClip audioClip )
{
WhisperModel whisperModel = await WhisperModel . FromPath ( modelPath ) ;
var chain = Chain . Set ( audioClip , "audio" )
| Chain . STT ( whisperModel , new WhisperSettings ( ) {
language = "en" ,
initialPrompt = "The following is a paragraph in English."
} ) ;
Debug . Log ( await chain . Run ( "text" ) ) ;
}Vous pouvez réduire la dépendance à l'égard de LLM en formant un classificateur en aval sur la base du modèle intégré pour effectuer certaines tâches de reconnaissance dans le jeu (comme le classificateur d'expression?).
Avis
1. Vous devez faire le composant dans un environnement Python.
2. Actuellement, Sentis vous oblige toujours à exporter manuellement vers le format ONNX
Meilleures pratiques: utilisez un modèle intégré pour générer des traits à partir de vos données de formation avant la formation. Seul le modèle en aval doit être exporté par la suite.
Ce qui suit est un exemple shape=(512,768,20) d'un classificateur Perceptron multicouche avec une taille d'exportation de seulement 1,5 Mo:
class SubClassifier ( nn . Module ):
#input_dim is the output dim of your embedding model
def __init__ ( self , input_dim , hidden_dim , output_dim ):
super ( CustomClassifier , self ). __init__ ()
self . fc1 = nn . Linear ( input_dim , hidden_dim )
self . relu = nn . ReLU ()
self . dropout = nn . Dropout ( p = 0.1 )
self . fc2 = nn . Linear ( hidden_dim , output_dim )
def forward ( self , x ):
x = self . fc1 ( x )
x = self . relu ( x )
x = self . dropout ( x )
x = self . fc2 ( x )
return x Les composants de jeu sont divers outils combinés avec la fonction de dialogue en fonction du mécanisme de jeu spécifique.
Un statemachine qui change les états en fonction du contenu du chat. La nidification de Statemachine (sous-étatmachine) n'est pas actuellement soutenue. Selon la conversation, vous pouvez sauter dans différents états et exécuter l'ensemble de comportements correspondant, similaire à la machine d'état animée d'Unity.
public void BuildStateMachine ( )
{
chatStateMachine = new ChatStateMachine ( dim : 512 ) ;
chatStateMachineCtrl = new ChatStateMachineCtrl (
TextEncoder : encoder ,
//Input a host Unity.Object
hostObject : gameObject ,
layer : 1
) ;
chatStateMachine . AddState ( "Stand" ) ;
chatStateMachine . AddState ( "Sit" ) ;
chatStateMachine . states [ 0 ] . AddBehavior < StandBehavior > ( ) ;
chatStateMachine . states [ 0 ] . AddTransition ( new LazyStateReference ( "Sit" ) ) ;
// Add a conversion directive and set scoring thresholds and conditions
chatStateMachine . states [ 0 ] . transitions [ 0 ] . AddCondition ( ChatConditionMode . Greater , 0.6f , "I sit down" ) ;
chatStateMachine . states [ 0 ] . transitions [ 0 ] . AddCondition ( ChatConditionMode . Greater , 0.6f , "I want to have a rest on chair" ) ;
chatStateMachine . states [ 1 ] . AddBehavior < SitBehavior > ( ) ;
chatStateMachine . states [ 1 ] . AddTransition ( new LazyStateReference ( "Stand" ) ) ;
chatStateMachine . states [ 1 ] . transitions [ 0 ] . AddCondition ( ChatConditionMode . Greater , 0.6f , "I'm well rested" ) ;
chatStateMachineCtrl . SetStateMachine ( 0 , chatStateMachine ) ;
}
public void LoadFromBytes ( string bytesFilePath )
{
chatStateMachineCtrl . Load ( bytesFilePath ) ;
} public class CustomChatBehavior : ChatStateMachineBehavior
{
private GameObject hostGameObject ;
public override void OnStateMachineEnter ( UnityEngine . Object hostObject )
{
//Get host Unity.Object
hostGameObject = hostObject as GameObject ;
}
public override void OnStateEnter ( )
{
//Do something
}
public override void OnStateUpdate ( )
{
//Do something
}
public override void OnStateExit ( )
{
//Do something
}
} private void RunStateMachineAfterPipeline ( )
{
var chain = PipelineCtrl . ToChain ( ) . Input ( "Your question." ) . CastStringValue ( "stringValue" )
| new StateMachineChain ( chatStateMachineCtrl , "stringValue" ) ;
await chain . Run ( ) ;
}Invoquez des outils basés sur le flux de travail ReactAgent.
Voici un exemple:
var userCommand = @"I want to watch a dance video." ;
var llm = LLMFactory . Create ( LLMType . ChatGPT , settingsAsset ) as OpenAIClient ;
llm . StopWords = new ( ) { " n Observation:" , " n t Observation:" } ;
//Create agent with muti-tools
var chain =
Chain . Set ( userCommand )
| Chain . ReActAgentExecutor ( llm )
. UseTool ( new AgentLambdaTool (
"Play random dance video" ,
@"A wrapper to select random dance video and play it. Input should be 'None'." ,
( e ) =>
{
PlayRandomDanceVideo ( ) ;
//Notice agent it finished its work
return UniTask . FromResult ( "Dance video 'Queencard' is playing now." ) ;
} ) )
. UseTool ( new AgentLambdaTool (
"Sleep" ,
@"A wrapper to sleep." ,
( e ) =>
{
return UniTask . FromResult ( "You are now sleeping." ) ;
} ) )
. Verbose ( true ) ;
//Run chain
Debug . Log ( await chain . Run ( "text" ) ) ; Voici quelques applications que j'ai faites. Puisqu'ils incluent certains plugins commerciaux, seules des versions de construction sont disponibles.
Voir page de version
Basé sur Unichat pour faire une application similaire dans l'unité
La version du référentiel synchronisé est
V0.0.1-alpha, la démo attend d'être mise à jour.

Voir page de version

Il contient des composants comportementaux et vocaux et n'est pas encore disponible.
Demo utilise TavernAI la structure des données du caractère, et nous pouvons écrire la personnalité du personnage, des exemples de conversations et des scénarios de chat en images.

Si vous utilisez une carte de caractères TavernAI , le mot de repère ci-dessus est écrasé.
https://www.akikurisu.com/blog/posts/create-chatbox-in-unity-2024-03-19/
https://www.akikurisu.com/blog/posts/use-nlp-in-unity-2024-04-03/