中文 | Englisch
Eine Pipeline zum Erstellen von Online- und Offline-Chat-Bot in Einheit.

Mit der Veröffentlichung von Unity.Sentis können wir einige neuronale Netzwerkmodelle zur Laufzeit verwenden, einschließlich des Textbettungsmodells für die Verarbeitung natürlicher Sprache.
Obwohl es in Spielen nichts Neues ist, mit KI zu chatten, ist es ein schwieriger Punkt, in Spielen ein Gespräch zu entwerfen, das nicht von den Ideen des Entwicklers abweichen, aber flexibler ist.
UniChat basiert auf Unity.Sentis .
Wenn Sie den Online -Modus verwenden, enthält UniChat natürlich auch ein Ketten -Toolkit, das auf Langchain basiert, um LLM und Agent schnell in das Spiel einzubetten.
Das Folgende ist das Flussdiagramm von Unichat. In der Local Inference Inferenzbox befinden sich die Funktionen, die offline verwendet werden können:

manifest.json hinzu: {
"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 mit 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 ( ) ;
} Das Einbettungsmodell wird standardmäßig BAAI/bge-small-zh-v1.5 verwendet und nimmt den geringsten Videospeicher ein. Es kann in der Veröffentlichung heruntergeladen werden, unterstützt jedoch nur Chinesen. Sie können dasselbe Modell von HuggingFaceHub herunterladen und es in das ONNX -Format konvertieren.
Der Lademodus ist optional UserDataProvider , StreamingAssetsProvider und ResourcesProvider AddressableProvider falls Unity.Addressables .
Der UserDataProvider -Dateipfad lautet wie folgt:

ResourcesProvider platzieren Sie die Dateien im Ordner "Modelle" im Ordner für Ressourcen.
StreamingAssetsProvider legen die Dateien im Ordner "Modelle" im Ordner streamingassets ein.
AddressablesProvider von IS wie folgt:

Unichat basiert auf Langchain C# unter Verwendung einer Kettenstruktur, um Komponenten in Reihe zu verbinden.
Sie können ein Beispiel in Repos Beispiel sehen.
Die einfache Verwendung ist wie folgt:
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 ) ;
}
} Das obige Beispiel verwendet Chain , um LLM direkt aufzurufen. Um die Datenbank zu durchsuchen und das Engineering zu erleichtern, wird empfohlen, ChatPipelineCtrl als Beginn der Kette zu verwenden.
Wenn Sie das folgende Beispiel ausführen, nennen Sie das erste Mal LLM und beim zweiten Mal direkt aus der Datenbank.
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 ( ) ;
} Sie können die Kette mit der Trace() -Methode verfolgen oder in Projekteinstellungen UNICHAT_ALWAYS_TRACE_CHAIN -Skriptsymbolen hinzufügen.
| Methodenname | Rückgabetyp | Beschreibung |
|---|---|---|
Trace(stackTrace, applyToContext) | void | Spurenkette |
stackTrace: bool | Ermöglicht die Stapelverfolgung | |
applyToContext: bool | Gilt für alle Subchains |

Wenn Sie eine Sprachsynthese -Lösung haben, können Sie die Implementierung einer TTS -Komponente auf vitsclient verweisen?.
Sie können AudioCache verwenden, um Sprache zu speichern, damit sie gespielt werden kann, wenn Sie eine Antwort aus der Datenbank im Offline -Modus abholen.
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 ) ;
}
}
}Sie können einen Sprach-zu-Text-Dienst wie Whisper.unity für lokale Inferenz verwenden?.
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" ) ) ;
}Sie können die Abhängigkeit von LLM auf der Grundlage des eingebetteten Modells durch Training eines nachgelagerten Klassifikators verringern, um einige Erkennungsaufgaben im Spiel (wie Ausdrucksklassifikator?) Auszuführen.
Beachten
1. Sie müssen die Komponente in einer Python -Umgebung herstellen.
2. Derzeit verlangt Sentis immer noch, dass Sie manuell im ONNX -Format exportieren müssen
Best Practice: Verwenden Sie ein eingebettetes Modell, um vor dem Training Merkmale aus Ihren Trainingsdaten zu generieren. Nur das nachgelagerte Modell muss anschließend exportiert werden.
Das Folgende ist eine Beispielform shape=(512,768,20) eines Mehrschicht-Perzeptron-Klassifizierers mit einer Exportgröße von nur 1,5 MB:
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 Spielkomponenten sind verschiedene Tools, die mit der Dialogfunktion gemäß dem spezifischen Spielmechanismus kombiniert werden.
Eine Statemachine, die die Zustände entsprechend dem Chat -Inhalt wechselt. Statemachine Nisting (Substatemachine) wird derzeit nicht unterstützt. Abhängig von der Konversation können Sie in verschiedene Zustände springen und die entsprechenden Verhaltensweisen ausführen, ähnlich der animierten Zustandsmaschine von 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 ( ) ;
}Rufen Sie Tools auf, die auf Reaktagent -Workflow basieren.
Hier ist ein Beispiel:
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" ) ) ; Hier sind einige Apps, die ich gemacht habe. Da sie einige kommerzielle Plugins enthalten, sind nur Build -Versionen verfügbar.
Siehe Release -Seite
Basierend auf Unichat, um eine ähnliche Anwendung in Einheit zu stellen
Die synchronisierte Repository-Version ist
V0.0.1-alpha, die Demo wartet darauf, aktualisiert zu werden.

Siehe Release -Seite

Es enthält Verhaltens- und Sprachkomponenten und ist noch nicht verfügbar.
Demo verwendet TavernAI die Charakterdatenstruktur, und wir können die Persönlichkeit des Charakters, die Beispielgespräche und die Chat -Szenarien in Bilder schreiben.

Wenn Sie TavernAI eine Charakterkarte verwenden, wird das obige Hinweis über überschrieben.
https://www.akikurisu.com/blog/posts/create-chatbox-inity-2024-03-19/
https://www.akikurisu.com/blog/posts/use-nlp-inity-2024-04-03/