Skip to content

Commit

Permalink
refactor: use a consistent prefix for auto-generated names (#2783)
Browse files Browse the repository at this point in the history
Co-authored-by: Tushar Mathur <[email protected]>
  • Loading branch information
laststylebender14 and tusharmath authored Sep 21, 2024
1 parent 2cf0f58 commit 7144fd3
Show file tree
Hide file tree
Showing 44 changed files with 614 additions and 491 deletions.
47 changes: 47 additions & 0 deletions examples/generate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
inputs:
- curl:
src: "https://jsonplaceholder.typicode.com/posts/1"
headers:
Content-Type: application/json
Accept: application/json
fieldName: post
- curl:
src: "https://jsonplaceholder.typicode.com/users/1"
fieldName: user
- curl:
src: "https://jsonplaceholder.typicode.com/users"
fieldName: users
- curl:
src: "https://jsonplaceholder.typicode.com/posts"
fieldName: posts
- curl:
src: "https://jsonplaceholder.typicode.com/comments"
fieldName: comments
- curl:
src: "https://jsonplaceholder.typicode.com/comments/1"
fieldName: comment
- curl:
src: "https://jsonplaceholder.typicode.com/photos"
fieldName: photos
- curl:
src: "https://jsonplaceholder.typicode.com/photos/1"
fieldName: photo
- curl:
src: "https://jsonplaceholder.typicode.com/todos"
fieldName: todos
- curl:
src: "https://jsonplaceholder.typicode.com/todos/1"
fieldName: todo
- curl:
src: "https://jsonplaceholder.typicode.com/comments?postId=1"
fieldName: postComments
preset:
mergeType: 1
consolidateURL: 0.5
treeShake: true
inferTypeNames: true
output:
path: "./jsonplaceholder.graphql"
format: graphQL
schema:
query: Query
85 changes: 65 additions & 20 deletions src/cli/llm/infer_type_name.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use std::collections::HashMap;

use genai::chat::{ChatMessage, ChatRequest, ChatResponse};
use indexmap::{indexset, IndexSet};
use serde::{Deserialize, Serialize};
use serde_json::json;

use super::{Error, Result, Wizard};
use crate::core::config::Config;
use crate::core::generator::PREFIX;
use crate::core::Mustache;

const BASE_TEMPLATE: &str = include_str!("prompts/infer_type_name.md");

pub struct InferTypeName {
wizard: Wizard<Question, Answer>,
}
Expand All @@ -29,44 +33,51 @@ impl TryFrom<ChatResponse> for Answer {

#[derive(Clone, Serialize)]
struct Question {
#[serde(skip_serializing_if = "IndexSet::is_empty")]
ignore: IndexSet<String>,
fields: Vec<(String, String)>,
}

#[derive(Serialize)]
struct Context {
input: Question,
output: Answer,
}

impl TryInto<ChatRequest> for Question {
type Error = Error;

fn try_into(self) -> Result<ChatRequest> {
let input = serde_json::to_string_pretty(&Question {
let input = Question {
ignore: indexset! { "User".into()},
fields: vec![
("id".to_string(), "String".to_string()),
("name".to_string(), "String".to_string()),
("age".to_string(), "Int".to_string()),
],
})?;
};

let output = serde_json::to_string_pretty(&Answer {
let output = Answer {
suggestions: vec![
"Person".into(),
"Profile".into(),
"Member".into(),
"Individual".into(),
"Contact".into(),
],
})?;
};

let template_str = include_str!("prompts/infer_type_name.md");
let template = Mustache::parse(template_str);
let template = Mustache::parse(BASE_TEMPLATE);

let context = json!({
"input": input,
"output": output,
});
let context = Context { input, output };

let rendered_prompt = template.render(&context);
let rendered_prompt = template.render(&serde_json::to_value(&context)?);

Ok(ChatRequest::new(vec![
ChatMessage::system(rendered_prompt),
ChatMessage::user(serde_json::to_string(&self)?),
ChatMessage::user(serde_json::to_string(&json!({
"fields": &self.fields,
}))?),
]))
}
}
Expand All @@ -76,20 +87,35 @@ impl InferTypeName {
Self { wizard: Wizard::new(model, secret) }
}

/// All generated type names starts with PREFIX
#[inline]
fn is_auto_generated(type_name: &str) -> bool {
type_name.starts_with(PREFIX)
}

pub async fn generate(&mut self, config: &Config) -> Result<HashMap<String, String>> {
let mut new_name_mappings: HashMap<String, String> = HashMap::new();

// removed root type from types.
// Filter out root operation types and types with non-auto-generated names
let types_to_be_processed = config
.types
.iter()
.filter(|(type_name, _)| !config.is_root_operation_type(type_name))
.filter(|(type_name, _)| {
!config.is_root_operation_type(type_name) && Self::is_auto_generated(type_name)
})
.collect::<Vec<_>>();

let mut used_type_names = config
.types
.iter()
.filter(|(ty_name, _)| !Self::is_auto_generated(ty_name))
.map(|(ty_name, _)| ty_name.to_owned())
.collect::<IndexSet<_>>();

let total = types_to_be_processed.len();
for (i, (type_name, type_)) in types_to_be_processed.into_iter().enumerate() {
// convert type to sdl format.
let question = Question {
ignore: used_type_names.clone(),
fields: type_
.fields
.iter()
Expand All @@ -104,12 +130,11 @@ impl InferTypeName {
Ok(answer) => {
let name = &answer.suggestions.join(", ");
for name in answer.suggestions {
if config.types.contains_key(&name)
|| new_name_mappings.contains_key(&name)
{
if config.types.contains_key(&name) || used_type_names.contains(&name) {
continue;
}
new_name_mappings.insert(name, type_name.to_owned());
used_type_names.insert(name.clone());
new_name_mappings.insert(type_name.to_owned(), name);
break;
}
tracing::info!(
Expand Down Expand Up @@ -142,19 +167,22 @@ impl InferTypeName {
}
}

Ok(new_name_mappings.into_iter().map(|(k, v)| (v, k)).collect())
Ok(new_name_mappings)
}
}

#[cfg(test)]
mod test {
use genai::chat::{ChatRequest, ChatResponse, MessageContent};
use indexmap::indexset;

use super::{Answer, Question};
use crate::cli::llm::InferTypeName;

#[test]
fn test_to_chat_request_conversion() {
let question = Question {
ignore: indexset! {"Profile".to_owned(), "Person".to_owned()},
fields: vec![
("id".to_string(), "String".to_string()),
("name".to_string(), "String".to_string()),
Expand All @@ -176,4 +204,21 @@ mod test {
let answer = Answer::try_from(resp).unwrap();
insta::assert_debug_snapshot!(answer);
}

#[test]
fn test_is_auto_generated() {
assert!(InferTypeName::is_auto_generated("GEN__T1"));
assert!(InferTypeName::is_auto_generated("GEN__T1234"));
assert!(InferTypeName::is_auto_generated("GEN__M1"));
assert!(InferTypeName::is_auto_generated("GEN__M5678"));
assert!(InferTypeName::is_auto_generated("GEN__Some__Type"));

assert!(!InferTypeName::is_auto_generated("Some__Type"));
assert!(!InferTypeName::is_auto_generated("User"));
assert!(!InferTypeName::is_auto_generated("T123"));
assert!(!InferTypeName::is_auto_generated("M1"));
assert!(!InferTypeName::is_auto_generated(""));
assert!(!InferTypeName::is_auto_generated("123T"));
assert!(!InferTypeName::is_auto_generated("A1234"));
}
}
4 changes: 1 addition & 3 deletions src/cli/llm/prompts/infer_type_name.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Given the sample schema of a GraphQL type, suggest 5 meaningful names for it.
The name should be concise and preferably a single word.
The name should be concise, preferably a single word and must not be in the `ignore` list.

Example Input:
{{input}}
Expand All @@ -8,5 +8,3 @@ Example Output:
{{output}}

Ensure the output is in valid JSON format.

Do not add any additional text before or after the JSON.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ChatRequest {
ChatMessage {
role: System,
content: Text(
"Given the sample schema of a GraphQL type, suggest 5 meaningful names for it.\nThe name should be concise and preferably a single word.\n\nExample Input:\n{\n \"fields\": [\n [\n \"id\",\n \"String\"\n ],\n [\n \"name\",\n \"String\"\n ],\n [\n \"age\",\n \"Int\"\n ]\n ]\n}\n\nExample Output:\n{\n \"suggestions\": [\n \"Person\",\n \"Profile\",\n \"Member\",\n \"Individual\",\n \"Contact\"\n ]\n}\n\nEnsure the output is in valid JSON format.\n\nDo not add any additional text before or after the JSON.\n",
"Given the sample schema of a GraphQL type, suggest 5 meaningful names for it.\nThe name should be concise, preferably a single word and must not be in the `ignore` list.\n\nExample Input:\n{\"ignore\":[\"User\"],\"fields\":[[\"id\",\"String\"],[\"name\",\"String\"],[\"age\",\"Int\"]]}\n\nExample Output:\n{\"suggestions\":[\"Person\",\"Profile\",\"Member\",\"Individual\",\"Contact\"]}\n\nEnsure the output is in valid JSON format.\n",
),
extra: None,
},
Expand Down
7 changes: 5 additions & 2 deletions src/core/config/transformer/improve_type_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use convert_case::{Case, Casing};

use super::RenameTypes;
use crate::core::config::Config;
use crate::core::generator::PREFIX;
use crate::core::transform::Transform;
use crate::core::valid::Valid;

Expand Down Expand Up @@ -77,8 +78,10 @@ impl<'a> CandidateGeneration<'a> {
fn generate(mut self) -> CandidateConvergence<'a> {
for (type_name, type_info) in self.config.types.iter() {
for (field_name, field_info) in type_info.fields.iter() {
if self.config.is_scalar(field_info.type_of.name()) {
// If field type is scalar then ignore type name inference.
if self.config.is_scalar(field_info.type_of.name())
|| field_name.starts_with(PREFIX)
{
// If field type is scalar or auto generated then ignore type name inference.
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ schema @server @upstream {
query: Query
}

type M1 {
type GEN__M1 {
body: String
id: Int
is_verified: Boolean
t1: M1
t1: GEN__M1
userId: Int
}

type Query {
q1: M1
q2: M1
q1: GEN__M1
q2: GEN__M1
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ input Far {
tar: String
}

input M1 {
input GEN__M1 {
tar: String
}

type Query {
bar(input: M1): String @http(path: "/bar")
bar(input: GEN__M1): String @http(path: "/bar")
far(input: Far): String @http(path: "/far")
foo(input: M1): String @http(path: "/foo")
foo(input: GEN__M1): String @http(path: "/foo")
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
source: src/core/config/transformer/merge_types/type_merger.rs
expression: config.to_sdl()
---
interface M1 {
interface GEN__M1 {
a: Int
}

type C implements M1 {
type C implements GEN__M1 {
a: Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ schema @server @upstream {
query: Query
}

type M1 {
type GEN__M1 {
id: Int
name: JSON
}

type Query {
bar: M1
foo: M1
bar: GEN__M1
foo: GEN__M1
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ schema @server @upstream {
query: Query
}

type M1 {
type GEN__M1 {
f1: String
f2: Int
f3: Boolean
Expand All @@ -15,8 +15,8 @@ type M1 {
}

type Query {
q1: M1
q2: M1
q3: M1
q4: M1
q1: GEN__M1
q2: GEN__M1
q3: GEN__M1
q4: GEN__M1
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ schema @server @upstream(baseURL: "http://jsonplacheholder.typicode.com") {
query: Query
}

union FooBar = Foo | M1
union FooBar = Foo | GEN__M1

type Foo {
a: String
bar: String
foo: String
}

type M1 {
type GEN__M1 {
bar: String
}

Expand Down
Loading

1 comment on commit 7144fd3

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running 30s test @ http://localhost:8000/graphql

4 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev
Latency 11.48ms 4.40ms 124.61ms 86.77%
Req/Sec 2.21k 277.81 2.97k 83.33%

264253 requests in 30.04s, 1.32GB read

Requests/sec: 8796.09

Transfer/sec: 45.15MB

Please sign in to comment.